2014-05-19 77 views
8

我在SQL(2008)中有一个查询,我不明白为什么它需要更长的时间才能评估是否在不应影响结果的WHERE语句中包含子句。下面是该查询的示例:为什么SQL计算一个WHERE子句是False?

declare @includeAll bit = 0; 

    SELECT 
     Id 
     ,Name 
     ,Total 
    FROM 
     MyTable 
    WHERE 
     @includeAll = 1 OR Id = 3926 

显然,在这种情况下,@includeAll = 1将评估假;然而,包括这种增加查询的时间,就好像它总是真的一样。我得到的结果是正确的,有或没有该条款:我只得到ID = 3926的1条目,但是(在我的真实世界查询中)包括该行将查询时间从< 0秒增加到约7分钟...所以它似乎正在运行查询,就好像该语句是真实的,即使它不是,但仍然返回正确的结果。

任何可以摆脱的光线为什么会有帮助。此外,如果您有解决此问题的建议,我会接受。我想有一个像这样的子句,这样我就可以在存储过程中包含一个参数,它将忽略它所拥有的Id并返回所有结果(如果设置为true),但是我不能允许它影响只需要获取一条记录就可以获得性能。

+0

什么是每一个执行计划? –

+0

WHERE @includeAll = 1 OR(@includeAll = 0 AND Id = 3926)是否有所作为? – UnhandledExcepSean

+0

尝试切换它们的顺序,并添加括号,两者都没有改变。 – waltsj19

回答

6

您需要查看查询计划以确保它正确,但是使用OR会在某些DBMS中像这样进行扫描。

此外,请阅读@Bogdan Sahlean对一些重要细节的回应,以了解为什么会发生这种情况。

这可能不行,但你可以尝试的东西一样,如果你需要坚持使用直SQL:

SELECT 
    Id 
    ,Name 
    ,Total 
FROM 
    MyTable 
WHERE Id = 3926 
UNION ALL 
SELECT 
    Id 
    ,Name 
    ,Total 
FROM 
    MyTable 
WHERE Id <> 3926 
AND @includeAll = 1 

如果使用存储过程,你可以有条件地运行的SQL无论哪种方式,而不是它是可能更有效。

喜欢的东西:

if @includeAll = 0 then 
    SELECT 
     Id 
     ,Name 
     ,Total 
    FROM 
     MyTable 
    WHERE Id = 3926 
else 
    SELECT 
     Id 
     ,Name 
     ,Total 
    FROM 
     MyTable 
+3

IF/THEN解决方案将是您表现最好的 - 长时间阅读您的过程,但这是做到这一点的方法。 – SQLMason

+0

@DanAndrews我同意。 – woot

+0

感谢您的帮助。在检查执行计划时,你是对的,它正在执行扫描。 IF/THEN逻辑是一个足够简单的修复。 – waltsj19

0

像这样在您的SQL中放入OR语句会导致扫描。它可能会扫描整个表或索引,这在非常大的表中会非常低效。

如果您查看没有@includeAll部分的查询的查询计划,您可能会看到索引查找操作。只要您添加该或者,您很可能将查询计划更改为表/索引扫描。你应该看看你的查询计划,看看它究竟做了什么。

+0

有没有关于此的任何文档?我从来没有听说过优化器的标准(但后来我在SQL Server内部吸) –

+0

@JoachimIsaksson我不知道。我只是在使用代理键的数据库上尝试了相同的查询,并且在添加OR部分时它正在扫描我的聚簇索引。 – Dismissile

+0

@JoachimIsaksson我不记得读过这个,但我知道这是真的。 – SQLMason

0

当SQL Server生成一个queryplan,就必须创建一个计划,将所有嵌入式变量的任何可能值工作。在你的情况下,当@IncludeAll = 0时索引seek会给你最好的结果,但是在事件@IncludeAll = 1中不能使用索引seek。所以查询优化器别无选择,只能使用适用于@IncludeAll的值。这导致表扫描。

+0

*“它必须创建一个可用于任何嵌入变量的任何可能值的计划”* - 这不是事实,SQL Server将根据编译查询时传递的值生成计划。 – GarethD

+0

@GarethD更多相关信息?我只能找到有关该问题的详细信息:参数嗅探过程,而不是临时SQL。 –

+3

@GarethD是的,这是真的。参数的值用于构建最佳计划,但同一计划必须为参数的所有可能值返回*正确的结果*。当使用“选项(重新编译)”时,SQL Server可以构建一个仅适用于当前参数值的计划,因为该计划将永远不会被重用。 –

0

这是因为SQL服务器不会在OR子句中出现短路。

通常在其他编程语言,如果我们有像

IF (@Param1 == 1 || @Param2 == 2) 

一个条件,如果参数1为= 1就不会甚至懒得评价其他表情。

在SQL Server中,如果你也有类似的或你的WHERE子句中像你在查询中有

WHERE @includeAll = 1 OR Id = 3926 

即使@includeAll = 1计算为真可能继续前进,检查第二个条件反正。

并且在你的where子句中更改哪个表达式被评估为1st的顺序并没有什么区别,因为这是Tunning optimiser在运行时将会决定的事情。你无法控制sql server的这种行为。短路表达评估或评估表达式的顺序。

2

我的猜测是参数嗅探 - 当@includeAll为1时编译的过程,这是已被高速缓存的查询计划。这意味着当它是假的时候,当潜在的和索引查找和密钥查找会更快时,你仍然在进行全表扫描。

我想这样做的最好的办法是:

declare @includeAll bit = 0; 

if @includeAll = 1 
    BEGIN 
     SELECT Id, Name,Total 
     FROM MyTable; 

    END 
ELSE 
    BEGIN 
     SELECT Id, Name,Total 
     FROM MyTable 
     WHERE Id = 3926; 
    END 

或者你可以在每次运行时强制recomplilation:

SELECT Id, Name,Total 
FROM MyTable 
WHERE Id = 3926 
OR @IncludeAll = 1 
OPTION (RECOMPILE); 

为了证明这一点。此外,我成立一个非常简单的表格,并用无意义的数据填充:

CREATE TABLE dbo.T (ID INT, Filler CHAR(1000)); 
INSERT dbo.T (ID) 
SELECT TOP 100000 a.Number 
FROM master..spt_values a, master..spt_values b 
WHERE a.type = 'P' 
AND  b.Type = 'P' 
AND  b.Number BETWEEN 1 AND 100; 

CREATE NONCLUSTERED INDEX IX_T_ID ON dbo.T (ID); 

然后我运行相同的查询4次。

  1. 随着@IncludeAll设置为1,查询计划使用表扫描和计划缓存
  2. 相同的查询与@IncludeAll设置为false,与表扫描计划仍缓存,使得在使用。
  3. 清除计划的缓存,然后以@IncludeAll为假再次运行查询,以便现在编译计划并使用索引查找和书签查找进行存储。
  4. 运行@IncludeAll设置为true。索引查找和查找再次使用。

DECLARE @SQL NVARCHAR(MAX) = 'SELECT COUNT(Filler) FROM dbo.T WHERE @IncludeAll = 1 OR ID = 2;', 
     @ParamDefinition NVARCHAR(MAX) = '@IncludeAll BIT', 
     @PlanHandle VARBINARY(64); 

EXECUTE sp_executesql @SQL, @ParamDefinition, 1; 
EXECUTE sp_executesql @SQL, @ParamDefinition, 0; 

SELECT @PlanHandle = cp.Plan_Handle 
FROM sys.dm_exec_cached_plans cp 
     CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS st 
WHERE st.text LIKE '%' + @SQL; 

DBCC FREEPROCCACHE (@PlanHandle); -- CLEAR THE CACHE 

EXECUTE sp_executesql @SQL, @ParamDefinition, 0; 
EXECUTE sp_executesql @SQL, @ParamDefinition, 1; 

DBCC FREEPROCCACHE (@PlanHandle); -- CLEAR THE CACHE 

检查执行计划表明,一旦查询已经编译它会重复使用相同的计划,无论参数值,而且它会缓存适合于通过当值计划它首先运行,而不是最灵活的基础。

enter image description here

+0

我对我的sql server 2008r2实例进行了测试,得到了完全相反的结果。具体来说,我总是得到一个扫描,不管我是先设置@@ includeAll = 0还是@@ IncludeAll = 1。我甚至使用过你的代码。 – Aheho

3

显然,在这种情况下,@includeAll = 1将评估假;然而,包括那个增加查询的时间,就好像它是 总是如此。

发生这种情况是因为这两个谓词强制SQL Server选择一个Index|Table Scan运算符。为什么?

@includeAll变量/参数的所有可能值生成执行计划。所以,当@includeAll = 0@includeAll = 1时使用相同的执行计划。如果@includeAll = 0为真,并且如果在Id列上存在索引,则SQL Server 可以使用Index SeekIndex Seek + Key|RID Lookup来查找行。但是,如果@includeAll = 1为真,则最佳数据访问运算符为Index|Table Scan。因此,如果对于@includeAll变量的所有值,执行计划必须为可用 SQL Server所使用的数据访问操作符是什么:Seek或Scan?答案是波纹管,你可以找到类似的查询:

DECLARE @includeAll BIT = 0; 

-- Initial solution 
SELECT p.ProductID, p.Name, p.Color 
FROM Production.Product p 
WHERE @includeAll = 1 OR p.ProductID = 345 

-- My solution 
DECLARE @SqlStatement NVARCHAR(MAX); 
SET @SqlStatement = N' 
SELECT p.ProductID, p.Name, p.Color 
FROM Production.Product p 
' + CASE WHEN @includeAll = 1 THEN '' ELSE 'WHERE p.ProductID = @ProductID' END; 

EXEC sp_executesql @SqlStatement, N'@ProductID INT', @ProductID = 345; 

这些查询有以下执行计划:

enter image description here

正如你所看到的,第一执行计划包括一个Clustered Index Scan两个not optimized谓词。

我的解决办法是基于动态查询和它产生取决于@includeAll变量的值的两个不同的查询从而:

[1]@includeAll = 0生成的查询(@SqlStatement)是

SELECT p.ProductID, p.Name, p.Color 
FROM Production.Product p 
WHERE p.ProductID = @ProductID 

并且执行计划包括Index Seek(如上图所示)和

[2]@includeAll = 1生成的查询(@SqlStatement)是

SELECT p.ProductID, p.Name, p.Color 
FROM Production.Product p 

和执行计划包括Clustered Index Scan。这两个生成的查询具有不同的最佳执行计划。

注:我用Adventure Works 2012样本数据库

+1

为什么它这么做的神奇解释! – waltsj19