1. 사전구성
SELECT number custid
, NEWID() code
, CASE WHEN number <= 10 THEN 5
WHEN number <= 110 THEN 4
WHEN number <= 410 THEN 3
WHEN number <= 710 THEN 2
ELSE 1 END level
INTO TB_Cust01
FROM master..spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 1000;
SELECT a.custid, DATEADD(dd, b.number, '2000-01-01') date
INTO TB_Pay01
FROM TB_Cust01 a, master..spt_values b
WHERE b.type = 'P'
AND b.number BETWEEN 1 AND 1000
AND (
(level IN (4, 5) AND custid%2=0)
OR (level IN (1,2,3) AND custid%100<>0)
);
CREATE INDEX NIDX01_Cust01 ON TB_Cust01 (level) INCLUDE (custid);
CREATE INDEX NIDX01_Pay01 ON TB_Pay01 (date) INCLUDE (custid);
table_name | index_name | index_description | Is_Unique | indexed_columns | included_columns |
TB_Cust01 | NIDX01_Cust01 | nonclustered | 0 | level | custid |
TB_Pay01 | NIDX01_Pay01 | nonclustered | 0 | date | custid |
2. AS-IS
SELECT custid
FROM TB_Cust01
WHERE level = 2
EXCEPT
SELECT custid
FROM TB_Pay01
WHERE date >= '2000-01-01';
- [TB_Cust01] 테이블에서 [TB_Pay01] 테이블의 데이터를 제외하기 위해
조건절을 필터하고 정렬을 수행한 뒤 Anti Semi Join으로 조인에 성공하지 못하는 데이터만을 최종 출력한다. - [TB_Pay01] 테이블에서 사용된 인덱스에는 조인 조건인 [custid] 컬럼이 키로 구성되어 있지 않기 때문에
[date] >= '2000-01-01' 조건만 인덱스로 탐색되며 선행 테이블의 결과 집합 각 행마다 조인에 성공하는 데이터를 찾을 때까지 읽게 되면서 I/O가 높게 사용된다.
3. TO_BE
CREATE INDEX NIDX02_Pay01 ON TB_Pay01 (custid, date)
;
SELECT custid
FROM TB_Cust01 a
WHERE level = 2
AND NOT EXISTS (
SELECT 'x'
FROM TB_Pay01 b
WHERE a.custid = b.custid
AND date >= '2000-01-01');
- NOT EXISTS 구문으로 변경하면 동일한 결과를 도출하면서 불필요한 정렬 과정을 제거할 수 있다.
- 결국 인덱스 쓰라는 말... NOT EXISTS도 Anti Semi Join으로 수행되며 조인에 성공하는 데이터를 찾으면 해당 지점에서 중단하지만
조인에 성공하는 데이터를 찾지 못한다면 모든 데이터를 읽어내야 하기 때문에 많은 I/O가 사용될 수 있다. - [TB_Pay01] 테이블의 조인 조건인 [custid] 컬럼이 인덱스에 선행 키로 포함되어 있다면
데이터가 정렬된 상태로 유지되기 때문에 첫 행만 읽어내도 조인되는 데이터가 있는지에 대한 판별이 가능하여 I/O를 개선할 수 있다.
'Develop > MSSQL' 카테고리의 다른 글
변수 사용에 따른 카디널리티 추정 이슈 (0) | 2022.12.12 |
---|---|
변수 테이블의 통계 개선 - 1 (0) | 2022.12.08 |
스칼라 서브 쿼리 성능 이슈 (0) | 2022.12.01 |
SPOOL 연산자 성능 개선 - 3 (0) | 2022.11.30 |
SPOOL 연산자 성능 개선 - 2 (0) | 2022.11.24 |
댓글