wal*_*wal 10 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
Run Code Online (Sandbox Code Playgroud)
t1,t2和t3分别有~70k,35k和73k行.
当我省略order by
此查询时,在3秒内执行73k行.
正如所写,查询需要8.5 分钟才能返回~50k行(我已经停止了它)
切换LEFT JOIN
s 的顺序有所不同:
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
Run Code Online (Sandbox Code Playgroud)
现在运行3秒钟.
我桌子上没有任何索引.添加索引t1.tradeNo
并t2.trade_id
和t3.TradeReportID
没有效果.仅使用一个左连接(两个方案)与order by
快速结合运行查询.
我可以更换LEFT JOIN
s 的顺序,但这并没有解释为什么会发生这种情况,在什么情况下它可能再次发生
VS
切换左连接的顺序(快):
我注意到的是明显不同但我不能解释这些来解释性能差异
似乎添加了order by
子句导致执行计划使用表假脱机(懒惰的假脱机)而不是在快速查询中使用它.如果我通过DBCC RULEOFF ('BuildSpool');
这个"修复"速度来关闭表盘,但根据这篇文章,这是不推荐的,不管怎么说都无法进行查询
返回的列之一(table3.Text
]具有类型varchar(max)
) - 如果更改为,nvarchar(512)
那么原始(慢)查询现在很快 - 即执行计划现在决定不使用表假脱机 - 还要注意,即使该类型是varchar(max)
字段每个行的值都为NULL.现在这是可以解决的,但我不是更明智的
执行计划中的警告说明
表达式中的类型转换(CONVERT_IMPLICIT(nvarchar(50),[t2].[trade_id],0))可能影响查询计划选择中的"CardinalityEstimate",...
t1.tradeNo
是nvarchar(21)
- 其他两个varchar(50)
- 在改变后两个与第一个相同后问题消失了!(保留更新2中所述的varchar(max)不变)
当UPDATE 2或UPDATE 3被纠正时,这个问题就消失了,我猜它是一个查询优化器的组合,使用临时表(表假脱机)为一个具有无限大小的nvarchar(max)
列- 尽管列没有数据,但非常有趣.
您可能最好的解决方法是确保连接的两端具有相同的数据类型.不需要一个varchar
人和另一个人nvarchar
.
这是DB中经常出现的一类问题.数据库假设它将要处理的数据的组成是错误的.您的预计执行计划中显示的成本可能与实际计划中的成本相差很远.我们都会犯错误,如果SQL Server从它自己学到的东西,那将是件好事,但目前它并没有.它将估计2秒的返回时间,尽管一次又一次被证明是错误的.公平地说,我不知道任何有机器学习能做得更好的DBMS.
最困难的部分是通过排序table3来预先完成的.这意味着它可以进行有效的合并连接,这反过来意味着它没有理由对假脱机做懒惰.
拥有一个ID引用存储为两个不同类型和数据长度的相同的东西不应该是必要的,并且对于ID永远不是一个好主意.在这种情况下nvarchar
,varchar
在另一个地方.如果这使它无法获得基数估计,这是关键的缺陷,这就是为什么:
优化器希望只需要table3中的几个唯一行.只有少数选项,如"男性","女性","其他".这就是所谓的"低基数".因此,想象一下,出于某种奇怪的原因,tradeNo实际上包含了性别的ID.记住,这是你的人性化背景化技能,谁知道这是不太可能的.DB对此视而不见.所以这是它预期发生的事情:当它第一次遇到"男性"的ID时执行查询,它将懒洋洋地获取相关数据(如"男性"一词)并将其放入假脱机中.接下来,因为它已经分类,所以它只需要更多的男性,并且只需重新使用它已经放在假脱机中的东西.
基本上,它计划从几个大块中获取表1和表2中的数据,停止一次或两次以从表3中获取新的细节.实际上,停止不是偶然的.实际上,它甚至可能会停止每一行,因为这里有许多不同的ID.该懒滑就像是要上楼去得到一个小的事情在同一时间.如果您认为自己只需要钱包,那就太好了.如果你搬家不太好,在这种情况下你会想要一个大盒子(急切的线轴).
缩小表3中字段大小的可能原因是,它意味着估计在完整排序方面做懒惰的线轴时相对较少的好处.由于varchar
它不知道有多少数据,可能有多少数据.需要改组的潜在数据块越大,需要做的物理工作就越多.
使表模式和索引反映数据的真实形状.
varchar
在一个表中,那么它不太可能需要nvarchar
另一个表中可用的额外字符.避免在ID上进行转换,并且尽可能使用整数而不是字符.使用连接顺序轻推正确的方向.
如果一切都失败了
INCLUDE
在TradeReportId的索引中从table3中获取所需的所有字段.索引无法帮助的原因是它们可以很容易地确定如何重新排序,但它仍然没有完成.这是它希望用懒惰的线轴进行优化的工作,但如果数据被包含在内,那么它就已经可用,所以没有优化的工作.