leo_wyn
作者leo_wyn·2010-10-27 15:27
商业智能工程师·Security

[db2 Opt] 解决 DB2 统计信息一致性的最佳实践

字数 16038阅读 5424评论 1赞 0
转自:http://www.ibm.com/developerworks/cn/data/library/techarticles/dm-1010qinw/index.html

序言

DB2 数据库中的统计信息(statistics)是 DB2 系统中很重要的一种信息。作为 DB2“大脑”的优化器(optimizer)是一种基于成本(cost)的优化器,而它计算成本的根据就是数据库对象(表,列,索引等)的相关统计信息。

一般说来,为了得到准确的统计信息,DB2 的数据库管理员(DBA)或者数据库程序开发人员可以通过 DB2 提供的 RUNSTATS 来收集所需的统计信息。但是用 RUNSTATS 来收集数据库中全部完整的统计信息会很耗时,尤其是对于存储海量数据的大型数据库(比如银行系统数据库),有些情况下完整的 RUNSTATS 可能会花费一天甚至数天,这样的操作显然成本太高。所以在实际生产过程中,用户采用的方法往往是用 RUNSTATS 来收集部分数据库对象的统计信息,过一段时间之后再用 RUNSTATS 来收集另一部分数据库对象的统计信息。这个做法的好处是比较灵活,用户只需要收集他感兴趣的某些数据库对象的统计信息,执行成本较低。

但是这样“灵活”的 RUNSTATS 操作也有它不利的一面。因为在某个阶段中 DB2 数据库可能会有数据的变化,比如添加数据或删除数据的操作,而这种数据变化并不会自动更新其对应的 DB2 统计信息,这就导致用户在不同时间点收集到的统计信息可能不一致甚至互相矛盾。这种统计信息之间的不一致对于 DB2 优化器有较大的消极影响,可能造成 SQL 查询性能不佳,但这种情况在某个时间点可能确实存在于用户的数据库当中,而它们往往又难于被发现。

那么在不进行完整的 RUNSTATS 操作的情况下,如何才能发现 DB2 统计信息中的这些“陷阱”,从而消除统计信息中的不一致?本文在这里给出解决这种问题的一些最佳实践。

识别统计信息中的“陷阱”:解决 DB2 统计信息一致性的最佳实践

统计信息对于 DB2 的 SQL 查询计划会有怎样的影响?首先来看一个相关的示例。对于如下的这样一个简单的 SQL 查询语句:

SELECT * FROM PART WHERE P_PARTKEY = ?

基于某个时刻的统计信息,DB2 优化器为这条 SQL 查询语句选定的访问路径图如下图 1 所示。从图中可以看到 DB2 使用了索引扫描来访问数据(index scan)。但是仔细观察会发现,图中的统计信息显示,表 PART 中仅有 1 条数据(表基数为 1)而该表上的索引 PXP@PKPTPN 却对应表中的 10 条数据(索引全键基数为 10)。从数据库理论的角度来看,这是一种统计信息不一致的情况,即:索引的基数不能大于表的基数(后文将会介绍这种情况)。


图 1. 存在统计信息不一致的访问路径图
图 1. 存在统计信息不一致的访问路径图

数据库并不会自动解决这种统计信息不一致的情况—这是数据库管理员要做的工作。那么这种不一致的统计信息是非会影响到 DB2 对访问路径的选择?

假定一个富有经验的数据库管理员注意到了这个问题,重新 RUNSTATS 收集了 PART 表和其上索引的统计信息,消除了这种不一致。此时新的访问路径图会变成下图 2 所示:


图 2. 更新统计信息后的访问路径图
图 2. 更新统计信息后的访问路径图

由图中可以看到,基于更新后正确的统计信息,DB2 选择了一个完全不同的访问路径:选择用表扫描(table scan)来直接访问 PART 表。有经验的数据库管理员可以看出,对于这个查询中数据量很小的 PART 表来说,这个访问路径比之前的方案更加合理。

通过这个简单的示例,可以看到不一致的统计信息对于 DB2 优化器在选择访问路径时存在着影响,这种影响通常情况下都是消极有害的。它们就像是统计信息中的“陷阱”,诱导 DB2 优化器做出不正确的分析和判断,由此产生 SQL 查询的性能问题。而作为数据库管理员,一个重要的任务就是识别并消除这些“陷阱”,保证统计信息的一致性,从而最终对 SQL 查询的性能进行优化。

在实际的应用环境中,DB2 数据库统计信息不一致的情况可能有很多种,如何入手进行分析?从理论上,可以从三个角度出发来进行检查,即:数据库架构决定的统计信息一致性,数据库对象在不同统计信息中的映射一致性,以及不同统计信息之间的代数一致性。

在开始正式介绍这三部分内容之前,首先简单介绍一下 DB2 数据库中常见的统计信息。

DB2 常见的统计信息

DB2 数据库针对不同的数据库对象,定义了各种不同种类的统计信息。这里先简单介绍一下 DB2 数据库当中常用的几种统计信息:

Table cardinality:表基数。对于普通表, 表示表中数据的总行数; 对于辅助表,表示辅助表中 LOB 总数。在本文出现的都是普通表的情况。对应系统表 SYSIBM.SYSTABLES 表 CARDF 列。示例:

Table1

Col1 Col2
1 1
1 1
2 2

Table1 的表基数为 3。

Column cardinality:列基数。对于普通表它表示该表对应列中不同值的数量。对应系统表 SYSIBM.SYSCOLUMNS 表 COLCARDF 列。示例:

Table3

Col1 Col2
3 1
3 2
3 2

Col1 的行基数为 1, 因为只有 ‘ 3 ’ 一种值。

Col2 的行基数为 1,因为有‘ 1 ’,‘ 2 ’ 两种不同值。

Column group: 列组。 统一表中由至少一列组成。示例:

Table4

Col1 Col2 Col3
1 2 3

Table4 可以定义的列组 (Col1),(Col2),(Col1,Col2), (Col2,Col1),(Col1,Col3,Col2) …

Column group cardinality:列组基数。表示表中一个列组的不同值的数量。对应系统表 SYSIBM. SYSCOLDIST,当 SYSIBM. SYSCOLDIST 表中 TYPE 列为‘ C ’时,列组基数对应 CARDF 列。示例:

Table5

Col1 Col2 Col3
11 21 31
11 22 31
11 22 32
11 22 32

Table5 中 对于列组(Col1,Col2,Col3)列组基数为 3. 其包含的不同值如下:

Col1 Col2 Col3
11 21 31
11 22 31
11 22 32

Index full key cardinality:索引全键基数。表示索引中全部键不同值的数量。对应系统表 SYSIBM.SYSINDEXES 中的 FULLKEYCARDF 列。

示例: 对于 Table5, 建立如下索引。

CREATE INDEX INDEX1 ON T1 (COL3,COL1,COL2)

则 index1 的索引全键基数为 3. 索引全部键的不同取值如下:

Col3 Col1 Col2
31 11 21
31 11 22
32 11 22

Index first key cardinality:索引第一键基数。表示索引第一个键的不同值的数量。对应系统表 SYSIBM.SYSINDEXES 中的 FIRSTKEYCARDF 列。

示例:对于 index1 索引第一键基数为 2。索引第一个键是 Col3,该键不同取值如下:

Col3
31
32

Column Frequency:列组频率。表示百分比形式的列组值频率。对应系统表 SYSIBM.SYSCOLDIST 中的 FREQUENCYF 列。示例:

Table6

Col1
1
2
2
3

对于列组(Col1)

Col1 值 列组频率
1 0.25
2 0.50
3 0.25

Histogram Frequency:列组柱状频率。表示百分比形式的列组值落入指定区间的频率。对应系统表 SYSIBM.SYSCOLDIST_HIST 中的 FREQUENCYF 列。示例:

Table7

Col1
1.1
2.1
2.3
3.5

对于列组(Col1)的列组柱状频率 如下

区间最小值 区间最大值 列组柱状频率
区间 1 1 2 0.25
区间 2 2 3 0.5
区间 3 3 4 0.25

在了解了 DB2 数据库常见的统计信息之后,开始更进一步的讨论不同统计信息之间的一致性问题。

需要说明的是,下文示例中用到的表,除特别说明外,均为 TPC-D 标准中定义的表,这样有助于读者更好的理解 SQL 语句本身。对于 TPC-D 标准的介绍,参见文章最后的参考资源。

数据库架构决定的统计信息一致性

数据库由不同的逻辑对象组成, 例如表,列组,列,索引等等,这些对象之间存在着逻辑关系,即:表包含了列; 索引是建立在表上; 索引键组成索引;索引键引用了表的列等等。 这些都是数据库结构决定的,这些结构也决定了统计信息的关系。由数据库架构决定的统计信息一致性是最简单而又最容易检验的。下面介绍一些常见的不一致的情况及解决办法。

1. 表基数、列组基数和列基数的结构一致性

数据库结构上表是由列组构成的,而列组是由列构成的。数据库中的表包含了列组, 是列组的超集。表基数大于等于该表的任意列组基数。 一个列组的基数大于组成该列的列基数。 若一个表 Table1, 其中任意一个列组 ColGroup1,ColGroup1 包含的列为 (Col 1, Col 2, … Col n), 则有:

列组一致性

同理, 列组是由列组成的, 列组是列的超集。 若一个列组 ColGroup1 有 n 列组成则对于列组中的某一列 Colx 有:

列一致性

若表基数、列组基数和列基数之间存在不一致则会影响优化器选择访问路径。在实际的数据库中,由于收集统计信息的不完整或者不同步,这几种统计信息之间可能会出现不一致的情况。

这里用一个例子来说明这种不一致的情况。例如,用以下的 SQL 语句从目录表 SYSIBM. SYSTABLES 中查询出 CUSTOMER 表上这个表的列组基数:

SELECT NAME,CARDF FROM SYSIBM.SYSTABLES WHERE NAME = 'CUSTOMER'

查询返回结果如图 3 所示:


图 3. 查询结果
图 3. 查询结果

用如下语句可以得到所有列组的基数 :

SELECT COLGROUPCOLNO, CARDF FROM SYSIBM.SYSCOLDIST WHERE TBNAME = 'CUSTOMER'

查询返回结果如图 4 所示:


图 4. 查询结果
图 4. 查询结果

用如下语句可以得到所有列的基数 :

SELECT TBNAME, COLCARDF FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = 'CUSTOMER'

查询返回结果如图 5 所示:


图 5. 查询结果
图 5. 查询结果

可以看到表中一个列组的基数大于表的基数 (140>130), 这说明统计信息不一致。 同样, 一个列组的单个列基数大于整个列组基数 (150>140), 这显然也是不一致的。

对于这个例子可通过重新收集表、列组和列的统计信息来更正这些不一致的现象。

RUNSTATS TABLESPACE "DB4CUST"."TSCUST" TABLE("SYSADM"."CUSTOMER") SAMPLE 40 COLUMN("C_MKTSEGMENT","C_NATIONKEY") COLGROUP("C_MKTSEGMENT","C_NATIONKEY") SORTNUM 4 SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

此外,一个表可以包含多个列组,并且列组之间也可以有包含关系。 若两个列组, 其中第一个列组 ColGroup1 的全部列 (Col 1,Col 2, … Col n) 都出现在第二个列组 ColGroup2 中, 且 ColGroup2 列的数量多于 ColGroup1 (Col 1,Col 2, … Col n, Col n+1, … Col m),也就是 ColGroup2 是 ColGroup1 的超集,则 ColGroup2 基数大于 ColGroup1 基数:

超集一致性

这里用一个例子来说明这种不一致的情况。例如,用如下语句可以得到 CUSTOMER 表所有列组的基数 :

SELECT COLGROUPCOLNO, CARDF FROM SYSIBM.SYSCOLDIST WHERE TBNAME = 'CUSTOMER'

查询返回结果如图 6 所示:


图 6. 查询结果
图 6. 查询结果

可以看到第一行中的列组 ColGroup1 列的数量多于第二行列组 ColGroup2 的列的数量,并且包含了 ColGroup2 的全部列,而 ColGroup1 基数却小于 ColGroup2, 这是统计信息不一致的情况。可以通过执行如下的 RUNSTATS 命令来消除这种不一致:

RUNSTATS TABLESPACE "DB4CUST"."TSCUST" TABLE("SYSADM"."CUSTOMER") SAMPLE 40 COLUMN("C_MKTSEGMENT","C_NATIONKEY") COLGROUP("C_MKTSEGMENT","C_NATIONKEY") SORTNUM 4 SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

2. 表基数,索引全键基数和索引第一键基数的结构一致性

索引是建立在表上, 组成索引的键来自于表的列, 所以组成索引的一个或多个键就是表的一个列组。 根据前面规则,可以得出表基数大于等于索引全键基数。 同理索引的第一个键,是索引对应列组中的第一列。 根据前面的规则,可以得出索引全键基数大于等于索引第一键基数。若一个表 Table1 上面建立了索引 Index1,Index1 可以有一个或多个索引键, Index1 中的某个索引键引用的表的列为 Colx, 则对于 Table1 有:

表基数一致性

这里用一个例子来说明这种不一致的情况。以 CUSTOMER 表为例,它定义了如下的一个索引 PXC@CKNKMS:

INDEX PXC@CKNKMS ON CUSTOMER(C_CUSTKEY,C_NATIONKEY,C_MKTSEGMENT)

可以用如下语句查询一个索引的全键基数和索引第一键基数:

SELECT NAME, FULLKEYCARDF, FIRSTKEYCARDF FROM SYSIBM.SYSINDEXES WHERE TBNAME = 'CUSTOMER' AND NAME= 'PXC@CKNKMS'

查询返回结果如图 7 所示:


图 7. 查询结果
图 7. 查询结果

然后用以下的 SQL 语句从目录表 SYSIBM. SYSTABLES 中查询出 CUSTOMER 这个表的基数:

SELECT NAME,CARDF FROM SYSIBM.SYSTABLES WHERE NAME = 'CUSTOMER'

查询返回结果如图 8 所示:


图 8. 查询结果
图 8. 查询结果

在这个例子中,表基数小于索引全键基数 (130<150),这是不一致的统计信息。可以执行如下的 RUNSTATS 命令来重新收集统计信息消除这种不一致:

RUNSTATS TABLESPACE "DB4CUST"."TSCUST" INDEX("PXO@OKODCKSPO") SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

数据库对象在不同统计信息中的映射一致性

数据库当中包括很多不同类型的对象,比如表,索引,行,和列等等。DB2 对于这些对象都有各自不同的统计信息,对于存在数据库中的数据来说,可以认为这些统计信息实际上是数据在 DB2 中不同维度上的映射。由于实际存在的数据是唯一确定的,所以在正常情况下,这些数据在不同维度上的映射之间(即统计信息之间)是存在一致性关系的。但是由于统计信息更新不及时不准确,某些相关的统计信息就有可能存在不一致的情况。下面就列举几个可能出现的这种不一致的例子,并给出对应的解决方法。

1. 单个列统计信息的映射一致性

单个列的统计信息是 DB2 查询中最常被用到的一类统计信息,例如对于 T1.C1=5 这样的查询条件,DB2 就会用到 T1.C1 的基数(cardinality)等统计信息来计算 T1.C1=5 的筛选率(filter factor)。而单个列的数据情况会映射到 DB2 不同类型的统计信息中去,如下表 1 所示:


表 1. 单个列在 DB2 统计信息中的映射
统计信息类型 描述 对应的 DB2 目录表
Column cardinality 单个列的基数 SYSIBM.SYSCOLUMNS.COLCARDF
1 Column Group cardinality 只包含 1 个列的列组的基数 SYSIBM.SYSCOLDIST.CARDF
Index First Key cardinality 索引中第一个键值列的基数 SYSIBM.SYSINDEXES.FIRSTKEYCARDF
1-key Index Full Key cardinality 只包含 1 个键值的索引的全键基数 SYSIBM.SYSINDEXES.FULLKEYCARDF

如果上述这四种统计信息中所涉及的都是同一个表中的同一个列(对于 Index First Key cardinality 来说,是指该列是索引中的第一个键),那么这四种统计信息之间应该是一致的,即:

一致的结果

但是在实际的数据库中,由于收集统计信息的不完整或者不同步,这几种统计信息之间可能会不一致。

这里用一个例子来说明这种不一致的情况。例如,用以下的 SQL 语句从 DB2 目录表中查询出 SYSADM.ORDER 表上每个列的基数:

SELECT NAME,COLCARDF FROM SYSIBM.SYSCOLUMNS WHERE TBCREATOR ='SYSADM' AND TBNAME='ORDER'

结果如图 9 所示:


图 9. 查询结果
图 9. 查询结果

接着用如下的 SQL 语句从 DB2 目录表中查询出 SYSADM.ORDER 表上只包含 1 个列的列组的基数:

SELECT TBNAME,NAME, NUMCOLUMNS, COLGROUPCOLNO, CARDF FROM SYSIBM.SYSCOLDIST WHERE TBOWNER ='SYSADM' AND TBNAME='ORDER' AND NUMCOLUMNS=1;

查询结果如图 10 所示:


图 10. 查询结果
图 10. 查询结果

从以上两个查询结果可以看出,SYSADM.ORDER.O_ORDERKEY 这个列在 SYSIBM. SYSCOLUMNS 和 SYSIBM.SYSCOLDIST 这两个不同的统计信息表当中存在不一样的基数(45000000 与 50000),这就违反了之前提到的一致性。

针对以上情况,可以执行如下的 RUNSTATS 命令来消除这种不一致:

RUNSTATS TABLESPACE "DB4ORDER"."TSORDER" TABLE("SYSADM"." ORDER ") COLUMN("O_ORDERKEY") COLGROUP("O_ORDERKEY") SORTDEVT SYSDA SORTNUM 4 SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

针对后两种索引相关的统计信息,需要考虑数据库中索引的结构。TPC-D 标准中 ORDER 表上定义了如下的索引:

INDEX PXO@OKODCKSPOP ON ORDER (O_ORDERKEY,O_ORDERDATE,O_CUSTKEY,O_SHIPPRIORITY,O_ORDERPRIORITY ) ;

注意这里 O_ORDERKEY 是该索引上的第一个键,即所谓的 First Key。它的基数可以这样从统计信息中获得:

SELECT FIRSTKEYCARDF FROM SYSIBM.SYSINDEXES WHERE NAME= 'PXO@OKODCKSPOP'

除此之外,在这里再假定存在一个特殊的索引:

INDEX IXDEMO ON ORDER (O_ORDERKEY)

这里 O_ORDERKEY 是该索引上的唯一一个键,即所谓的 Full Key, 它的基数可以这样从统计信息中获得:

SELECT FULLKEYCARDF FROM SYSIBM.SYSINDEXES WHERE NAME= ' IXDEMO'

以上得到的两个基数应该是相当的,如果存在不一致的情况,则说明统计信息存在冲突不一致。针对这种情况,可以执行如下的 RUNSTATS 命令来消除这种不一致:

RUNSTATS INDEX("PXO@OKODCKSPO", "IXDEMO" ) SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

2. 列组统计信息的映射一致性

列组是指由同一个表中的若干个列构成的集合。对于这种数据对象,DB2 会在目录表 SYSIBM.SYSCOLDIST 中记录其对应的统计信息。另一方面,DB2 会在目录表 SYSIBM.SYSINDEXES 中记录索引的统计信息。如果列组中的这些列恰好也是某个索引的列,那么这个列组的统计信息也会被映射到 SYSIBM.SYSINDEXES 中去,即 DB2 这两个目录表中对于列组的统计信息应该是一致的。但是在实际的数据库中,由于收集统计信息的不完整或者不同步,这两种统计信息之间也可能会出现不一致的情况。

这里用一个例子来说明这种不一致的情况。

例如,在 TPC-D 标准中的 CUSTOMER 表,它定义了如下的一个索引 PXC@CKNKMS:

INDEX PXC@CKNKMS ON CUSTOMER(C_CUSTKEY,C_NATIONKEY,C_MKTSEGMENT)

在目录表 SYSIBM.SYSINDEXES 中就会存在该索引的统计信息。用以下的 SQL 语句可以从目录表中查询出索引 PXC@CKNKMS 的全键基数:

SELECT NAME, FULLKEYCARDF FROM SYSIBM.SYSINDEXES WHERE NAME='PXC@CKNKMS'

查询结果如图 11 所示:


图 11. 查询结果
图 11. 查询结果

接着用以下的 SQL 语句从目录表 SYSIBM.SYSCOLDIST 中查询出 CUSTOMER 表上这个列组(C_CUSTKEY,C_NATIONKEY,C_MKTSEGMENT)的列组基数:

SELECT TBNAME,NAME, NUMCOLUMNS, COLGROUPCOLNO, CARDF FROM SYSIBM.SYSCOLDIST WHERE TBOWNER ='SYSADM' AND TBNAME='CUSTOMER' AND NUMCOLUMNS=3 AND COLGROUPCOLNO=X'000100040007' ;

这里需要对这个 SQL 语句做出一些说明:Numcolumns=3 表示列组中包括 3 个列,而 Colgroupcolno=X'000100040007'则指明了这三个列是 CUSTOMER 表上第 1,4,7 列,分别对应(C_CUSTKEY,C_NATIONKEY,C_MKTSEGMENT)。查询结果如图 12 所示:


图 12. 查询结果
图 12. 查询结果

从以上两个查询结果可以看出,(C_CUSTKEY,C_NATIONKEY,C_MKTSEGMENT)这个列组在 SYSIBM. SYSINDEXES 和 SYSIBM.SYSCOLDIST 这两个不同的统计信息表当中存在不一样的基数(4500000 与 3600000),这就违反了上面提到的一致性。

针对以上情况,可以执行如下的 RUNSTATS 命令来消除这种不一致:

RUNSTATS TABLESPACE "DB4CUST"."TSCUST" TABLE("SYSADM"." CUSTOMER") COLUMN("C_CUSTKEY") COLGROUP(C_CUSTKEY,C_NATIONKEY,C_MKTSEGMENT) INDEX("PXC@CKNKMS " ) SORTDEVT SYSDA SORTNUM 4 SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

需要特别指出的是,在这里列组统计信息的映射一致性是与列组当中列的顺序无关的,也与索引当中键的顺序无关。即在上面的例子当中,如果列组是(C_NATIONKEY, C_CUSTKEY ,C_MKTSEGMENT),那么它的列组基数同样应该与索引 PXC@CKNKMS 的全键基数相一致;反之亦然。

3. 表统计信息的映射一致性

一个表当中的所有行(row)构成了该表当中包含的全部数据,这些数据行也有其对应的统计信息,被记录在 DB2 目录表 SYSIBM. SYSTABLES 中。另一方面,如果该表上定义了唯一索引(unique index),即这些数据行在表中都是唯一的(表的主键 Primary-key 就是一种常见的 unique index),那么全部这些数据行的统计信息也会被映射到 SYSIBM.SYSINDEXES 中去,即 DB2 这两个目录表中对于列组的统计信息应该是一致的。但是在实际的数据库中,由于收集统计信息的不完整或者不同步,这两种统计信息之间也可能会出现不一致的情况。

这里用一个例子来说明这种不一致的情况。

例如,TPC-D 标准中的 CUSTOMER 表,在 DB2 目录表 SYSIBM. SYSTABLES 中会记录其全部数据的统计信息,即表基数,用以下的 SQL 语句可以从目录表中查询出这个基数:

SELECT NAME, CARDF FROM SYSIBM. SYSTABLES WHERE CREATOR='SYSADM' AND NAME='CUSTOMER'

查询结果如图 13 所示:


图 13. 查询结果
图 13. 查询结果

此外,CUSTOMER 表还定义了如下的一个唯一索引 :

UNIQUE INDEX UXC@NKCK CUSTOMER(C_NATIONKEY, C_CUSTKEY);

在目录表 SYSIBM.SYSINDEXES 中就会存在该索引的统计信息。用以下的 SQL 语句可以从目录表中查询出索引 UXC@NKCK 的全键基数:

SELECT NAME, FULLKEYCARDF FROM SYSIBM.SYSINDEXES WHERE NAME='UXC@NKCK'

查询结果如图 14 所示:


图 14. 查询结果
图 14. 查询结果

从以上两个查询结果可以看出,CUSTOMER 表中的数据在 SYSIBM.SYSTABLES 和 SYSIBM. SYSINDEXES 这两个不同的统计信息表当中存在不一样的基数(4500000 与 3500000),这就违反了上面提到的一致性。

针对以上情况,可以执行如下的 RUNSTATS 命令来消除这种不一致:

RUNSTATS TABLESPACE "DB4CUST"."TSCUST" INDEX("UXC@NKCK " ) SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

不同统计信息之间的代数一致性

除了上面说的两大类关系之外,在部分 DB2 的统计信息之间,还存在另一种类型的联系。这是由于许多不同类型的统计信息之间是存在联系的,例如列组是由列组成的,那么列组的基数和列的基数之间也就存在联系。这种类型的联系常常可以表达为一些数学上的不等式。这种类型的不一致比较隐蔽而不容易发现,但是这种不一致同样可能误导优化器而导致不确定的结果。

1. 列的基数与它们所组成的列组的基数

由于基数是表征列组互不相同值的数量的值,因此一个列组的最大基数为其所包含所有值均不相同,此时这个列组的基数为其所有列的基数的积;在其他情况下,列组的基数都将小于列的基数的积。据此能得到一个代数关系,

列组基数

实际上,列组的基数不仅小于所有列的基数的乘积,它还小于有子集关系的列组与列组之间或与列之间的积。这个关系可以表示为如下公式:

列组积

这里用一个例子来说明这种不一致的情况。

用以下的查询语句,可以得到一个表上所有的列的基数:

SELECT NAME, COLNO, COLCARDF FROM SYSIBM.SYSCOLUMNS WHERE TBCREATOR = 'SYSADM' AND TBNAME = 'CUSTOMER';

查询返回结果如图 15 所示:


图 15. 查询结果
图 15. 查询结果

用如下语句可以得到所有列组的基数 :

SELECT COLGROUPCOLNO, CARDF FROM SYSIBM.SYSCOLDIST WHERE TBOWNER = 'SYSADM' AND TBNAME = 'CUSTOMER'

查询返回结果如图 16 所示:


图 16. 查询结果
图 16. 查询结果

注意图 14 中的 C_NATIONKEY 和 C_MKTSEGMENT 两个列及他们组成的列组的统计信息数据,经过计算可以发现 25.0*5.0<140.0。这个违反了以上的公式,所以存在不一致。

针对以上情况,重新收集所有涉及到的列和列组的统计信息。

RUNSTATS TABLESPACE "DB4CUST"."TSCUST" TABLE("SYSADM"."CUSTOMER") SAMPLE 40 COLUMN("C_MKTSEGMENT","C_NATIONKEY") COLGROUP("C_MKTSEGMENT","C_NATIONKEY") SORTNUM 4 SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

2. 列组的频率与其基数

列组的频率是表示列组中某个值出现频率的统计信息,其值的计算方法是(该值出现次数)/(这个列组的基数),这个值必定是一个小于 1 的浮点数。这样就建立了一个列组的频率信息和基数信息的联系,可以表达为如下公式:

列组的频率信息和基数信息的联系

一般来说一个列组里面会有多个频率信息数据,只用比较最大的一个频率信息即可。虽然这两个统计信息是位于同一个表中,但是他们并不一定是同时收集的。

这里用一个例子来说明这种不一致的情况。可以用以下的查询语句来快速找到存在这种不一致现象的列组。

SELECT TBOWNER, TBNAME, NAME, COLGROUPCOLNO, FREQUENCYF, CARDF FROM SYSIBM.SYSCOLDIST WHERE FREQUENCYF * CARDF > 1;

查询返回结果如图 17 所示:


图 17. 查询结果
图 17. 查询结果

从返回结果计算可知,0.16*50=8,说明在这个列组上,统计信息存在不一致。

针对这个不一致问题,需要如下的 RUNSTATS 来重新收集列组频率信息,相应的基数信息会同时自动更新,用户无需再次收集:

RUNSTATS TABLESPACE "DB4PART"."TSPART" TABLE("SYSADM"."PART") COLUMN("P_ MFGR") COLGROUP("P_MFGR") FREQVAL COUNT 3 SORTNUM 4 SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

这里 FREQVAL COUNT 设定为多少,需要根据实际情况来指定。

3. 列组的柱状频率与其基数

列组柱状频率中包含了每个区间基数信息。显然,所有区间基数的和应该等于这个列组的基数。收集过列组的柱状频率之后,在系统编目表中会同时存在列组的基数信息和每个区间的基数。这两个统计信息由 COLDIST 表中的 TYPE 列来区分:TYPE 为 H 的是每个区间的统计信息,而 TYPE 为 C 的则是整个列组的统计信息。

这里用一个例子来说明这种不一致的情况。可以用如下查询语句来检查是否存在这种不一致:

SELECT A.TBOWNER, A.TBNAME, A.NAME, A.COLGROUPCOLNO, A.CARDF, B.SCARDF FROM SYSIBM.SYSCOLDIST AS A, ( SELECT TBOWNER, TBNAME, COLGROUPCOLNO, SUM(CARDF) AS SCARDF FROM SYSIBM.SYSCOLDIST WHERE TYPE = 'H' GROUP BY TBOWNER, TBNAME, COLGROUPCOLNO) AS B WHERE A.COLGROUPCOLNO = B.COLGROUPCOLNO AND A.TBOWNER = B.TBOWNER AND A.TBNAME = B.TBNAME AND A.TYPE = 'C' AND A.CARDF <> B.SCARDF;

在的测试系统上执行这条查询语句之后,得到的结果如图 18 所示


图 18. 查询结果
图 18. 查询结果

区间基数的和为 3000500,而列组的基数仅为 3000000,两者不相等说明此处存在不一致。

为了解决这种不一致,需要同时 RUNSTATS 收集这个列组的基数和柱状频率,最简单的方法就是重新收集一次这个列组的柱状频率,而基数会随着这个统计信息的更新而自动更新:

RUNSTATS TABLESPACE "DB4ORDER"."TSORDER" TABLE("SYSADM"."ORDER") COLUMN("O_CUSTKEY") COLGROUP("O_CUSTKEY") HISTOGRAM SHRLEVEL CHANGE REPORT YES UPDATE ALL HISTORY NONE

补充说明

1. 本文所讨论的统计信息之间的一致性关系,是理论意义上的一致性关系。在实际的数据库系统之中,一定程度之内的某些不一致是可以容忍的。比如有一个索引 IX(C1,C2,C3)和一个列组(C1,C2,C3),根据本文提到的一致性规则,它们的基数信息必定是严格相等的。但是在实际系统之中,由于各种原因,两者之间可能存在微小的差距。这种微小的差异一般来讲对数据库的优化器不会有太大的影响,因此对待发现的统计信息不一致情况要灵活对待。比如对于两个都是刚刚收集过的统计信息,不一定需要再次 RUNSTATS 收集;再比如两者差距不到 1% 的统计信息(针对相等的情况),也可以忽略这种不一致性。

2. 有经验的数据库管理员会知道,在执行 RUNSTATS 命令时,DB2 数据库会顺便更新一些用户没有显示指明但是可以同时得到的统计信息。最典型的一个例子就是在收集列组的统计信息时——假设是(C1,C2,C3)—— DB2 会同时收集并更新有子集关系并且列顺序相同的几个列组的基数,在这个例子中就是(C1,C2)和(C1)。

3. 根据 RUNSTATS 的语法,假如在语句中没有显示的指明 COLUMN 子句,那么默认会收集目标表上所有列的统计信息,但这有时候这并不是用户所要的结果。在本文的例子中为了避免这种情况,我们根据需要在 COLUMN 子句刻意的加上一些无关的列以防止收集全部列的统计信息。

总结

本文介绍了在 DB2 数据库的统计信息中可能出现的若干种不一致的情况,并给出相应的最佳实践来消除这种不一致。统计信息对于 DB2 数据库的查询性能非常重要,如果执行完整 RUNSTATS 操作的代价太高不可接受,那么此时尽可能消除统计信息中的不一致,就可以有效的避免在 DB2 查询时由此带来的性能问题,从而减少总的成本并提高你的业务的投资回报。


如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

0

添加新评论1 条评论

Birdflying1005Birdflying1005软件工程师DONOTMENTION
2011-06-28 18:21
连作者信息都不转, 好像不太好吧。 ~~~
Ctrl+Enter 发表

作者其他文章

相关问题

X社区推广