데이터가 기울어졌고 쿼리 힌트를 사용하여 옵티마이 저가 수행 할 작업을 강요하고 싶지 않으며 가능한 모든 입력 값에 대해 좋은 성능을 가져야한다고 가정합니다 @Id
. 다음 색인 쌍 (또는 동등한 색인)을 기꺼이 작성하려는 경우 가능한 입력 값에 대해 몇 가지 논리적 읽기를 요구하는 쿼리 계획을 얻을 수 있습니다.
CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;
아래는 내 테스트 데이터입니다. 13 M 행을 테이블에 넣고 그중 절반 '3A35EA17-CE7E-4637-8319-4C517B6E48CA'
이 Id
열 값을 갖도록했습니다 .
DROP TABLE IF EXISTS dbo.MyTable;
CREATE TABLE dbo.MyTable (
Id uniqueidentifier,
SomeTimestamp DATETIME2,
SomeInt INT,
SomeBit BIT,
FILLER VARCHAR(100)
);
INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT NEWID(), CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT '3A35EA17-CE7E-4637-8319-4C517B6E48CA', CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
이 쿼리는 처음에는 조금 이상하게 보일 수 있습니다.
DECLARE @Id UNIQUEIDENTIFIER = '3A35EA17-CE7E-4637-8319-4C517B6E48CA'
SELECT
@Id,
st.SomeTimestamp,
si.SomeInt
FROM (
SELECT TOP (1) SomeInt, Id
FROM dbo.MyTable
WHERE Id = @Id
AND SomeBit = 1
ORDER BY SomeInt DESC
) si
CROSS JOIN (
SELECT TOP (1) SomeTimestamp, Id
FROM dbo.MyTable
WHERE Id = @Id
AND SomeBit = 1
ORDER BY SomeTimestamp ASC
) st;
몇 가지 논리적 읽기로 최소 또는 최대 값을 찾기 위해 인덱스 순서를 활용하도록 설계되었습니다. 는 CROSS JOIN
대한 매칭되는 열이 없을 때 정확한 결과를 얻을 수있다 @Id
값. 테이블에서 가장 인기있는 값 (6.5 백만 행 일치)을 필터링하더라도 8 개의 논리적 읽기 만 얻습니다.
테이블 'MyTable'. 스캔 횟수 2, 논리적 읽기 8
쿼리 계획은 다음과 같습니다.
두 인덱스 모두 0 또는 1 개의 행을 찾습니다. 매우 효율적이지만 두 개의 인덱스를 만드는 것은 시나리오에 너무 과도 할 수 있습니다. 대신 다음 색인을 고려할 수 있습니다.
CREATE INDEX CoveringIndex ON dbo.MyTable (Id) INCLUDE (SomeTimestamp, SomeInt) WHERE SomeBit = 1;
이제 원래 쿼리에 대한 쿼리 계획 (선택적 MAXDOP 1
힌트 포함)이 약간 다르게 보입니다.
키 조회는 더 이상 필요하지 않습니다. 모든 입력에 대해 잘 작동하는 더 나은 액세스 경로를 통해 밀도 벡터로 인해 옵티마이 저가 잘못된 쿼리 계획을 선택하는 것에 대해 걱정할 필요가 없습니다. 그러나 인기있는 @Id
값 을 찾는 경우이 쿼리 및 인덱스는 다른 쿼리와 인덱스보다 효율적이지 않습니다 .
테이블 'MyTable'. 스캔 카운트 1, 논리적 읽기 33757