쿼리성능개선은 어떻게 하는가(기초)

Junkyu_Kang·2024년 12월 5일

쿼리성능개선은 어떻게 하는가!

1. 인덱스 최적화!

시나리오
데이터베이스: 1백만 건의 사용자 테이블

테이블 구조:

sqlCopyCREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    last_name VARCHAR(50),
    city VARCHAR(50),
    created_at TIMESTAMP
);

!성능 측정 단계

초기 쿼리 성능 확인

sqlCopyEXPLAIN ANALYZE 
SELECT * FROM users 
WHERE last_name = 'Kim' AND city = 'Seoul';

결과 예시:

실행 시간: 1.5초
전체 테이블 스캔 (비효율적)
성능 저하 원인: 인덱스 부재

인덱스 생성

sqlCopyCREATE INDEX idx_last_name_city ON users(last_name, city);

인덱스 후 성능 비교

sqlCopyEXPLAIN ANALYZE 
SELECT * FROM users 
WHERE last_name = 'Kim' AND city = 'Seoul';

개선 결과:

실행 시간: 0.05초 (약 30배 성능 향상)
인덱스 스캔으로 변경
디스크 I/O 최소화

2. 조인 최적화 - 실무 사례

복잡한 조인 시나리오

sqlCopy-- 비효율적인 원본 쿼리
SELECT o.id, c.name, p.product_name, o.total_price
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN products p ON o.product_id = p.id
WHERE c.city = 'Seoul' AND o.order_date > '2023-01-01';

성능 분석

실행 시간: 2.3초
3개 테이블 풀 스캔
복잡한 조인 연산

3. 최적화 접근

조인 순서 최적화
인덱스 추가
서브쿼리로 변환

최적화된 쿼리

방법 1: 조인 순서 및 인덱스 최적화
CREATE INDEX idx_customer_city ON customers(city);
CREATE INDEX idx_order_date ON orders(order_date);

SELECT /*+ USE_NL(c o) */ 
    o.id, c.name, p.product_name, o.total_price
FROM customers c
JOIN orders o ON c.id = o.customer_id
JOIN products p ON o.product_id = p.id
WHERE c.city = 'Seoul' AND o.order_date > '2023-01-01';

-- 방법 2: 서브쿼리 최적화
SELECT o.id, 
       (SELECT name FROM customers WHERE id = o.customer_id) as customer_name,
       (SELECT product_name FROM products WHERE id = o.product_id) as product_name,
       o.total_price
FROM orders o
WHERE o.customer_id IN (
    SELECT id FROM customers WHERE city = 'Seoul'
) AND o.order_date > '2023-01-01';

성능 비교

원본 쿼리: 2.3초
최적화 쿼리: 0.3초 (약 7.6배 성능 향상)

4.쿼리 캐싱 - 구체적 적용

Redis 기반 쿼리 캐싱 예시

pythonCopyimport redis
import json

redis_client = redis.Redis(host='localhost', port=6379, db=0)

def cached_database_query(query, params, expire_time=3600):
    # 캐시 키 생성
    cache_key = f"query:{hash(query)}:{hash(str(params))}"
    
    # 캐시된 결과 확인
    cached_result = redis_client.get(cache_key)
    if cached_result:
        return json.loads(cached_result)
    
    # 데이터베이스 쿼리 실행
    result = execute_database_query(query, params)
    
    # 결과 캐싱
    redis_client.setex(cache_key, expire_time, json.dumps(result))
    
    return result

캐싱 효과

반복 쿼리 응답 시간: 10ms 이내
데이터베이스 부하 감소
캐시 적중률 80% 이상

5. 파티셔닝 전략

대규모 테이블 파티셔닝

sqlCopyCREATE TABLE sales (
    sale_id INT,
    product_id INT,
    sale_date DATE,
    amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(sale_date)) (
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

파티셔닝 성능 이점

특정 연도 데이터 쿼리 속도 50-70% 향상
불필요한 데이터 스캔 방지
관리 용이성 증가

핵심 성능 최적화 전략:
  1. 정확한 성능 측정
  2. 인덱스 전략 수립
  3. 쿼리 구조 최적화
  4. 캐싱 메커니즘 도입
  5. 주기적 모니터링

확실히 쿼리를 짜다보면 성능개선 부분에 대해 생각하게 된다..
이번에도 energy_Resource가 1만개인데 join을 남발해서 혼나지 않았는가..

그래서 생각한 답은 2개다. indexing을 해보자, partitioning을 해보자.

그 결과를 어떻게 봐야할까 하다가 해본 결과이다.

확실히 자주 검색하는 부분에 대해서는 캐싱을 해두거나 indexing을 조건으로 만들어두는게 좋겠다..

다음엔 안혼난다.. 아자아자

다음 쿼리성능개선편은 추후에.. 투비컨티뉴..

profile
강준규

0개의 댓글