我在SQL(2008)中得到了一个查询,我无法理解为什么花了这么长时间来评估我是否在WHERE语句中包含一个不应该包含的条款。影响结果。以下是查询示例:
declare @includeAll bit = 0;
SELECT
Id
,Name
,Total
FROM
MyTable
WHERE
@includeAll = 1 OR Id = 3926
显然,在这种情况下,@ includeAll = 1将评估为false;但是,包括这会增加查询的时间,就像它总是如此。无论有没有该子句,我得到的结果都是正确的:我只获得Id = 3926的1条目,但是(在我的真实世界查询中)包括该行增加了来自< 0秒到大约7分钟......所以它似乎在运行查询,好像该语句是真的,即使它不是,但仍然返回正确的结果。
任何可以解释的光都会有所帮助。另外,如果您有解决方法的建议,我会接受它。我希望有一个像这样的子句,这样我就可以在存储过程中包含一个参数,使其忽略它所具有的Id,如果设置为true则返回所有结果,但我不能允许只需尝试获取一条记录就会影响性能。
答案 0 :(得分: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
答案 1 :(得分:3)
显然,在这种情况下,@ includeAll = 1将评估为false; 但是,包括这会增加查询的时间,就好像它一样 总是如此。
这是因为这两个谓词强制SQL Server选择Index|Table Scan
运算符。为什么?
为@includeAll
变量/参数的所有可能值生成执行计划。因此,在@includeAll = 0
和@includeAll = 1
时使用相同的执行计划。如果@includeAll = 0
为真并且Id
列上有索引,则SQL Server 可以使用 Index Seek
或Index Seek
+ {{1}找到行。但如果Key|RID Lookup
为真,则最佳数据访问运算符为@includeAll = 1
。因此,如果执行计划必须可用所有Index|Table Scan
变量值,SQL Server使用的数据访问运算符是什么:Seek或Scan?答案在下面你可以找到类似的查询:
@includeAll
这些查询具有以下执行计划:
如您所见,第一个执行计划包含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;
个Clustered Index Scan
个谓词。
我的解决方案基于动态查询,并根据not optimized
变量的值生成两个不同的查询:
[1] 当@includeAll
生成的查询(@includeAll = 0
)
@SqlStatement
并且执行计划包括SELECT p.ProductID, p.Name, p.Color
FROM Production.Product p
WHERE p.ProductID = @ProductID
(如上图所示)和
[2] 当Index Seek
生成的查询(@includeAll = 1
)
@SqlStatement
并且执行计划包括SELECT p.ProductID, p.Name, p.Color
FROM Production.Product p
。这两个生成的查询具有不同的最佳执行计划。
注意:我使用了Adventure Works 2012示例数据库
答案 2 :(得分:2)
我的猜测是参数嗅探 - @includeAll
为1时编译的过程,这是已缓存的查询计划。这意味着当它为false时,你仍然可以进行全表扫描,并且索引搜索和键查找会更快。
我认为最好的方法是:
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
或者你可以在每次运行时强制重新编译:
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次相同的查询。
@IncludeAll
设置为1,查询计划使用表扫描并计划缓存@IncludeAll
设置为false的同一查询,仍然会缓存带有表扫描的计划,以便使用该计划。@IncludeAll
false再次运行查询,以便现在使用索引查找和书签查找编译和存储计划。@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
检查执行计划表明,一旦编译了查询,它将重用相同的计划而不管参数值如何,并且它将缓存适用于首先运行时传递的值的计划,而不是大多数灵活的基础。
答案 3 :(得分:0)
在您的SQL中放置OR语句会导致扫描。它可能会扫描整个表或索引,这在非常大的表中非常低效。
如果查看没有@includeAll部分的查询的查询计划,您可能会看到索引查找操作。只要添加该OR,您就很可能将查询计划更改为表/索引扫描。您应该查看查询计划,看看它到底在做什么。
答案 4 :(得分:0)
在Sql Server生成查询计划的情况下,它必须创建一个适用于任何嵌入变量的任何可能值的计划。在你的情况下,索引搜索会在@IncludeAll = 0时给你最好的结果但是在@IncludeAll = 1的事件中不能使用索引搜索。所以查询优化器别无选择,只能使用适用于任何一个的查询计划@IncludeAll的值。这会导致表格扫描。
答案 5 :(得分:0)
这是因为当你在where子句中使用OR时,sql server不会短路。
通常在其他编程语言中,如果我们有像
这样的条件IF (@Param1 == 1 || @Param2 == 2)
如果Param1 = 1,它甚至不会打扰评估另一个表达式。
在Sql Server中,如果你的where子句中的OR与查询中的OR相似
WHERE @includeAll = 1 OR Id = 3926
即使@includeAll = 1的计算结果为true,它也可以继续检查第二个条件。
更改where子句中的Order,哪个表达式被评估为1st并没有任何区别,因为这是Tunning优化器将在运行时决定的。你无法控制sql server的这种行为。短路表达式评估或评估表达式的顺序。