为何SQL Server建议将谓词列CompanyId放入索引INCLUDE而非键列?
我正在优化一个大型查询并创建索引,原以为自己的索引设计正确,但SQL Server给出了不同建议。相关查询片段如下:
SELECT CompanyTransaction.Amount -- columns from other tables.. FROM Company -- JOIN other tables LEFT JOIN CompanyTransaction ON CompanyTransaction.CompanyId = Company.Id AND CompanyTransaction.Type = 'SR' AND CompanyTransaction.Code = 1000
我根据过滤条件,将CompanyId、Type、Code设为键列,同时把查询需要的Amount放入INCLUDE,创建的索引如下:
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>] ON [dbo].[CompanyTransaction] ([CompanyId],[Type],[Code]) INCLUDE ([Amount])
但SQL Server建议的索引为:
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>] ON [dbo].[CompanyTransaction] ([Type],[Code]) INCLUDE ([CompanyId], [Amount])
这种情况之前也出现过,想了解为何SQL Server认为作为搜索条件的CompanyId更适合放在INCLUDE中?
核心原因在于查询的执行逻辑和数据分布特征,具体可以从以下几点理解:
LEFT JOIN的执行顺序影响
这个查询是从Company主表出发做LEFT JOIN,优化器大概率会先扫描Company表,再用每条数据的Company.Id去匹配CompanyTransaction表的记录。这种场景下,Type='SR'和Code=1000是强过滤条件——如果这两个值的组合在CompanyTransaction表中占比极低,优化器会优先用这两个键列快速定位到极小范围的数据集,再在这个小范围内匹配CompanyId,整体IO成本更低。索引有序性的利用效率
- 若把
CompanyId放在键列最前面,索引的有序性是按CompanyId→Type→Code排列的。但主表Company的Id可能分布分散,优化器需要遍历大量不同CompanyId对应的索引分支,再过滤Type和Code,遍历成本很高。 - 而以
Type→Code作为键列的索引,叶子节点是按这两个值有序排列的,SQL Server可以快速定位到所有符合Type='SR'且Code=1000的行(因为键列有序,能直接跳转到目标区间),再通过INCLUDE里的CompanyId和主表做匹配,不需要回表,效率更高。
- 若把
缺失索引建议的评估逻辑
SQL Server的缺失索引建议是基于查询的预估执行计划生成的,它会计算不同索引的IO、CPU成本。如果Type+Code的过滤性足够强,对应的索引叶子节点数据量极小,即使要在其中匹配CompanyId,整体成本也远低于以CompanyId开头的索引。
最后可以通过查看实际执行计划,对比两个索引的逻辑读取数、执行时间,来验证哪个更适配你的真实数据分布。
内容的提问来源于stack exchange,提问作者Murphybro2

