Sal*_*n A 7 sql-server optimization execution-plan sql-server-2012
我正在审查一个性能不佳的查询,如下所示:
WHERE manymany.Active = -1
AND manymany.Check1 = -1
AND manymany.WebsiteID = @P1
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND (main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL)
ORDER BY aux.SortCode
Run Code Online (Sandbox Code Playgroud)
我不小心在这个查询上使用了 SSMS 查询设计器,它重新编写了查询,如下所示:
WHERE manymany.Active = -1
AND manymany.Check1 = -1
AND manymany.WebsiteID = @P2
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND main.TextCol1 IS NOT NULL
OR manymany.Active = -1
AND manymany.Check1 = -1
AND manymany.WebsiteID = @P2
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND main.TextCol2 IS NOT NULL
ORDER BY aux.SortCode
Run Code Online (Sandbox Code Playgroud)
如果您仔细观察,您会注意到它只是OR
通过重复所有条件来扩展条件,即它更改a AND (b OR c)
为(a AND b) OR (a AND c)
。
结果查询的成本减少了 50%,执行时间减少了 33%。我只是不明白为什么OR
在两个查询相同(?)时重新安排条件会改变计划。我可以OR
通过复制粘贴条件来自己扩展条件,但我为什么要这样做?
main 2718
manymany 188761
aux 19
Run Code Online (Sandbox Code Playgroud)
text
数据类型,不能被索引但为什么 SQL Server 不将这两个查询视为一个查询呢?毕竟,a AND (b OR c) = (a AND b) OR (a AND c)?
逻辑上是一样的,也会得到同样的结果。
假设
我的假设是,对于“更快”的计划,优化器不会考虑顶部的某些过滤器语句与OR
底部的某些过滤器语句相同。我可能完全没有根据。
获得这些假设的推理基于以下过滤谓词:
Main
该过滤谓词使用表与表之间的联接结果manymany
。
请注意,此过滤器中的EXPR1021和EXPR1022是根据表上的标量运算符创建的表达式manymany
。
该过滤器由两部分组成,第一部分为普通过滤(.. AND .. OR .. AND ..)
,第二部分为普通AND
过滤
(getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
OR getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
AND (getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
Run Code Online (Sandbox Code Playgroud)
OR
正如您所看到的,该过滤器第一部分 上方和下方的唯一区别是
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
Run Code Online (Sandbox Code Playgroud)
VS
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL
Run Code Online (Sandbox Code Playgroud)
无论如何,第二部分必须为真,因为它们是AND
没有任何 's 的谓词OR
。
导致相同函数的额外计算,我认为这是不需要的。再次,我的猜测是,sql server 进行这些计算的原因是它不知道它们是相同的。
对于 where 子句的某些其他部分,它确实知道这些部分是相同的,例如在主表中, statusid = 1 仅评估一次:
在manymany
表中,相同的语句被计算两次:
在“慢”计划中,语句不与OR
子句添加在一起,这就是为什么优化器生成不同的计划,在表上单独应用过滤谓词(并且没有重复的过滤器)。
假设结束
两个计划的比较
我认为您对“快速”计划的性能很幸运,但是当匹配数据增加时,“快速”计划可能会变得丑陋。这可能取决于您应用过滤器的位置和时间(以及其他因素)。
快速计划过滤
在“快速”计划中:由于两个+ ( ) 块的不同组合,sql server 在main
表与表连接后应用一些过滤器。在找到与表的所有可能的组合后,对列中的列进行过滤。manymany
OR
AND ... AND ... AND...
maintable
manymany
结果,相同的谓词在manymany
表上执行了两次:
main
但表上的某些查找谓词并非如此
在此之后,连接发生,并且对于所有可能的组合,再次
针对main
和之间的连接结果进行更大的过滤谓词manymany
请注意,此过滤器中的EXPR1021和EXPR1022是根据表上的标量运算符创建的表达式manymany
。
该过滤器由两部分组成,第一部分为普通过滤(.. AND .. OR .. AND ..)
,第二部分为普通AND
过滤
(getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
OR getdate()>=[Expr1021]
AND getdate()<=[Expr1022]
AND getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
AND (getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL OR getdate()>=[DB1].[dbo].[main].[FromDate]
AND getdate()<=isnull([DB1].[dbo].[main].[UptoDate],'2050-01-01 00:00:00.000')
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL)
Run Code Online (Sandbox Code Playgroud)
OR
正如您所看到的,该过滤器第一部分 上方和下方的唯一区别是
AND [DB1].[dbo].[main].[TextCol1] IS NOT NULL
Run Code Online (Sandbox Code Playgroud)
VS
AND [DB1].[dbo].[main].[TextCol2] IS NOT NULL
Run Code Online (Sandbox Code Playgroud)
无论如何,第二部分必须为真,因为它们是AND
没有任何 's 的谓词OR
。
导致我认为不需要的额外计算。
慢计划过滤
在“慢”计划中:sql server 根据AND (TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL
) 部分将过滤器直接应用于主表,然后与manymany
表连接以过滤掉其余部分以达到 15 行。
Main
表过滤器
manymany
表过滤器
其他一些有时重叠的信息:
较慢的计划
当我们查看较慢的计划时,将聚集索引 PK_main 用于计算标量、过滤器和嵌套循环运算符:
估计扫描谓词将返回 93 行:
实际上比预期的1947 行少了大约 20 倍。
之后,计算标量或此语句:
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
Run Code Online (Sandbox Code Playgroud)
对这 1947 行进行评估。
然后过滤运算符 ( main.TextCol1 IS NOT NULL OR main.TextCol2 IS NOT NULL
) 将其减少到 1374 行。
之后,将这 1374 行连接到dbo.manymany
表中以获取返回的 15 行。
更快的计划
更快的计划是使用 NC 索引:CVR_main_4
在the dbo.Main
表上,
它使用查找谓词进行过滤,将 27 行返回给nested loops
Join 运算符,再次与表连接dbo.manymany
。
并且实际返回的行数甚至低于估计的行数:
实际 27 行,估计 152 行
过滤
一个很大的区别是过滤发生的位置,在“较慢”计划中,这是直接在桌面上完成的dbo.Main
:
使用谓词:TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL
并将此过滤器应用于 1943 行。
其他过滤直接在dbo.manymany
桌面上进行
而另一个,在“更快”的计划中,在连接 from toOR
之后被过滤,并在 27 行上产生一个更大的过滤器。dbo.Main
dbo.manymany
OR
更大的过滤器, 27 行有多个。
另一个区别是键查找运算符:
它从聚集索引中获取 10 个额外列,但只需对 27 行执行此操作。
优化器选择“较慢”计划的另一个原因可能是因为优化器认为不查找其他列会更好。
快速计划是更快,还是总是“更快”?
我确实认为,如果通过过滤器的数据增加,“慢”计划会更好。不仅是由于键查找,还因为计划中更靠后的更大的过滤器运算符。
如果发生这种情况,就在索引旁边。您可以通过使用语句将查询拆分为多个部分来改进过滤UNION
。
就像这样:
SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
AND manymany.Check1 = -1
AND manymany.Active = -1
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND TextCol1 IS NOT NULL
UNION
SELECT main.MainID, Title, Column1, Column2, Column7, Column4, Column6, Column3, Column5
, CASE WHEN TextCol1 IS NOT NULL OR TextCol2 IS NOT NULL THEN -1 ELSE 0 END AS MoreFlag
, CASE WHEN Stars BETWEEN 1 AND 5 THEN Stars END AS Rating
FROM manymany
INNER JOIN main ON manymany.MainID = main.MainID
LEFT JOIN aux ON manymany.AuxID = aux.AuxID
WHERE manymany.WebsiteID = @P1
AND manymany.Check1 = -1
AND manymany.Active = -1
AND CURRENT_TIMESTAMP BETWEEN ISNULL(manymany.FromDate, '1950-01-01') AND ISNULL(manymany.UptoDate, '2050-01-01')
AND main.Active = -1
AND main.StatusID = 1
AND CURRENT_TIMESTAMP BETWEEN main.FromDate AND ISNULL(main.UptoDate, '2050-01-01')
AND TextCol2 IS NOT NULL
ORDER BY SortCode;
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
171 次 |
最近记录: |