在我的机器上,以下查询执行聚簇索引扫描并占用大约6.8秒的cpu时间:
SELECT ID1,ID2 FROM two_col_key_test WITH (FORCESCAN) WHERE ID1 NOT IN ( N'1',N'2',N'3',N'4',N'5',N'6',N'7',N'8',N'9',N'10',N'11',N'12',N'13',N'14',N'15',N'16',N'17',N'18',N'19',N'20' ) AND (ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT')) ORDER BY ID1,ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY OPTION (MAXDOP 1);
以下查询执行聚簇索引查找(仅差异是删除FORCESCAN提示),但占用大约18.2秒的cpu时间:
SELECT ID1,ID2 FROM two_col_key_test WHERE ID1 NOT IN ( N'1',ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY OPTION (MAXDOP 1);
查询计划非常相似.对于这两个查询,从聚簇索引中读取120000001行:
我在sql Server 2017 CU 10上.以下是创建和填充two_col_key_test表的代码:
drop table if exists dbo.two_col_key_test; CREATE TABLE dbo.two_col_key_test ( ID1 NVARCHAR(50) NOT NULL,ID2 NVARCHAR(50) NOT NULL,FILLER NVARCHAR(50),PRIMARY KEY (ID1,ID2) ); DROP TABLE IF EXISTS #t; SELECT TOP (4000) 0 ID INTO #t FROM master..spt_values t1 CROSS JOIN master..spt_values t2 OPTION (MAXDOP 1); INSERT INTO dbo.two_col_key_test WITH (TABLOCK) SELECT N'FILLER TEXT' + CASE WHEN ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) > 8000000 THEN N' 2' ELSE N'' END,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),NULL FROM #t t1 CROSS JOIN #t t2;
我希望得到的答案不仅仅是调用堆栈报告.例如,我可以看到sqlmin!TCValSSInRowExprFilter< 231,0> :: Getdatax在慢速查询中占用了比快速查询更多的cpu周期:
我不想停在那里,而是想了解那是什么以及为什么两个查询之间存在如此大的差异.
解决方法
Why is there a large difference in cpu time for these two queries?
扫描计划为每行评估以下推送的非sargable(残差)谓词:
[two_col_key_test].[ID1]<>N'1' AND [two_col_key_test].[ID1]<>N'10' AND [two_col_key_test].[ID1]<>N'11' AND [two_col_key_test].[ID1]<>N'12' AND [two_col_key_test].[ID1]<>N'13' AND [two_col_key_test].[ID1]<>N'14' AND [two_col_key_test].[ID1]<>N'15' AND [two_col_key_test].[ID1]<>N'16' AND [two_col_key_test].[ID1]<>N'17' AND [two_col_key_test].[ID1]<>N'18' AND [two_col_key_test].[ID1]<>N'19' AND [two_col_key_test].[ID1]<>N'2' AND [two_col_key_test].[ID1]<>N'20' AND [two_col_key_test].[ID1]<>N'3' AND [two_col_key_test].[ID1]<>N'4' AND [two_col_key_test].[ID1]<>N'5' AND [two_col_key_test].[ID1]<>N'6' AND [two_col_key_test].[ID1]<>N'7' AND [two_col_key_test].[ID1]<>N'8' AND [two_col_key_test].[ID1]<>N'9' AND ( [two_col_key_test].[ID1]=N'FILLER TEXT' AND [two_col_key_test].[ID2]>=N'' OR [two_col_key_test].[ID1]>N'FILLER TEXT' )
寻求计划进行两项寻求操作:
Seek Keys[1]: Prefix: [two_col_key_test].ID1 = Scalar Operator(N'FILLER TEXT'),Start: [two_col_key_test].ID2 >= Scalar Operator(N'') Seek Keys[1]: Start: [two_col_key_test].ID1 > Scalar Operator(N'FILLER TEXT')
…匹配谓词的这一部分:
(ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))
残差谓词应用于通过上述搜索条件的行(示例中的所有行).
但是,每个不等式被两个单独的测试所取代,小于OR大于:
([two_col_key_test].[ID1]<N'1' OR [two_col_key_test].[ID1]>N'1') AND ([two_col_key_test].[ID1]<N'10' OR [two_col_key_test].[ID1]>N'10') AND ([two_col_key_test].[ID1]<N'11' OR [two_col_key_test].[ID1]>N'11') AND ([two_col_key_test].[ID1]<N'12' OR [two_col_key_test].[ID1]>N'12') AND ([two_col_key_test].[ID1]<N'13' OR [two_col_key_test].[ID1]>N'13') AND ([two_col_key_test].[ID1]<N'14' OR [two_col_key_test].[ID1]>N'14') AND ([two_col_key_test].[ID1]<N'15' OR [two_col_key_test].[ID1]>N'15') AND ([two_col_key_test].[ID1]<N'16' OR [two_col_key_test].[ID1]>N'16') AND ([two_col_key_test].[ID1]<N'17' OR [two_col_key_test].[ID1]>N'17') AND ([two_col_key_test].[ID1]<N'18' OR [two_col_key_test].[ID1]>N'18') AND ([two_col_key_test].[ID1]<N'19' OR [two_col_key_test].[ID1]>N'19') AND ([two_col_key_test].[ID1]<N'2' OR [two_col_key_test].[ID1]>N'2') AND ([two_col_key_test].[ID1]<N'20' OR [two_col_key_test].[ID1]>N'20') AND ([two_col_key_test].[ID1]<N'3' OR [two_col_key_test].[ID1]>N'3') AND ([two_col_key_test].[ID1]<N'4' OR [two_col_key_test].[ID1]>N'4') AND ([two_col_key_test].[ID1]<N'5' OR [two_col_key_test].[ID1]>N'5') AND ([two_col_key_test].[ID1]<N'6' OR [two_col_key_test].[ID1]>N'6') AND ([two_col_key_test].[ID1]<N'7' OR [two_col_key_test].[ID1]>N'7') AND ([two_col_key_test].[ID1]<N'8' OR [two_col_key_test].[ID1]>N'8') AND ([two_col_key_test].[ID1]<N'9' OR [two_col_key_test].[ID1]>N'9')
重写每个不等式,例如:
[ID1] <> N'1' -> [ID1]<N'1' OR [ID1]>N'1'
……在这里适得其反.整理感知字符串比较是昂贵的.将比较次数加倍可以解释您看到的cpu时间的大部分差异.
您可以通过禁用使用未记录的跟踪标志9130来推送不可搜索的谓词来更清楚地看到这一点.这将显示残差作为单独的过滤器,您可以单独检查性能信息:
这也将突出显示搜索上的轻微基数误差,这解释了为什么优化器首先选择搜索而不是扫描(它期望搜索部分消除一些行).
虽然不等式重写可能使(可能已过滤)索引匹配成为可能(以充分利用b树索引的搜索能力),但如果两个半部分都以残差结束,则随后恢复此扩展会更好.您可以将此建议作为SQL Server feedback site的改进.
另请注意,原始(“遗留”)基数估计模型恰好为此查询选择默认扫描.