SQL문 단순 수정으로 쿼리 튜닝하기
기본 키를 변형하는 나쁜 SQL 문
사원번호가 1100으로 시작하면서 사원번호가 5자리인 사원의 정보 모두 출력하기
AS-IS
튜닝 전 SQL 문
SELECT *
FROM 사원
WHERE SUBSTRING(사원번호, 1, 4) = 1100
AND LENGTH(사원번호) = 5;
- 0.168s 소요
튜닝 전 실행 계획
EXPLAIN SELECT *
FROM 사원
WHERE SUBSTRING(사원번호, 1, 4) = 1100
AND LENGTH(사원번호) = 5;
- 사원 테이블 하나만 존재하므로 최종 결과는 1개 행으로 출력
- type = ALL 이므로, 테이블 풀 스캔 방식을 사용하여 인덱스를 사용하지 않음 -> 필요 범위 접근이 아니라 비효율적일 수 있음
- rows = 299,246
TO-BE
튜닝 수행
SELECT COUNT(1)
FROM 사원;
SHOW INDEX FROM 사원;
- 튜닝 전 SQL문을 보면, 사원번호를 그대로 쓰는 대신 SUBSTRING과 LENGTH와 같이 가공하여 작성했으므로,
- 기본 키를 사용하지 않고 테이블 풀 스캔을 수행하게 됨
- 기본 키를 사용할 수 있도록 조정할 필요가 있음
튜닝 후 SQL 문
SELECT *
FROM 사원
WHERE 사원번호 BETWEEN 11000 AND 11009;
- 0.168s -> 0.052s 로 개선
튜닝 후 실행 계획
EXPLAIN SELECT *
FROM 사원
WHERE 사원번호 BETWEEN 11000 AND 11009;
- BETWEEN 구문에 의해 기본 키의 특정 범위만 스캔한다는 걸 알 수 있음 -> type = range
- 출력하는 사원번호가 10개 -> rows = 10
사용하지 않는 함수를 포함하는 나쁜 SQL 문
사원 테이블에서 성별 기준으로 몇 명의 사원이 있는지 출력하기
AS-IS
튜닝 전 SQL 문
SELECT IFNULL(성별, 'NO DATA') AS 성별, COUNT(1) 건수
FROM 사원
GROUP BY IFNULL(성별, 'NO DATA');
- 1.552s 소요
튜닝 전 실행 계획
EXPLAIN SELECT IFNULL(성별, 'NO DATA') AS 성별, COUNT(1) 건수
FROM 사원
GROUP BY IFNULL(성별, 'NO DATA');
- key = I_성별_성 인덱스 -> 인덱스 풀 스캔 방식 수행
- Extra = Using index; Using temporary -> 인덱스 사용, 임시 테이블 생성
TO-BE
튜닝 수행
- 사원 테이블의 성별에는 NOT NULL 조건으로 인해 NULL 값이 존재하지 않고 M/F 만 저장되고 있음
- IFNULL() 함수를 처리하기 위해 DB 내부적으로 별도의 임시테이블을 만들면서 null 여부를 검사하려 했으나, 불필요함
튜닝 후 SQL 문
SELECT 성별, COUNT(1) 건수
FROM 사원
GROUP BY 성별;
- 1.552s -> 1.363s 로 개선
튜닝 후 실행 계획
EXPLAIN SELECT 성별, COUNT(1) 건수
FROM 사원
GROUP BY 성별;
- 튜닝 수행 전과 다르게 임시 테이블 없이 인덱스만 사용하여 데이터를 추출하고 있음
열을 결합하여 사용하는 나쁜 SQL 문
사원 테이블에서 Radwan 성을 가진 남자 사원을 출력하기
AS-IS
튜닝 전 SQL 문
SELECT *
FROM 사원
WHERE CONCAT(성별, ' ', 성) = 'M Radwan';
- 0.201s 소요
튜닝 전 실행 계획
EXPLAIN
SELECT *
FROM 사원
WHERE CONCAT (성별, ' ', 성) = 'M Radwan';
- type = ALL -> CONCAT을 사용한 조건절로 데이터에 접근하지만, 풀 스캔 접근을 하고 있음
TO-BE
튜닝 수행
SELECT CONCAT(성별, ' ', 성) '성별_성', COUNT(1)
FROM 사원
WHERE CONCAT(성별, ' ', 성) = 'M Radwan'
GROUP BY CONCAT(성별, ' ', 성)
UNION ALL
SELECT '전체 데이터', COUNT(1)
FROM 사원;
- 전체 데이터는 300,024 건이고, 그중 102건의 데이터를 조회하는 것임을 알 수 있음
SHOW INDEX FROM 사원;
- I_입사일자 인덱스 = 입사일자 열
- I_성별_성 인덱스 = 성별 열 + 성 열
- WHERE 절에서 I_성별_성 인덱스를 활용하여 빠르게 조회 가능
튜닝 후 SQL 문
SELECT *
FROM 사원
WHERE 성별 = 'M' AND 성 = 'Radwan';
- 0.201s -> 0.041s 로 개선
- CONCAT 함수로 인덱스 열들을 가공하는 것 보다, 각 열을 분리하고 열의 변형을 제거하는 게 더 효과적임
튜닝 후 실행 계획
EXPLAIN
SELECT *
FROM 사원
WHERE 성별 = 'M' AND 성 = 'Radwan';
- key = I_성별_성 -> 기존엔 null 이였으나, 인덱스를 사용해 테이블에 접근하고 있음
- 튜닝 전에는 30만 건의 데이터에 접근했다면, 튜닝 후에는 102 건의 데이터에만 접근
습관적으로 중복을 제거하는 나쁜 SQL 문
부서 관리자의 사원번호, 이름, 성, 부서번호 데이터를 중복 제거하여 조회하기
AS-IS
튜닝 전 SQL 문
SELECT DISTINCT 사원.사원번호, 사원.이름, 사원.성, 부서관리자.부서번호
FROM 사원
JOIN 부서관리자
ON (사원.사원번호 = 부서관리자.사원번호);
- 0.039s 소요
튜닝 전 실행 계획
EXPLAIN SELECT DISTINCT 사원.사원번호, 사원.이름, 사원.성, 부서관리자.부서번호
FROM 사원
JOIN 부서관리자
ON (사원.사원번호 = 부서관리자.사원번호);
- 드라이빙 테이블: 부서관리자 / 드리븐 테이블: 사원
- table = 부서관리자
- type = index -> 인덱스 풀스캔 방식으로 수행
- Extra = Using temporary -> distinct를 수행하고자 별도의 임시테이블 생성
- table = 사원
- type = eq_ref & ref = tuning.부서관리자.사원번호
- 사원번호라는 primary key를 사용하여 1 건의 데이터를 조회하는 방식으로 조인됨
- type = eq_ref & ref = tuning.부서관리자.사원번호
TO-BE
튜닝 수행
DISTINCT 키워드
나열된 열들을 정렬한 뒤, 중복된 데이터는 삭제한다.
따라서 DISTINCT를 쿼리에 작성하는 것만으로도 정렬 작업이 포함됨을 인지해야 한다.
- 사원 테이블의 기본 키인 사원번호에는 중복된 데이터가 없으므로, DISTINCT 키워드를 사용할 필요 없음
튜닝 후 SQL 문
SELECT 사원.사원번호, 사원.이름, 사원.성, 부서관리자.부서번호
FROM 사원
JOIN 부서관리자
ON (사원.사원번호 = 부서관리자.사원번호);
- 0.039s -> 0.035s (시간 개선은 거의 없음)
튜닝 후 실행 계획
EXPLAIN SELECT 사원.사원번호, 사원.이름, 사원.성, 부서관리자.부서번호
FROM 사원
JOIN 부서관리자
ON (사원.사원번호 = 부서관리자.사원번호);
- 부서관리자 테이블의 Using temporary가 삭제 되었음 -> 임시 테이블 생성 x
- 정렬과 중복 제거를 수행하지 않아도 되므로 DISTINCT를 삭제했기 때문에 임시 테이블도 생성 X
다수 쿼리를 UNION 연산자로만 합치는 나쁜 SQL 문
성이 Baba인 사원 데이터를 남/여로 나누어 UNION으로 조회하기
AS-IS
튜닝 전 SQL 문
SELECT 'M' AS 성별, 사원번호
FROM 사원
WHERE 성별 = 'M' AND 성 = 'Baba'
UNION
SELECT 'F', 사원번호
FROM 사원
WHERE 성별 = 'F' AND 성 = 'Baba';
- 0.044s 소요
튜닝 전 실행 계획
EXPLAIN
SELECT 'M' AS 성별, 사원번호
FROM 사원
WHERE 성별 = 'M' AND 성 = 'Baba'
UNION
SELECT 'F', 사원번호
FROM 사원
WHERE 성별 = 'F' AND 성 = 'Baba';
- id = 3에서 임시 테이블을 생성하여 id = 1, 2 각 결과의 UNION 연산 작업 수행
- 만약 메모리에 상주하기 어려울 만큼 id = 1, 2인 행의 결과량이 많다면, 메모리가 아닌 디스크에 임시 파일을 생성하여 UNION 작업
TO-BE
튜닝 수행
- SQL 문에서 WHERE 절의 경우, 성별과 성 컬럼이 동등(=) 조건으로 작성되어 있으므로, I_성별_성 index로 빠른 조회 가능
- UNION 연산으로 통합하는 과정에서 두 결과를 합친 후 중복 제거를 하지만, 사원번호 기본키가 출력되는 상황에서는 불필요함
튜닝 후 SQL 문
SELECT 'M' AS 성별, 사원번호
FROM 사원
WHERE 성별 = 'M' AND 성 = 'Baba'
UNION ALL
SELECT 'F', 사원번호
FROM 사원
WHERE 성별 = 'F' AND 성 = 'Baba';
- 0.044s -> 0.042s (시간 개선은 거의 없음)
튜닝 후 실행 계획
EXPLAIN
SELECT 'M' AS 성별, 사원번호
FROM 사원
WHERE 성별 = 'M' AND 성 = 'Baba'
UNION ALL
SELECT 'F', 사원번호
FROM 사원
WHERE 성별 = 'F' AND 성 = 'Baba';
- 정렬하여 중복을 제거하는 작업이 제외되면서 불필요한 리소스 낭비 방지
UNION ALL vs UNION
UNION ALL: 여러 개의 SELECT 문을 실행하는 결과를 단순히 합치는 것에 그침
UNION: 여러 개의 SELECT 문 실행 결과를 합친 뒤, 중복된 데이터를 제거하는 작업까지 포함
인덱스 고려 없이 열을 사용하는 나쁜 SQL 문
성(family name)과 성별 순서로 grouping 하기
AS-IS
튜닝 전 SQL 문
SELECT 성, 성별, COUNT(1) as 카운트
FROM 사원
GROUP BY 성, 성별;
- 0.281s 소요
튜닝 전 실행 계획
EXPLAIN
SELECT 성, 성별, COUNT(1) as 카운트
FROM 사원
GROUP BY 성, 성별;
- key = I_성별_성 -> I_성별_성 인덱스 사용
- 임시 테이블을 생성해서 성과 성별을 그루핑해 카운트 연산 수행
TO-BE
튜닝 수행
- 인덱스를 활용하는데도 메모리나 디스크에 임시테이블을 꼭 생성해야 하는지에 대한 고민이 필요
- 인덱스만으로 카운트 연산을 수행할 수 있다면, 임시테이블을 생성할 필요가 없음
- I_성별_성 인덱스는 성별 컬럼 기준으로 정렬 후, 성 컬럼으로 정렬되었다는 의미 -> 인덱스 순서 활용 가능
튜닝 후 SQL 문
SELECT 성, 성별, COUNT(1) as 카운트
FROM 사원
GROUP BY 성별, 성;
- 0.281s -> 0.07s 로 개선
튜닝 후 실행 계획
EXPLAIN
SELECT 성, 성별, COUNT(1) as 카운트
FROM 사원
GROUP BY 성별, 성;
- Extra 항목에 Using temporary가 사라진 걸 확인할 수 있음
엉뚱한 인덱스를 사용하는 나쁜 SQL 문
입사일자가 '1989'로 시작하면서 사원번호가 100,000을 초과하는 데이터 조회하기
AS-IS
튜닝 전 SQL 문
SELECT 사원번호
FROM 사원
WHERE 입사일자 LIKE '1989%' AND 사원번호 > 100000;
- 0.283s 소요
튜닝 전 실행 계획
EXPLAIN
SELECT 사원번호
FROM 사원
WHERE 입사일자 LIKE '1989%' AND 사원번호 > 100000;
- key = PRIMARY & type = range -> 사원 테이블의 기본 키로 범위 스캔 수행
- 스토리지 엔진으로부터 기본 키를 구성하는 사원번호를 조건으로 데이터를 가져온 뒤,
- MySQL 엔진에서 남은 필터 조건 (입사일자 LIKE '1989%')으로 추출하여 filtered 항목에 11.11%라는 예측값 출력
TO-BE
튜닝 수행
- 전체 데이터 개수 300,024개
- 입사일자가 1989년도인 데이터 개수 28,394개
- 사원번호가 100,000 보다 큰 데이터 개수 210,024개
- 사원번호가 100,000을 초과하는 데이터의 비율이 전체 데이터의 약 70%를 차지하므로, 기본 키(사원번호)로 접근하는 방법은 비효율적일 수 있음
- 입사일자가 1989년도인 사원 수는 전체 데이터 대비 약 10%를 차지하므로, 입사일자 열을 데이터 액세스 조건으로 활용 검토하기
EXPLAIN
SELECT 사원번호
FROM 사원 USE INDEX(I_입사일자)
WHERE 입사일자 LIKE '1989%' AND 사원번호 > 100000;
- I_입사일자 인덱스를 강제로 사용하도록 USE INDEX를 설정한 뒤 실행 계획 출력해보기
- Extra = Using index for skip scan -> 인덱스 루스 스캔 방식에 의해 인덱스를 스킵하는 오버헤드 발생 가능
- 입사일자 컬럼의 데이터 유형은 date
- LIKE 절보다 부등호(<, >, <=, >=) 절이 우선하여 인덱스를 사용하므로 데이터 접근 범위 줄이기
튜닝 후 SQL 문
SELECT 사원번호
FROM 사원
WHERE 입사일자 >= '1989-01-01' AND 입사일자 < '1990-01-01' AND 사원번호 > 100000;
- 0.283s -> 0.035s 로 개선
튜닝 후 실행 계획
EXPLAIN
SELECT 사원번호
FROM 사원
WHERE 입사일자 >= '1989-01-01' AND 입사일자 < '1990-01-01' AND 사원번호 > 100000;
- I_입사일자 인덱스를 활용하여 범위 스캔을 수행하고 테이블에 접근하지 않고 I_입사일자 인덱스만 사용하여 결과 출력
- Extra = Using index를 의미
전체 데이터 대비 조회 결과 데이터 비율이 높을 때, 인덱스를 활용하면 나쁜 SQL 문
B 출입문으로 출입한 이력이 있는 정보를 모두 조회하기
AS-IS
튜닝 전 SQL 문
SELECT *
FROM 사원출입기록
WHERE 출입문 = 'B';
- 6.777s 소요
- 총 300,000건
튜닝 전 실행 계획
EXPLAIN
SELECT *
FROM 사원출입기록
WHERE 출입문 = 'B';
- key = I_출입문 -> I_출입문 인덱스를 사용하여 데이터에 접근
- ref = const -> 출입문 = 'B'로 명확한 상수화 조건으로 데이터 접근 범위를 줄였으므로
TO-BE
튜닝 수행
SELECT 출입문, COUNT(1)
FROM 사원출입기록
GROUP BY 출입문;
- 총 66만 건의 데이터를 가진 사원출입기록 테이블에서 출입문 B는 전체 데이터의 50%인 30만 건을 차지함
- 전체 데이터의 50%에 달하는 데이터를 조회하기 위해서 인덱스를 활용하는 게 과연 효율적일지 고민할 필요가 있음
- -> 인덱스를 사용한다고 해서 항상 좋은건 아님
튜닝 후 SQL 문
SELECT *
FROM 사원출입기록 IGNORE INDEX(I_출입문)
WHERE 출입문 = 'B';
- IGNORE INDEX를 사용하여 MySQL의 옵티마이저가 I_출입문 인덱스를 무시하도록 설정
- 6.777s -> 6.596s
튜닝 후 실행 계획
EXPLAIN
SELECT *
FROM 사원출입기록 IGNORE INDEX(I_출입문)
WHERE 출입문 = 'B';
- type = ALL -> 테이블 풀 스캔 방식
- 랜덤 액세스가 발생하지 않고, 한 번에 다수의 페이지에 접근하는 테이블 풀 스캔 방식으로 수행됨
테이블 조인 설정 변경으로 쿼리 튜닝하기
작은 테이블이 먼저 조인에 참여하는 나쁜 SQL 문
부서의 시작일자가 '2002-03-01' 이후인 사원 데이터 조회하기
AS-IS
튜닝 전 SQL 문
SELECT 매핑.사원번호, 부서.부서번호
FROM 부서사원_매핑 매핑, 부서
WHERE 매핑.부서번호 = 부서.부서번호 AND 매핑.시작일자 >= '2002-03-01';
- 0.309s 소요
튜닝 전 실행 계획
EXPLAIN SELECT 매핑.사원번호, 부서.부서번호
FROM 부서사원_매핑 매핑, 부서
WHERE 매핑.부서번호 = 부서.부서번호 AND 매핑.시작일자 >= '2002-03-01';
- 드라이빙 테이블: 부서 / 드리븐 테이블: 부서사원_매핑
- 부서 테이블에 먼저 접근 후(ref = null) 'UI_부서명' 인덱스를 활용해 인덱스 풀 스캔 수행
- 상대적으로 큰 크기를 가진 부서사원_매핑 테이블은 'I_부서번호' 인덱스로 Index Scan 수행
- 인덱스 스캔을 하고, 랜덤 액세스로 테이블에 접근
- 드리븐 테이블에서 대량의 데이터에 대해 랜덤 액세스를 수행하면 비효율적
TO-BE
튜닝 수행
- 위와 같이 부서 테이블에는 9건의 데이터가 있고, 부서사원_매핑 테이블에는 약 33만 건의 데이터가 존재함
- 상대적으로 규모가 큰 부서사원_매핑 테이블의 '매핑.시작일자 >= '2002-03-01' 조건절을 먼저 적용할 수 있다면 조인할 때 비교 대상이 줄어들 것임
튜닝 후 SQL 문
SELECT STRAIGHT_JOIN 매핑.사원번호, 부서.부서번호
FROM 부서사원_매핑 매핑, 부서
WHERE 매핑.부서번호 = 부서.부서번호 AND 매핑.시작일자 >= '2002-03-01';
- STRAIGHT_JOIN을 사용하여 FROM 절에 작성된 테이블 순서대로 조인에 참여할 수 있도록 고정
- 즉, 부서사원_매핑 테이블에 먼저 접근 후 부서 테이블에 반복하여 접근
- 0.309s -> 0.116s 로 개선
튜닝 후 실행 계획
EXPLAIN SELECT STRAIGHT_JOIN 매핑.사원번호, 부서.부서번호
FROM 부서사원_매핑 매핑, 부서
WHERE 매핑.부서번호 = 부서.부서번호 AND 매핑.시작일자 >= '2002-03-01';
- 드라이빙 테이블: 부서사원_매핑 / 드라이븐 테이블: 부서
- 부서사원_매핑 테이블 -> 테이블 풀 스캔
- 부서 테이블 -> 기본 키로 반복 접근하여 1개의 데이터에만 접근
- type = ALL -> 랜덤 액세스 없이 테이블 풀 스캔 수행
메인 테이블에 계속 의존하는 나쁜 SQL 문
사원번호가 450,000 보다 크고, 그동안 받은 연봉 중 한번이라도 100,000 달러를 초과한 적이 있는 사원 정보 찾기
AS-IS
튜닝 전 SQL 문
SELECT 사원.사원번호, 사원.이름, 사원.성
FROM 사원
WHERE 사원번호 > 450000 AND (SELECT MAX(연봉)
FROM 급여
WHERE 사원번호 = 사원.사원번호) > 100000;
- 0.382s 소요
튜닝 전 실행 계획
EXPLAIN SELECT 사원.사원번호, 사원.이름, 사원.성
FROM 사원
WHERE 사원번호 > 450000 AND (SELECT MAX(연봉)
FROM 급여
WHERE 사원번호 = 사원.사원번호) > 100000;
- id = 1 (사원 테이블)
- key = PRIMARY & type = range -> 기본 키를 활용해서 범위 스캔 수행
- id = 2 (급여 테이블)
- select_type = DEPENDENT SUBQUERY -> 사원 테이블로부터 조건을 전달받아 수행해야 하는 의존성을 가진 서브쿼리
TO-BE
튜닝 수행
- 약 30만 건의 데이터를 가진 사원 테이블에서 사원 번호가 450,000를 초과하는 데이터는 약 15% 수준
- 보통 실행 계획의 select_type 항목에 DEPENDENT 키워드가 있으면, 외부 테이블에서 조건절을 받은 뒤 처리되어야 하므로 튜닝 대상으로 고려할 수 있음
- 조인으로 변경해볼 방법 생각해보기
튜닝 후 SQL 문
SELECT 사원.사원번호, 사원.이름, 사원.성
FROM 사원, 급여
WHERE 사원.사원번호 > 450000 AND 사원.사원번호 = 급여.사원번호
GROUP BY 사원.사원번호
HAVING MAX(급여.연봉) > 100000;
- 0.382s -> 0.198s 로 개선
튜닝 후 실행 계획
EXPLAIN SELECT 사원.사원번호, 사원.이름, 사원.성
FROM 사원, 급여
WHERE 사원.사원번호 > 450000 AND 사원.사원번호 = 급여.사원번호
GROUP BY 사원.사원번호
HAVING MAX(급여.연봉) > 100000;
- DEPENDENT SUBQUERY 방식은 제거되고, 단순 조인 방식으로 변경됨
- Nested Loop Join 형태 (중첩 루프 조인)
불필요한 조인을 수행하는 나쁜 SQL 문
A 출입문으로 출입한 사원이 총 몇 명인지 구하기
AS-IS
튜닝 전 SQL 문
SELECT COUNT(DISTINCT 사원.사원번호) as 데이터건수
FROM 사원,
(SELECT 사원번호
FROM 사원출입기록 기록
WHERE 출입문 = 'A') 기록
WHERE 사원.사원번호 = 기록.사원번호;
- FROM 절에서 사원 테이블과 사원출입기록 테이블로 작성한 인라인 뷰를 사원번호 열로 내부 조인
- 2.062s 소요
튜닝 전 실행 계획
EXPLAIN SELECT COUNT(DISTINCT 사원.사원번호) as 데이터건수
FROM 사원,
(SELECT 사원번호
FROM 사원출입기록 기록
WHERE 출입문 = 'A') 기록
WHERE 사원.사원번호 = 기록.사원번호;
- id가 모두 1 이므로 조인이 수행됨을 알 수 있음
- 드라이빙 테이블: 사원출입기록 / 드리븐 테이블: 사원
- ref = const -> WHERE 절에서 출입문 = 'A'인 상수와 비교하므로
- type = eq_ref -> 드리븐 테이블에서 기본 키를 사용하므로 표시되는 유형
TO-BE
튜닝 수행
- 앞서 작성한 SQL 문에서의 FROM 절의 인라인 뷰는
- 사실상 옵티마이저에 의해 아래의 SQL 문과 같이 조인 방식이 뷰 병합으로 최적화 됨
SELECT COUNT(DISTINCT 기록.사원번호) as 데이터건수
FROM 사원, 사원출입기록 기록
WHERE 사원.사원번호 = 기록.사원번호 AND 출입문 = 'A';
- 튜닝 전 실행 계획을 보면, 드라이빙 테이블인 사원출입기록에 접근할 때 I_출입문 인덱스를 활용하여 데이터에 접근하고 있음
- 66만여 건에 달하는 사원출입기록 테이블의 데이터 결과가 최종 결과에 어떻게 활용되는지 확인할 필요가 있다
- -> 사원출입기록 테이블의 데이터는 최종 결과에 사용하지 않고, 존재 여부만 파악해도되므로 EXISTS 구문으로 수정
튜닝 후 SQL 문
SELECT COUNT(1) as 데이터건수
FROM 사원
WHERE EXISTS (SELECT 1
FROM 사원출입기록 기록
WHERE 출입문 = 'A'
AND 기록.사원번호 = 사원.사원번호);
- 2.062s -> 0.251s 로 개선
튜닝 후 실행 계획
EXPLAIN
SELECT COUNT(1) as 데이터건수
FROM 사원
WHERE EXISTS (SELECT 1
FROM 사원출입기록 기록
WHERE 출입문 = 'A'
AND 기록.사원번호 = 사원.사원번호);
- 드라이빙 테이블: 사원 / 드리븐 테이블: <subquery2>
- 사원출력기록 테이블은 EXISTS 연산자로 데이터 존재 여부를 파악하기 위해 임시 테이블을 생성하는 MATERIALIZED 로 표기
정리
SQL문 단순 수정으로 쿼리 튜닝하기
- 기본키를 변형하지 말기
- 쿼리 작성 시, 불필요한 함수를 호출하지 않도록 한다.
- 결합하고자하는 열이 인덱스가 존재하는 경우, 열을 결합하여 사용하지 않는 것이 좋다.
- DISTINCT를 남용하지 않는다.
- UNION(중복 제거)을 남용하지 않는다.
- 인덱스 열을 고려하여 정렬 작업을 수행해야 한다.
- 데이터 액세스를 위한 더 효율적인 인덱스를 검토해본다.
- 인덱스 스캔으로 잦은 랜덤 액세스가 발생한다면, 인덱스를 활용하지 않는 방향을 검토해본다.
- 전체 데이터 대비 조회 결과 데이터 비율이 높다면, 인덱스를 활용하지 않는 방향이 좋을 수 있다.
테이블 조인 설정 변경으로 쿼리 튜닝하기
- 데이터가 더 적은 테이블이 먼저 조인에 참여하지 않도록 한다.
- 메인 테이블에 의존하는 DEPENDENT 쿼리 방식은 사용하지 않도록 한다.
- 불필요한 조인을 수행하지 않는다.