SQL의 WHERE 조건은 단순히 필터 역할을 넘어 쿼리 성능의 핵심 요소입니다.
특히, 조건절에 어떤 컬럼이 들어가느냐, 그리고 그 컬럼에 인덱스가 있느냐에 따라 쿼리 속도가 수배에서 수십 배까지 차이 날 수 있습니다.
이번 글에서는 WHERE절 조건을 중심으로 SQL 튜닝 실습을 진행해보겠습니다.
실습: Sales 부서이면서 최근 3일 이내에 가입한 유저 조회
데이터셋 준비
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
department VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 100만 건 더미 데이터 생성
SET SESSION cte_max_recursion_depth = 1000000;
INSERT INTO users (name, department, created_at)
WITH RECURSIVE cte (n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM cte WHERE n < 1000000
)
SELECT
CONCAT('User', LPAD(n, 7, '0')),
CASE
WHEN n % 10 = 3 THEN 'Sales'
ELSE 'Other'
END,
TIMESTAMP(DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 3650) DAY) + INTERVAL FLOOR(RAND() * 86400) SECOND)
FROM cte;
기본 조회
SELECT *
FROM users
WHERE department = 'Sales'
AND created_at >= DATE_SUB(NOW(), INTERVAL 3 DAY);
→ 조회 시간은 약 0.294s이고, 실행 계획에서 type = ALL인 것과 더불어 searching row 수가 약99만건에 달하는 것을 알 수 있습니다.
이를 통해 해당 쿼리를 실행할 때 Full Table Scan을 했다는 것을 알 수 있습니다. → 튜닝 필요
지금부터 여러 컬럼에 인덱스를 적용하면서 성능 비교를 해보도록 하겠습니다.
1. created_at 기준 인덱스
CREATE INDEX idx_created_at ON users(created_at);
→ 조회 시간은 약 0.025s이고, 실행 계획에서 type이 range로 변경되었으며, searching row 수도 약 1,000개 정도로 대폭 적어진 것을 확인할 수 있었습니다.
2. department 기준 인덱스
ALTER TABLE users DROP INDEX idx_created_at; -- created_at 인덱스 제거
CREATE INDEX idx_department ON users(department);
→ 조회 시간은 약 0.285s이고, 실행 계획에서 type이 ref로 변경되었으며, searching row 수는 191,314로 type=ALL보다 적어지긴 했지만, created_at 보다 좋지 않은 성능을 보이고 있습니다.
row 수를 통해 이러한 차이는 인덱스로 설정된 department 컬럼의 중복성이 created_at보다 높기 때문이라는 점으로 추측할 수 있습니다.
3. created_at, department 기준 인덱스
ALTER TABLE users DROP INDEX idx_department; -- 기존 department 인덱스 제거, created_at 인덱스만 추가할 수 있지만 이해 편의상 기존 인덱스 제거 후 두 인덱스 추가
CREATE INDEX idx_created_at ON users(created_at);
CREATE INDEX idx_department ON users (department);
→ 조회 시간은 약 0.021s이고, 실행 계획에서 type이 range로 변경되었으며, searching row 수도 약 1,000개 정도로 적어진 것을 확인할 수 있었습니다.
지금까지 가장 좋은 성능을 보이고 있습니다.
그러면 이 인덱스로 설정해야 할까요?
아닙니다.
EXPLAIN을 보시면 possible_keys에서는 created_at과 department가 key로 인식이 되었지만, 정작 실제 사용된 인덱스는 우측의 key에서 보이다시피 created_at만 사용되었습니다.
이는 MySQL의 옵티마이저에서 자체적으로 유용한 인덱스로 created_at만 사용하는 것으로 판단을 내렸다는 내용입니다.
따라서, 이러한 경우에서는 실질적으로 성능 향상에 유의미한 created_at 컬럼만 사용하는 것이 효율적입니다
4. created_at, department 멀티 컬럼 인덱스
ALTER TABLE users DROP INDEX idx_department;
ALTER TABLE users DROP INDEX idx_created_at;
CREATE INDEX idx_created_at_dept ON users(created_at, department);
-- 또는
CREATE INDEX idx_dept_created_at ON users(department, created_at);
→ 조회 시간은 약 0.022s이고, 실행 계획에서 type이 range로 변경되었으며, searching row 수도 약 1,000개 정도로 이전 결과와 크게 차이나지 않는 것을 알 수 있었습니다.
→ 멀티 컬럼 인덱스가 항상 더 빠른 것은 아니며, 단일 컬럼 인덱스만으로도 충분할 수 있습니다.
'SQL' 카테고리의 다른 글
[DB Optimization#13] ORDER BY 문 튜닝 (1) | 2025.04.17 |
---|---|
[DB Optimization#12] 인덱스가 안 먹히나요? (0) | 2025.04.17 |
[DB Optimization#10] 한 번에 너무 많은 데이터를 조회하지 마세요 (0) | 2025.04.17 |
[DB Optimization#9] 실행 계획(Execution Plan)이란? (0) | 2025.04.17 |
[DB Optimization#8] 커버링 인덱스(Covering Index)란? (0) | 2025.04.17 |