当心空无一物,它便无边无涯。树在。山在。大地在。岁月在。我在。你还要怎样更好的世界?——张晓风《我在》
为什么要写这个内容?
随着工作经历的积累,越来越感觉到,大量的关系型数据库的性能问题,其根源在于统计信息。这里说的是根源,其实很多时候大家觉得的那些什么索引失效等都只是表象。当然,不能一概而论,还有很多问题如配置问题、设计问题等等,甚至电源也会影响性能。
之所以得出这个结论,因为在常规的开发和部署过程中,一般企业级系统已经大量使用较为高级的磁盘阵列甚至企业级SSD,IO方面的问题已经很少,而且关系数据库的理论发展跟不上硬件发展的速度,所以硬件性能问题带来的系统级别的性能问题已经很少了。
而数据库设计方面的问题,当然也是很重要的,但是对于一个用了几年的系统来说,修修补补也差不多了。
对于编码问题,其实跟索引合起来会比较好,很多时候不是索引有问题,而是你的写法导致了优化器没有用上索引。另外如果是索引问题(没有索引、有索引但没用上、索引过多等),可以看一下我另外两篇索引相关的文章:
1. SQL Server 索引维护(1)——如何获取索引使用情况 http://blog.csdn.net/dba_huangzj/article/details/49735941
2. SQL Server 索引维护(1)——系统常见的索引问题 http://blog.csdn.net/dba_huangzj/article/details/49734963
上面提到的几点相对来说比较容易发现和控制,但是本系列提到的统计信息,由于它很少直接表现出来,而是直接或间接引起其他问题,所以它更加隐秘和难发现。也因此我觉得有必要写这个主题。
总得来说:统计信息就是为了协助数据库管理系统的核心组件之一——性能优化器/查询优化器生成合适的执行计划从而尽可能高效地响应请求。
什么是统计信息
在研究性能问题之前,非常有必要研究统计信息,因为没有统计信息,关系数据库就是个傻子。没有统计信息,优化器往往会使用不合理的假设来生成执行计划从而严重地影响性。
那么数据库需要什么信息呢?首先,要说说数据存储,关系数据库使用块或者页作为最小存储单元,通常是8K/4K大小。也就是说如果你只存储1K的数据,就有7K/3K的空间是浪费的。那么数据库如何知道数据的存储情况以便后期在对 数据库进行维护和使用时能够有一个“可靠”的依据呢?答案当然就是统计信息。
回到统计信息上面,当要求数据库收集统计信息时,它通常会收集这些内容:
- 表有多少行/页
- 对于表中的每一列,唯一值的长度信息(最长,最短,平均)、数据值的范围(最大,最小,平均)
- 表上面的索引信息
这些统计信息可以帮助优化器评估语句可能要使用的CPU、I/O、内存等资源。
同时列上面的统计信息非常重要,比如表A,在关联时用到两个列:firstname和lastname,通过统计信息,数据库知道firstname有1000个不同的值,而lastname有10万个不同的值。因此,在关联时数据库会先关联lastname再关联firstname,因为在lastname中,由于存在大量不同的值,所以列匹配时通常只需要匹配前2~3个字符就足够了。
但是这仅仅是基础统计信息,除此之外还可以让数据库收集其他信息如直方图(histograms),直方图描述列的数据分布情况,比如:
- 出现频率最高的值。
- 分位数等
准确的统计信息可以协助优化器生成更好的执行计划。特别是有=符号的限定词(如where age=18)。或者范围限定词(where age>18 and age<40。)因为通过统计信息,数据库已经知道这部分数据的分布情况。(专业术语叫选择度 selectivity)。
由于统计信息的重要性,必须实时更新,没有什么比数据库认为一个100万行的表只有500行更糟糕。统计信息唯一的缺点就是需要资源去计算准确的信息。这也是为什么很多数据库管理系统默认不自动更新统计信息的原因。计算几百万数据的信息即使今天也相当困难。所以可以只计算基础统计信息或者计算数据库的一个样本。
比如当你处理几百个都过百万行数据的表时,只计算10%的统计信息可以得益不浅。但是如有可能,还是应该保证统计信息的准确性,因为不准确的统计信息可以把一个30秒的查询变成运行8小时。
SQL Server统计信息简介及环境准备
由于本人一直从事SQLServer工作,所以很不幸,在演示、介绍过程中会以SQL Server为例,但是我尽量接近关系数据库层面而不限于某个产品。同时下一节专门介绍SQLServer统计信息,所以这里只做一个简单的概述:
90%以上的用户关注数据库,除了功能满足要求之外,剩下的关注点无疑就在性能上面。那么如何获得高性能?根源还是在让数据库生成合适的高效的执行计划(如何生成执行计划不在本系列中介绍)。前面提到过,统计信息就是为了协助优化器生成执行计划,准确的、有用的统计信息可以最大限度提供优化器生成高效执行计划所需要的信息(当然这是一个复杂的过程,影响执行计划生成的因素有很多),不准确的统计信息却能把一个秒级查询变成数小时的慢查询。所以统计信息也和索引一样,是一个双刃剑。我们可以把这段话简化成:
查询,烂查询和统计信息
回到SQL Server,当我们需要从数据库中获取数据时,性能是一个关键点(系统异常、故障、逻辑错误在这里不考虑)。SQL Server如果能从数据库中得到准确的有效的关于数据的信息,那么SQLServer在执行数据查询时,就能选择更加高效的方式(表现为执行计划)。而这些信息的关键数据源就是“统计信息”。如果统计信息过时/有误,性能将变得无法接受。
你传给SQL Server的查询首先会传递到SQL Server查询优化器(Query Optimizer)中,如果SQL Server没有缓存执行计划,那么它就会针对这个查询生成一个查询计划。在生成的过程中,SQL Server优化器会 选择与逻辑操作符(如join等)对应的合适的物理操作符在物理层面上进行执行(注:SQL,结构化查询语言,是为了让用户告知数据库需要什么数据或操作,而不是告诉数据库如何去获取数据和操作,所以SQL语言传输到DBMS后需要转换成计算机能识别的操作,这些操作在高层次上看来就是一系列的物理操作符)。在选择物理操作符的时候,一个逻辑操作符可能可以使用多个物理操作符来实现,比如对于逻辑操作符中的WHERE XXX=XXXX,在物理操作符中可能使用索引查找(Index seek)或表扫描(Table Scan)来实现,又比如对于SQL语句中的各种JOIN,可选的物理操作符有Hash、Merge和Nested Loop Joins。
现实世界中,有很多因素会影响优化器对物理操作符的选择,其中最重要的一个就是基数预估(cardinality estimations),这个过程会计算在应用了过滤操作之后,可能剩下的符合条件的行数。不准确的基数预估所生成的执行计划会比准确的执行计划低几个数量级(本人真的见过小时级降到秒级的差距,其原因就是统计信息)。除此之外,这些基数预估还会影响是否进行并行执行、JOIN的顺序,甚至内存分配等一系列的问题。
为了后续演示之用,本系列主要使用SQLServer 2014,自定义一个数据库和测试数据来做演示,如果没有SQL 2014也没关系,这个只影响后面介绍SQL 2014新基数预估的实现而已,本系列中大部分的内容在SQL Server全系列(2000之前的就免了,但是我建议还是以2008 为起点)还是通用的。
本文仅仅是一个简介,对于这里提到的很多东西,会在后面几篇文章里面提到,写好的链接会跟在下面贴出来。