按顺序减慢查询速度

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 JOINs 的顺序有所不同:

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.tradeNot2.trade_idt3.TradeReportID没有效果.仅使用一个左连接(两个方案)与order by快速结合运行查询.

我可以更换LEFT JOINs 的顺序,但这并没有解释为什么会发生这种情况,在什么情况下它可能再次发生

估计的exectuion计划是:(慢) 在此输入图像描述

(感叹号详细信息) 在此输入图像描述

VS

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

在此输入图像描述

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

UPDATE

似乎添加了order by子句导致执行计划使用表假脱机(懒惰的假脱机)而不是在快速查询中使用它.如果我通过DBCC RULEOFF ('BuildSpool');这个"修复"速度来关闭表盘,但根据这篇文章,这是不推荐的,不管怎么说都无法进行查询

更新2

返回的列之一(table3.Text]具有类型varchar(max)) - 如果更改为,nvarchar(512)那么原始(慢)查询现在很快 - 即执行计划现在决定不使用表假脱机 - 还要注意,即使该类型是varchar(max)字段每个行的值都为NULL.现在这是可以解决的,但我不是更明智的

更新3

执行计划中的警告说明

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

t1.tradeNonvarchar(21)- 其他两个varchar(50)- 在改变后两个与第一个相同后问题消失了!(保留更新2中所述的varchar(max)不变)

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

Ada*_*ish 5

您可能最好的解决方法是确保连接的两端具有相同的数据类型.不需要一个varchar人和另一个人nvarchar.

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

您的查询速度很快

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

哪里很慢

拥有一个ID引用存储为两个不同类型和数据长度的相同的东西不应该是必要的,并且对于ID永远不是一个好主意.在这种情况下nvarchar,varchar在另一个地方.如果这使它无法获得基数估计,这是关键的缺陷,这就是为什么:

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

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

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

你今后可以做些什么来避免

使表模式和索引反映数据的真实形状.

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

使用连接顺序轻推正确的方向.

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

如果一切都失败了

  • 可能的解决方法是INCLUDE在TradeReportId的索引中从table3中获取所需的所有字段.索引无法帮助的原因是它们可以很容易地确定如何重新排序,但它仍然没有完成.这是它希望用懒惰的线轴进行优化的工作,但如果数据被包含在内,那么它就已经可用,所以没有优化的工作.