【连载】关系型数据库是如何工作的?(12) - 查询管理器之Statistics

在学习数据库如何优化查询之前,我们必须先讲讲统计这件事,因为没有统计信息,数据库会变的很愚蠢导致没法优化或者优化效果很差。

数据库究竟需要哪种统计信息呢?首先,我们必须简短的介绍下数据库和操作系统是如何存储数据的。它们的最小存储单位称为或者,每一页大小为4K或者8K。这就意味着,如果你存储1K大小的数据,同样会占用一页,如果一页是8K,那么就会浪费7K的存储空间。

回到统计,当你要求数据库统计时,它会计算一下数据:

  • 表中行和页的数量;
  • 计算每一列的如下数据:

    • 排重列值
    • 列值的长度,包括最大长度、最小长度、平均长度
    • 列值得范围信息,包括最大值、最小值、平均值
  • 表内索引的信息

这些评估会帮助优化器评估一个SQL耗费的CPU、磁盘IO以及内存情况。

每一列的统计信息非常重要,例如一张PERSON表的LAST_NAME和FIRST_NAME列是join字段,基于统计信息,数据库就会知道FIRST_NAME列有1000个不同的值,而LAST_NAME有1 000 000不同的值。因此数据库会将join顺序从FIRST_NAME,LAST_NAME改为LAST_NAME, FIRST_NAME。原因是LAST_NAME的区分度更高,可能只需要比较其前2-3个字符即可,因此需要的比较更少。


译者注:上边的说明道理很简单,但还是需要仔细斟酌理解,我来用伪代码举个具体的例子。
假设有两张表PERSON_BASIC、PERSON_EXTEND,两者之间可以通过LAST_NAME和FIRST_NAME列来关联join,每张表有10条数据。考虑一种极端情况,LAST_NAME有10种取值,FIRST_NAME只有1种取值。假设采用nested loop join方式(这也是MySQL的默认join方式),则:

如果采用FIRST_NAME,LAST_NAME顺序,总比较次数是10*10=100,伪代码如下:

define Result_Set;
for(basicInfo : PERSON_BASIC) {
    for(extendInfo : PERSON_EXTEND) {
        if(basicInfo.FIRST_NAME = extendInfo.FIRST_NAME) {// 比较10次并且10次都会进入内层循环,由于FIRST_NAME只有1种取值,所有这个条件相当于永远为真。
            if(basicInfo.LAST_NAME  = extendInfo.LAST_NAME) {// 比较10次,如果相等则加入结果集。
                Result_Set.add(extendInfo);
            }
        }
    }
}

如果采用LAST_NAME,FIRST_NAME顺序,总比较次数一般情况下小于10*10=100,平均来看是5*10=50,则伪代码如下:

define Result_Set;
for(basicInfo : PERSON) {
    for(extendInfo : PERSON_EXTEND) {
        if(basicInfo.LAST_NAME = extendInfo.LAST_NAME) {// 比较10次,如果相等进入内层循环,大部分情况下,不会都相等,所以进入内层循环次数一般小于10,我们平均值5吧。
            if(basicInfo.FIRST_NAME  = extendInfo.FIRST_NAME) {// 比较10次,如果相等(本例中总是相等),加入结果集。
                Result_Set.add(extendInfo);
            }
        }
    }
}

所以,将区分度高的字段放在join的前边,有利于减少比较的次数,达到优化性能的目的。


上边所说的统计时最基本的统计,数据库也可以具备更高级的统计,比如统计每一列值得分布情况。

这些额外的统计会帮助数据库找到更好的查询计划,尤其是对于相等(如: WHERE AGE = 18 )或者范围(如: WHERE AGE > 10 and AGE <40 ) 条件,因为数据库掌握了列值得选择性。

这些统计信息被存储在数据库元数据中,对于非分区表可以在一下地方看到统计信息:

  • Oracle:USER/ALL/DBA_TABLES and USER/ALL/DBA_TAB_COLUMNS
  • DB2:SYSCAT.TABLES and SYSCAT.COLUMNS

统计信息必须是实时更新的,因为没有什么比数据库认为一张表有500行而实际有1 000 000行更糟糕的事情了。统计的唯一缺点就是数据库需要耗费时间来计算它们,因此数据库一般情况下回禁止自动计算统计信息。当数据上百万的时候,要计算其统计信息会非常耗费,这个时候可以只计算基本统计。

例如,当你的每张表都有几百万行数据时,如果只在10%的数据上计算统计信息也会带来一个巨大的收获。但是对于Oracle 10G来说,这将是一个非常糟糕的决定,因为Oracle 10G偶尔选择一个指定表的指定列的10%数据和100%(一般表不太可能有上亿行)数据完全不是一回事,这种错误的统计会导致查询耗费8个小时,简直是个噩梦。这也体现了统计的重要性。
译者注:这段的大概意思是,对于大部分数据库计算10%数据的统计信息也会带来巨大的性能收获,但是对于Oracle 10G并不适用,因为其选择计算行机制比较特殊。

注:当然每个数据库都有更加高级的统计规范,如果想了解更多可以阅读指定数据库的官方文档。但是,让我真正理解数据库如何使用统计信息的最好的官方文档是PostgreSQL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值