我将假设您的数据有偏差,您不想使用查询提示来强制优化器执行该操作,并且您需要为的所有可能输入值获得良好的性能@Id
。如果您愿意创建以下一对索引(或它们的等效索引),则可以确保查询计划对任何可能的输入值仅需要少量的逻辑读取:
CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;
以下是我的测试数据。我在表中放入了1300万行,并使其中一半的值'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
价值。即使我过滤表中最流行的值(匹配650万行),我也只会得到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