通过大幅减慢查询来

时间:2015-11-27 00:16:56

标签: sql-server performance

使用sql server 2014; ((SP1-CU3)(KB3094221)2015年10月10日x64

我有以下查询

SELECT * FROM dbo.table1 t1

                    LEFT JOIN dbo.table2 t2 ON t2.trade_id = t1.tradeNo
                    LEFT JOIN dbo.table3 t3 ON t3.TradeReportID = t1.tradeNo                                                                                                
                    order by t1.tradeNo

分别在t1,t2和t3中有~70k,35k和73k行。

当我省略order by此查询在3秒内以73k行执行时。

正如所写,查询需要8.5 分钟才能返回~50k行(我已经停止了它)

切换LEFT JOIN的顺序会有所不同:

SELECT * FROM dbo.table1 t1

                    LEFT JOIN dbo.table3 t3 ON t3.TradeReportID = t1.tradeNo                                                                                                
                    LEFT JOIN dbo.table2 t2 ON t2.trade_id = t1.tradeNo                     
                    order by t1.tradeNo

现在运行3秒钟。

我在桌子上没有任何索引。添加索引t1.tradeNot2.trade_id以及t3.TradeReportID无效。

我可以更换order by的顺序,但这并不能解释为什么会发生这种情况,以及在什么情况下它可能再次发生

估计的exectuion计划是:(慢) enter image description here

(感叹号详情) enter image description here

VS

切换左连接的顺序(快):

enter image description here

我注意到明显不同但我不能解释这些来解释性能差异

更新

似乎添加LEFT JOIN子句会导致执行计划使用Table Spool(延迟假脱机)而不是在快速查询中使用它。 如果我通过order by关闭表格假脱机,则“修复”速度,但根据this post这不建议,无论如何都不能按照查询执行

更新2

返回的其中一列(DBCC RULEOFF ('BuildSpool');]的类型为table3.Text) - 如果将其更改为varchar(max),则原始(慢)查询现在很快 - 即执行计划现在决定不使用Table Spool - 还要注意,即使类型为nvarchar(512),每个行的字段值都为NULL。现在这已经可以解决,但我不是更明智的

更新3

执行计划中的警告

  

表达式中的类型转换(CONVERT_IMPLICIT(nvarchar(50),[t2]。[trade_id],0))可能影响查询计划选择中的“CardinalityEstimate”,...

varchar(max)t1.tradeNo - 其他两个是nvarchar(21) - 在将后两个改为与第一个相同后问题消失了! (保留更新2中所述的varchar(max))

当UPDATE 2或UPDATE 3被纠正时,这个问题消失了我猜它是一个查询优化器的组合使用临时表(表假脱机)为一个具有无限大小的列 - 尽管{非常有趣} {1}}列没有数据。

2 个答案:

答案 0 :(得分:5)

您可能最好的解决方法是确保您的联接的两侧具有相同的数据类型。没有人需要varchar而另一个nvarchar

这是DB中经常出现的一类问题。数据库假设错误关于其即将处理的数据的组合估算执行计划中显示的费用可能与您在实际计划中获得的费用相差很远。我们都犯了错误,如果SQL Server从它自己学到的东西,那真的很好,但目前它并没有。它将估计2秒的返回时间,尽管一次又一次被证明是错误的。公平地说,我不知道任何有机器学习能做得更好的DBMS。

您的查询快速

最困难的部分是通过排序table3来预先完成的。这意味着它可以进行有效的合并连接,这反过来意味着它没有理由对假脱机做懒惰。

慢的地方

拥有一个ID,该ID引用存储为两种不同类型和数据长度的相同内容不应该是必需的,并且永远不会是ID的好主意。在这种情况下,nvarchar位于另一个地方varchar。如果这使得无法获得基数估计是关键缺陷,那就是原因:

优化器希望只需要table3中的几个唯一行。只有少数选项,如"男","女","其他"。那将是所谓的"低基数"。因此,想象一下,出于某种奇怪的原因,tradeNo实际上包含了性别的ID。请记住,你拥有语境化的人类技能,谁知道这种可能性很小。 DB对此视而不见。所以这是它预期会发生的事情:当它第一次遇到" Male"的ID时执行查询。它将懒洋洋地获取相关数据(如单词" Male")并将其放入假脱机中。接下来,因为它的排序,它只需要更多的男性,并简单地重新使用它已经放在假脱机中的东西。

基本上,它计划从表1和表2中取几个大块的数据,停止一次或两次以从表3中获取新的细节。实际上,停止不是偶然的。实际上,它甚至可以停止每一行,因为这里有很多不同的ID。 懒惰的线轴就像上楼一次得到一件小东西。如果您认为自己只需要钱包,那就太好了。如果你搬家的话不太好,在这种情况下,你会想要一个大箱子(热心的线轴)。

缩小表3中字段大小的可能原因是,它意味着估计在完全排序方面做懒惰的线轴时相对较少的好处。使用varchar时,它不知道有多少数据,可能有多少数据。需要改组的潜在数据块越大,需要做的物理工作就越多。

将来可以采取哪些措施

使表格架构和索引反映数据的真实形状。

  • 如果某个ID可以在一个表中varchar,则它不太可能需要nvarchar中另一个表中可用的额外字符。避免在ID上进行转换,并且尽可能使用整数而不是字符。
  • 问问自己这些表中是否有任何需要交易才能填写 所有行。如果是这样,请在该表上使其不可为空。接下来,问一下 对于任何这些表,ID应该是唯一的,并在其中进行设置 适当的指数。唯一的是最大基数的定义 所以它不会再犯这个错误了。

使用连接顺序向右移动。

  • 您在SQL中加入的顺序是向数据库发出的信号,表明您希望每次加入的强大程度/难度。 (有时作为一个人,你知道更多。例如,如果查询50岁的宇航员,你知道过滤宇航员将是第一个应用的过滤器,但可能从搜索50年办公室工作人员的年龄开始。)沉重的东西应该来第一。如果它认为有更好的信息可以忽略你,但在这种情况下,它依赖于你的知识。

如果一切都失败了

  • 可能的解决方法是INCLUDE您在TradeReportId的索引中从table3中需要的所有字段。索引无法提供帮助的原因是,它们可以轻松识别如何重新排序,但它仍然没有完成 >。这是它希望用懒惰的线轴进行优化的工作,但是如果数据被包含在内,它就已经可用,所以没有优化的工作。

答案 1 :(得分:-1)

在表上建立索引是加速数据检索的关键。从这开始,然后重试您的查询,看看是否使用'ORDER BY'

提高了速度