背景
最近小伙伴们在开发过程中遇到一个有趣的问题:原本很快的SQL在LEFT JOIN了两张表,并增加了一个WHERE id IN list的查询条件之后,查询性能急剧下降导致性能问题。
分析执行计划后,一直没有办法解释为什么新增了一个查询条件之后会导致性能下降。后来经过DBA帮助后发现,在更新了SQL Server的Statistic信息后,问题解决。以前一直没有怎么关注过statistics这个,觉得问题很有意思,于是记录下来作为备忘。
解决过程
因为此次修改仅仅是在之前的存储过程的基础上,增加了两个表的LEFT JOIN和WHERE语句中增加了一个IN的条件。经过排查,基本排除了索引之类的问题,分析执行计划后发现有个表异常查询次数异常的多。
但是问题在于,此次新增的条件和该问题表并无关系,所以无法解释为什么带上新的条件和不带新的条件性能差距达到了几十倍的差距。问题SQL大致如下:
// 这里的table2的join子查询本身就不太好,因此最好使用临时表来替代
// 而且table2也是执行计划反应出来的主要问题点
SELECT *
FROM table1 as t1
LEFT JOIN (SELECT * from table2 group by name) as t2 on t1.id = t2.id
LEFT JOIN table3 as t3 ON t1.id = t3.id
WHERE t.valid>0 AND t1.id in (1,2,3,4,5)
后来经过DBA的帮助,手动更新了SQL Server的Statistics信息后,发现不仅之前的查询性能得到了改善,而且性能问题也消失了。结合相关资料分析后,认为可能是因为Statistic信息不准确导致了执行计划不准确,从而导致的性能问题。
后续思考
在以前的SQL调优过程中,最多的还是关注了在B+树上的索引情况,来进一步优化SQL,但是对于实际上数据库本身所维护的statistic(这些信息没怎么关注过。其实,这些信息本身不仅仅能够完成本身的工作,还能给我们是否有必要建立索引,以及索引的有效性等提供很大的帮助。
例如在SQL Server中可以通过执行如下命令,可以获取到statistics的相关信息。
DBCC SHOW_STATISTICS(<table_name> , <index_name>)
-
首先我们可以看到statistic相关的一些更新时间和采样数量之类的信息。这里最重要的一个个人觉得就是统计信息的更新时间了。虽然在数据库中(不同数据库不同版本间的策略是不一样的,比如mysql5.5和5.6之间就有差异)统计信息的更新一般默认是由数据库自动创建和维护的,但是鉴于性能等因素的考量,这个更新并非是及时的。因此有的DBA会定期会手动(脚本也算手动的一种吧)去触发它的更新从而维持数据的性能。这也是本次问题造成的原因之一吧。
其次就是采样之类的数据表明了统计的准确性,这里可以看到由于数据量不多所以进行的全表采样,所以准确性是比较高的。
-
其次第二张表主要可以看到density(简单的可以认为是数据的重复性)这一类给我们的索引的必要性提供了很好的依据,如果这个值比较小其实索引的意义相对就不大。原因也很简单,如果大部分数据都是重复的,那么其实索引的效果肯定不会很好,比如性别可能就不是一个很好的索引列,因为无论如何取值就只有两个(当然这里是为了简单,其实性别也可以有很多个)。
p.s. 但是凡事无绝对,虽然普通的索引来说性别不是个好的索引列,但是bitmap索引来说,情况就不一样了。
-
最后一张表中,通常也称为Histogram,主要描述了数据的分布情况。
结语
虽然以往的sql优化中主要还是以sql语句本身的优化为主,但是更好的了解数据库本身的执行原理,能够帮助我们更好的做好优化和问题排查,从而实现高性能的服务。
在优化sql语句本身,分析执行计划一筹莫展的时候,更新更新statistics,然后在结合统计信息分析分析也是一个好办法。
参考资料
SQL语句调优 - 统计信息的含义与作用及维护计算
SQL Server 统计信息维护策略的选择
MySQL中Cardinality值的介绍
MySQL 5.6为什么关闭元数据统计信息自动更新&统计信息收集源代码探索