使用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.tradeNo
和t2.trade_id
以及t3.TradeReportID
无效。
1}}只使用一个左连接(两个方案)运行查询的速度很快。
我可以更换order by
的顺序,但这并不能解释为什么会发生这种情况,以及在什么情况下它可能再次发生
VS
切换左连接的顺序(快):
我注意到明显不同但我不能解释这些来解释性能差异
似乎添加LEFT JOIN
子句会导致执行计划使用Table Spool(延迟假脱机)而不是在快速查询中使用它。
如果我通过order by
关闭表格假脱机,则“修复”速度,但根据this post这不建议,无论如何都不能按照查询执行
返回的其中一列(DBCC RULEOFF ('BuildSpool');
]的类型为table3.Text
) - 如果将其更改为varchar(max)
,则原始(慢)查询现在很快 - 即执行计划现在决定不使用Table Spool - 还要注意,即使类型为nvarchar(512)
,每个行的字段值都为NULL。现在这已经可以解决,但我不是更明智的
执行计划中的警告
表达式中的类型转换(CONVERT_IMPLICIT(nvarchar(50),[t2]。[trade_id],0))可能影响查询计划选择中的“CardinalityEstimate”,...
varchar(max)
是t1.tradeNo
- 其他两个是nvarchar(21)
- 在将后两个改为与第一个相同后问题消失了! (保留更新2中所述的varchar(max))
当UPDATE 2或UPDATE 3被纠正时,这个问题消失了我猜它是一个查询优化器的组合使用临时表(表假脱机)为一个具有无限大小的列 - 尽管{非常有趣} {1}}列没有数据。
答案 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
时,它不知道有多少数据,可能有多少数据。需要改组的潜在数据块越大,需要做的物理工作就越多。
使表格架构和索引反映数据的真实形状。
varchar
,则它不太可能需要nvarchar
中另一个表中可用的额外字符。避免在ID上进行转换,并且尽可能使用整数而不是字符。使用连接顺序向右移动。
如果一切都失败了
INCLUDE
您在TradeReportId的索引中从table3中需要的所有字段。索引无法提供帮助的原因是,它们可以轻松识别如何重新排序,但它仍然没有完成 >。这是它希望用懒惰的线轴进行优化的工作,但是如果数据被包含在内,它就已经可用,所以没有优化的工作。答案 1 :(得分:-1)
在表上建立索引是加速数据检索的关键。从这开始,然后重试您的查询,看看是否使用'ORDER BY'
提高了速度