我正在对大约 5 亿行的单个表上的列存储索引进行一些测试。聚合查询的性能提升非常棒(以前运行大约需要 2 分钟的查询现在运行在 0 秒内即可聚合整个表)。
但我也注意到另一个利用在同一张表上的现有行存储索引上查找的测试查询现在的运行速度是之前创建列存储索引之前的 4 倍。我可以反复演示删除列存储索引时,行存储查询在 5 秒内运行,并且通过在列存储索引中添加回行存储查询在 20 秒内运行。
我一直关注行存储索引查询的实际执行计划,无论列存储索引是否存在,这两种情况几乎完全相同。(它在这两种情况下都使用行存储索引。)
行存储测试查询是:
SELECT *
INTO #TEMP
FROM Table1 WITH (FORCESEEK)
WHERE IntField1 = 571
AND DateField1 >= '6/01/2020'
此查询中使用的行存储索引是:CREATE NONCLUSTERED INDEX IX_Table1_1 ON Table1 (IntField1, DateField1) INCLUDE (IntField2)
列存储测试查询是:
SELECT COUNT(DISTINCT IntField2) AS IntField2_UniqueCount, COUNT(1) AS RowCount
FROM Table1
WHERE IntField1 = 571 -- Some other test columnstore queries also don't use any WHERE predicates on this table
AND DateField1 >= '1/1/2019'
列存储索引为:CREATE NONCLUSTERED COLUMNSTORE INDEX IX_Table1_2 ON Table1 (IntField2, IntField1, DateField1)
我注意到这两个计划之间的唯一区别是,在创建列存储索引后,排序操作的警告消失了,而键查找和表插入 (#TEMP) 运算符花费的时间要长得多。
我会认为在这两种情况下专门利用相同行存储索引和执行计划的读取查询在每次运行时应该具有大致相同的性能,而不管该表上存在哪些其他索引。这里给出了什么?
添加非聚集列存储索引允许在第二个执行计划中进行批处理模式排序。这会导致所有处理都在一个线程上完成 - 因此即使查询具有并行计划,它本质上也是串行运行的。您可以通过查看不同运营商的详细信息来了解这一点。
我在本地重现了您的问题,这是每个线程计数的排序运算符 - 正如您所看到的,一切都在线程 1 上:
注意“实际执行模式”是“批处理”。
排序之后的所有内容(嵌套循环连接、键查找等)本质上都是串行的,这会减慢查询速度。
有关详细信息和可能的解决方案,请参阅此知识库文章:
添加跟踪标志 9358 以禁用 SQL Server 2016 中复杂并行查询中的批处理模式排序操作
为完整起见,此处列出的选项包括:
QUERY_OPTIMIZER_HOTFIXES
数据库选项或ENABLE_QUERY_OPTIMIZER_HOTFIXES
查询提示)摆脱排序是这个问题的另一个解决方案。排序仅用于尝试防止来自嵌套循环连接的过多随机 I/O,它使用无序预取,如 Craig Freedman 的这篇文章中所述:
通过排序优化 I/O 性能——第 1 部分
您可以通过以下方式摆脱排序:
OPTION (QUERYTRACEON 9115)
通过向查询添加(未记录,不支持的跟踪标志)来禁用嵌套循环预取