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를 개선할 수 있다.
댓글