寻求谓词不使用所有可用列


8

我有一个奇怪的查询编译问题,很难重现。它仅在高负载下发生,不能轻易重复。

  • 有一个带有列A,B,C,D的表T。
  • T(A,B,C,D)上存在一个非唯一的聚集索引。
  • 有一个查询SELECT * FROM T WHERE A = @ P1 AND B = @ P2 AND(C = @ P3或C = @ P4)AND D = @ P5。查找条件位于聚集索引的所有列上,第3列具有OR。

问题在于此查询的查询计划仅在A和B上具有Seek谓词!C和D上的谓词是普通谓词,因此这意味着不使用C和D列上的搜索树。

所有参数的数据类型都与列数据类型匹配。

谁能提供暗示为什么会发生这种情况?SQL版本是2008 R2(SP1)-10.50.2789.0(X64)


您是否真的有一个针对参数化查询的计划,该计划对所有4列执行均等搜索?如果是这样,您在使用OPTION (RECOMPILE)吗?
马丁·史密斯

Answers:


8

对于参数化查询,不能只对它执行两次搜索

WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5 

WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5 

因为如果@P3 = @P4那样会错误地带回重复的行。因此,将需要一个操作员首先从这些操作中删除重复项。

从快速测试的角度来看,这是否取决于表的大小。在下面的测试中,245/ 246行是计划之间的截止点(这也是索引适合一页上的所有索引,然后变为2个叶页和一个根页之间的截止点)。

CREATE TABLE T(A INT,B INT,C INT,D INT)

INSERT INTO T
SELECT TOP (245) 1,2,3,5
FROM master..spt_values v1

CREATE CLUSTERED INDEX IX ON T(A, B, C, D)

SELECT index_level,page_count, record_count
FROM sys.dm_db_index_physical_stats(db_id(),object_id('T'),1,NULL, 'DETAILED')

DECLARE @C1 INT = 3,
        @C2 INT = 4

 SELECT * FROM T WHERE A=1 AND B=2 AND (C=@C1 OR C=@C2) AND D=5

 DROP TABLE T

1页/ 245行

该计划寻求A=1 AND B=2一个带有剩余谓词的(C=@C1 OR C=@C2) AND D=5

计划1

2页/ 246行

方案2

在第二个计划中,额外的运算符负责@C1,@C2在执行查找之前从第一个中删除任何重复项。

第二个计划中的搜索实际上是介于A=1 AND B=2 AND C > Expr1010A=1 AND B=2 AND C < Expr1011带有剩余谓词的范围搜索D=5。这仍然不是所有4列上的均等搜索。有关其他计划运营商的更多信息,请参见此处

添加OPTION (RECOMPILE)确实允许它在编译时检查参数值是否重复,并生成具有两个相等查找的计划。

您也可以使用

;WITH CTE
     AS (SELECT DISTINCT ( C )
         FROM   (VALUES (@C1),
                        (@C2)) V(C))
SELECT CA.*
FROM   CTE
       CROSS APPLY (SELECT *
                    FROM   T
                    WHERE A=1 AND B=2 AND D=5  AND C = CTE.C) CA

方案3

但是实际上,在此测试用例中,它可能会适得其反,因为在单页索引中有两次查找,而不是一次增加逻辑IO。


1
昨晚在添加第一个评论之前对此问题做了一些测试。我走到尽头看到的行为,但没有得到引起它的原因(@ P3 = @ P4),所以为此+1(已经做到了)。我认为a select .. union select ...还会给您两个搜索以及从结果中删除重复项的附加步骤。
Mikael Eriksson

1
@MikaelEriksson-但SELECT * FROM T WHERE A=1 AND B=2 AND C=@C1 AND D=5 UNION SELECT * FROM T WHERE A=1 AND B=2 AND C=@C2 AND D=5可能会错误地删除应返回的重复项。在我懒洋洋地填充都具有相同的价值我的示例数据,它将返回1行不256
马丁·史密斯

2
是的。OP甚至指定密钥是非唯一的。幸运的是,我没有尝试回答这一问题:)。
Mikael Eriksson

4

完全同意马丁的分析。由于使用OR谓词,该查询无法在所有4列上产生搜索,除非(可能)使用OPTION(RECOMPILE)。我猜想这是一个大海捞针的查询,您可能不想要额外的开销。

这个怎么样:

IF @P3=@P4
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
ELSE
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
    UNION ALL
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5.

我没有对此进行测试,但是else部分应该对所有4个值给出2个寻道,并通过串联实现廉价的并集。如果两个查找都在同一页面中结束,我认为读取额外的逻辑页面不会增加很多时间。但是,根据您的数据,这两个搜索很可能位于不同的页面上。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.