在前阵子写的一篇博文“SQL SERVER 2014 下IF EXITS 居然引起执行计划变更的案例分享”里介绍了数据库从SQL SERVER 2005升级到 SQL SERVER 2014后,发现一个SQL出现性能问题,当时分析后发现执行计划变了,导致SQL出现了性能问题。但是没有彻底搞清楚为什么出现这种情况。当时看到Actual Number of Rows 与Estimated Number of Rows之间的偏差较大(统计信息是最新的),以为是优化器的Bug造成的。其实罪魁祸首是SQL SERVER 2014新特性——基数评估(Cardinality Estimator)所引起的。IF EXISTS完全成了我这个标题党的替罪羊(罪过罪过)。下面我再就这个问题展开做一次分析。
查看该SQL语句的实际执行计划,在属性里面我们可以看到CardinalityEstimationModelVersion的值为120,120表示这是新的基数评估,70就是老的基数评估
其实当数据库的兼容级别为120的时候,默认使用新的基数评估。也就是说启用了新的基数评估,那么我们现在使用查询跟踪标记9481来关闭新的基数评估,使用老的基数评估。
DBCC TRACEON(9481, 1);
GO
启用跟踪标记9481后,这个SQL语句的执行计划变了(可以对比图4),可以看到CardinalityEstimationModelVersion的值也变为了70。SQL语句一秒就执行完了。这个是因为基数评估出现了偏差导致了不合适的JOIN算法。
我们对比下面”图四:旧执行计划“,发现其实还是使用Nested Loops,只是外部循环表与内部循环表变了。
图四:旧执行计划
那么关于新的基数评估(Cardinality Estimator)特性,你想多了解一些这方面的知识,可以参考官方文档Optimizing Your Query Plans with the SQL Server 2014 Cardinality Estimator。 中文翻译版本可以参考SQL Server 2014新特性——基数评估(白皮书阅读笔记)。下面是官方文档关于基数评估出现偏差可能会造成的一些后果:
对于基数评估,每个执行计划中的运算符都有评估值输入,这个值决定了优化器使用什么算法的操作符,同时也决定了最终的执行计划。所以如果评估出现偏差,会导致执行计划选择出现偏差,导致无法选出一个高效的执行计划。
评估出现偏差会出现以下结果:
如果评估过小:
1.原本可以使用并行计划更加有效的,现在使用串行计划
2.不合适的join算法
3.不合适的索引选择,和索引访问方法
如果评估过大:
1.原本使用串行计划更加有效,现在使用并行计划
2.不合适的join算法
3.不合适的索引选择,和索引访问方法
4.过多的内存分配
5.内存浪费和没必要的并发
上面这段对应的英文资料如下所示(英语原文作参考,这才是原汁原味的信息):
The individual operator cost models receive the estimates as input. The estimates are a major factor in deciding which physical operator algorithms and plan shapes (such as join orders) are chosen. They also determine the final query plan that executes. Given these critical plan choices, when the cardinality estimation process contains a significantly skewed assumption, this can lead to an inefficient plan choice. This can, in turn, result in degraded performance.
Under estimating rows can lead to memory spills to disk, for example, where not enough memory was requested for sort or hash operations. Under estimating rows can also result in:
- The selection of serial plan when parallelism would have been more optimal.
- Inappropriate join strategies.
- Inefficient index selection and navigation strategies.
Inversely, over estimating rows can lead to:
- Selection of a parallel plan when a serial plan might be more optimal.
- Inappropriate join strategy selection.
- Inefficient index navigation strategies (scan versus seek).
- Inflated memory grants.
- Wasted memory and unnecessarily throttled concurrency.
Improving the accuracy of row estimates can improve the quality of the query execution plan and, as a result, improve the performance of the query.
其实关于SQL SERVER 2014这个新的基数评估(Cardinality Estimator)特性,确实造成了不少SQL出现性能问题。我们数据库升级到SQL SERVER 2014后,被这个新特性坑惨了,由于没有选择最优的执行计划,导致一些SQL出现严重的性能问题,也间接导致了SQL之间的阻塞(block)急剧上升。开发人员和我都在救火队员的角色中疲于奔命。最后我不得不采取将数据库的兼容基本从120降为110。从而立马解决了这个问题。另外从我搜索的一些资料看,SQL SERVER 2014这个新的基数评估(Cardinality Estimator)这个新特性确实还有很多不完善的地方。因为也有不少人都发现升级到SQL Server 2014后出现了性能问题。例如:
MS SQL Server CPU load goes up dramatically when turning on 2014 features by setting compatibility level
Query is slow in SQL Server 2014, fast in SQL Server 2012
参考资料: