SQL Server 如何确定缺失索引请求中的键列顺序?

Bry*_*bok 33 performance sql-server index-tuning query-performance

SQL Server 如何确定查询计划的缺失索引建议中键列的顺序?

Bry*_*bok 46

当 SQL Server 为特定查询计划创建缺失索引建议时,它会将可能的键列分成 2 组。第一组包含作为 EQUALITY 谓词一部分的所有推荐列。第二组包含作为 INEQUALITY 谓词一部分的所有推荐列。

在每个集合中,列根据表定义按列的顺序位置排序。

(非常感谢 Brent Ozar 构建了一个针对 Stack Overflow 数据库的复制脚本来证明这一点!)

1. 创建 3 个相同的表,但将它们的列按不同的顺序排列。(这里的原因是使用各种列名和数据类型来表明这不会影响缺失索引建议中的列顺序。)

CREATE TABLE dbo.NumberLetterDate (ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, 
fINT INT, fNVARCHAR NVARCHAR(40), fDATE DATETIME, AboutMe NVARCHAR(MAX));
GO
CREATE TABLE dbo.LetterDateNumber (ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, 
fNVARCHAR NVARCHAR(40), fDATE DATETIME, fINT INT, AboutMe NVARCHAR(MAX));
GO
CREATE TABLE dbo.DateNumberLetter (ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
fDATE DATETIME, fINT INT, fNVARCHAR NVARCHAR(40), AboutMe NVARCHAR(MAX));
GO
Run Code Online (Sandbox Code Playgroud)

2. 用相同的数据填充表。从具有真实数据分布的 Users 表中获取 100,000 行。

INSERT INTO dbo.NumberLetterDate(fINT, fNVARCHAR, fDATE, AboutMe)
SELECT TOP 100000 Age, DisplayName, LastAccessDate, AboutMe
  FROM dbo.Users WITH (NOLOCK)
  ORDER BY Id;
GO
INSERT INTO dbo.LetterDateNumber(fINT, fNVARCHAR, fDATE, AboutMe)
SELECT TOP 100000 Age, DisplayName, LastAccessDate, AboutMe
  FROM dbo.Users WITH (NOLOCK)
  ORDER BY Id;
GO
INSERT INTO dbo.DateNumberLetter(fINT, fNVARCHAR, fDATE, AboutMe)
SELECT TOP 100000 Age, DisplayName, LastAccessDate, AboutMe
  FROM dbo.Users WITH (NOLOCK)
  ORDER BY Id;
GO
Run Code Online (Sandbox Code Playgroud)

3. 编写一个需要索引的查询。从 3 个相等过滤器开始,过滤所有 3 个字段中的确切值。请注意,所有 3 个查询都具有相同顺序的相同字段:

SELECT ID
  FROM dbo.NumberLetterDate
  WHERE fINT = 100
  AND fNVARCHAR = 'Brent Ozar'
  AND fDATE = '2018/01/01'
  AND 1 = (SELECT 1);

SELECT ID
  FROM dbo.LetterDateNumber
  WHERE fINT = 100
  AND fNVARCHAR = 'Brent Ozar'
  AND fDATE = '2018/01/01'
  AND 1 = (SELECT 1);

SELECT ID
  FROM dbo.DateNumberLetter
  WHERE fINT = 100
  AND fNVARCHAR = 'Brent Ozar'
  AND fDATE = '2018/01/01'
  AND 1 = (SELECT 1);
GO
Run Code Online (Sandbox Code Playgroud)

所有三个表都具有完全相同的数据,并且查询完全相同。唯一的区别是字段顺序——这也是我们缺少索引请求的区别:

具有 3 个相等字段的执行计划

在执行计划中,缺失索引请求中的列顺序与表中的列顺序完全匹配。例如,在 dbo.NumberLetterDate 中,数字列是第一个,因此它也是缺失索引请求中的第一个:

  • 在 dbo.NumberLetterDate 上,缺失的索引在 fINT(数字)、fLetter(nvarchar)、fDate 上,表中字段的顺序相同
  • 在 dbo.LetterDateNumber 上,索引顺序切换为 fNVARCHAR、fDATE、fINT
  • 在 dbo.DateNumberLetter 上,索引顺序切换为 fDATE、fINT、fNVARCHAR

对于像这样的单表操作,索引字段顺序似乎并不取决于查询中的选择性、数据类型或位置。(我把它留给其他人用更复杂的查询和连接来证明这一点。)

4. 混入不等式过滤器。例如,在 INT 字段中,输入 <> 100 作为过滤器:

SELECT ID
  FROM dbo.NumberLetterDate
  WHERE fINT <> 100
  AND fNVARCHAR = 'Brent Ozar'
  AND fDATE = '2018/01/01'
  AND 1 = (SELECT 1);

SELECT ID
  FROM dbo.LetterDateNumber
  WHERE fINT <> 100
  AND fNVARCHAR = 'Brent Ozar'
  AND fDATE = '2018/01/01'
  AND 1 = (SELECT 1);

SELECT ID
  FROM dbo.DateNumberLetter
  WHERE fINT <> 100
  AND fNVARCHAR = 'Brent Ozar'
  AND fDATE = '2018/01/01'
  AND 1 = (SELECT 1);
GO
Run Code Online (Sandbox Code Playgroud)

在执行计划中,等式字段在前,然后是不等式字段——所以在这里,fINT 在所有 3 个缺失的索引请求中显示在最后,因为它是一个不等式搜索:

具有 2 个等式和 1 个不等式搜索的执行计划

5. 使用 3 个不等式过滤器。对所有字段 (<>) 使用相同的搜索:

SELECT ID
  FROM dbo.NumberLetterDate
  WHERE fINT <> 100
  AND fNVARCHAR <> 'Brent Ozar'
  AND fDATE <> '2018/01/01'
  AND 1 = (SELECT 1);

SELECT ID
  FROM dbo.LetterDateNumber
  WHERE fINT <> 100
  AND fNVARCHAR <> 'Brent Ozar'
  AND fDATE <> '2018/01/01'
  AND 1 = (SELECT 1);

SELECT ID
  FROM dbo.DateNumberLetter
  WHERE fINT <> 100
  AND fNVARCHAR <> 'Brent Ozar'
  AND fDATE <> '2018/01/01'
  AND 1 = (SELECT 1);
GO
Run Code Online (Sandbox Code Playgroud)

由于没有相等搜索,所有 3 个字段在缺失索引推荐中都具有相同的优先级顺序,现在我们回到纯粹按字段顺序排序:

具有 3 次不等式搜索的执行计划