在 DB2 优化器中使用分布统计信息
2008-09-17 16:31:19 来源:WEB开发网简介
为了执行查询或 DML 语句(INSERT、UPDATE、DELETE),DB2 必须创建一个访问计划(access plan)。访问计划定义按什么顺序访问表,使用哪些索引,以及用何种连接(join)方法来关联数据。好的访问计划对于 SQL 语句的快速执行至关重要。DB2 优化器可以创建访问计划。这是一种基于成本的优化器,这意味着它是根据表和索引的相关统计信息来作出决策的。DB2 在生成统计信息时,不但能提供基本统计信息,还允许创建所谓的分布统计信息。不但数据库管理员要理解分布统计信息,而且应用程序开发人员也要理解分布统计信息。应用程序开发人员必须小心谨慎,因为在某些情况下分布统计信息对于 DB2 优化器来说非常重要。主变量或参数标记(在 Java 中为 java.sql.PreparedStatement)的使用可能会造成阻碍,使优化器无法最大限度地利用分布统计信息。本文解释什么是分布统计信息、分布统计信息在哪些情况下尤为重要,以及应用程序开发人员应该考虑些什么,才能使 DB2 优化器创建有效的访问计划。
基本统计信息和分布统计信息
在研究分布统计信息之前,我们先来看看基本统计信息,只要执行 RUNSTATS 即可收集这些表的相关统计信息。
表的相关统计信息:
当前使用的页面数
包含记录行的页面数
溢出的行数
表中的行数(基数)
对于 MDC 表,还有包含数据的块(block)数
表中各列的相关统计信息:
列的基数
列的平均长度
列中第二大的值
列中第二小的值
列中 NULL 值的个数
通常,执行 RUNSTATS 时,不但可以收集到关于表的统计信息,而且还可以收集到相应的索引的相关统计信息。要了解为索引而收集的统计信息,请参阅 DB2 Administration Guide: Performance - Statistical information that is collected。
观察一个表的基本统计信息,您可以看到,DB2 优化器知道一个表由多少行组成(表的基数),以及一个列包含多少个不同的值(列的基数)。但是,还有一些信息是基本统计信息无法提供的。例如,基本统计信息不能告诉优化器一个列中某些值出现的频率。假设表 TABLE_X 有大约 1,000,000 行,在该表上执行这样一条查询:
SELECT * FROM TABLE_X WHERE COLUMN_Y = 'VALUE_Z'
难道 DB2 优化器知道 TABLE_X 中有多少行满足条件 COLUMN_Y = 'VALUE_Z' 不重要吗?换句话说:知道这个查询将返回 1 行、100 行、1000 行还是 10000 行有什么不好呢?
实际上,通过基本统计信息,DB2 优化器只能估计 'VALUE_Z' 在 COLUMN_Y 中出现的频率。在这种情况下,优化器认为所有值在 COLUMN_Y 中是平均分布的,这意味着它认为所有的值都有相同的出现频率。如果事实碰巧如此,这样估计并无大碍。但是,如果有些值比其他值出现得更频繁一些(例如,如果 'VALUE_Z' 出现 900,000 次,即占所有行的 90%),那么优化器不能考虑到这一点,因而生成的访问计划就不是最优的。而分布统计信息可以填补这一空白。分布统计信息可以提供关于数据出现频率及其分布情况的信息,如果数据库中存储了很多重复值,并且数据在表中并非平均分布的时候,分布统计信息对于基本统计信息是一个重要的补充。
分布统计信息的类型 —— 频率(frequency)统计信息和分位数(quantile)统计信息
有两种不同类型的分布统计信息 —— 频率统计信息和分位数统计信息。让我们通过一个示例表来研究一下这两种不同类型的分布统计信息。
示例表 “CARS” 表示一家汽车制造商,对于生产的每一辆汽车,在表中都有相应的一行。每辆汽车可以由它的 ID 来标识,因此 “ID” 是表 “CARS” 的主键(PK)。此外,表中有一个 “STATE” 列,表明汽车当前处在制造流程中的哪一步。一辆汽车的制造流程从第 1 步开始,然后是第 2 步、第 3 步,...、第 49 步、第 50 步、第 51 步、...、第 98 步、第 99 步,一直到第 100 步 —— 第 100 步意味着汽车已经完工了。已完工的汽车所对应的行仍然保留在表中,后续流程(例如投诉管理、质量保证等)仍要用到这些行。汽车制造商生产 10 种不同型号(“TYPE” 列)的汽车。为了简化问题,在这个示例表中,各种汽车型号命名为 A、B、C、D、...、J。除主键索引(在 “ID” 列上)之外,“STATE” 列上也有一个索引(“I_STATE”),在 “TYPE” 列上还有一个索引(“I_TYPE”)。实际上,一个 “CARS” 表包含的列远不止 “ID”、“STATE” 和 “TYPE”。为简单起见,示例表中没有出现其他这些列。
频率统计信息
假设表 CARS 现在有大约 1,000,000 条记录,不同的型号在表中出现的频率如下所示:
表 1. 表 CARS 中 TYPE 列的频率统计信息
TYPE | COUNT(TYPE) |
A | 506135 |
B | 301985 |
C | 104105 |
D | 52492 |
E | 19584 |
F | 10123 |
G | 4876 |
H | 4589 |
I | 4403 |
J | 3727 |
型号为 A 的汽车最受购买者的青睐,因此生产的汽车中大约有 50% 是这种型号。型号 B 和型号 C 仅次于型号 A ,分别占所有汽车的 30% 和 10%。其他所有型号加在一起仅占 10%。
上面的表显示了 “TYPE” 列的频率统计信息。通过基本统计信息,DB2 优化器只能了解到该表包含 1,000,000 行(表的基数)和 10 种不同的值(型号),即 A 到 J。如果没有分布统计信息,优化器会认为每种值以相同的频率出现,大约都是出现 100,000 次。而一旦生成了关于 “TYPE” 列的分布统计信息,优化器即可了解每种型号真正的出现频率。因此,优化器清楚各种已有型号出现的不同频率。
优化器使用频率统计信息来计算用于检查相等或不等的谓词的过滤因子。例如:
SELECT * FROM CARS WHERE TYPE = 'H'
分位数统计信息
与频率统计信息不同,分位数统计信息与不同值的出现频率无关,而与一个表中有多少行小于或大于某个值(或者有多少行介于两个值之间)相关。分位数统计信息提供关于一个列中的值是否聚合的信息。为获得这样的信息,DB2 假定列中的值是按升序排列的,并根据正则行间隔确定相应的值。
我们来看看表 CARS 中的 “STATE” 列,该列按升序排列。根据正则行间隔,即可确定 “STATE” 的对应值。
表 2. CARS 表中 STATE 列的分位数统计信息
COUNT(row) | STATE ASC |
5479 | 1 |
54948 | 10 |
109990 | 21 |
159885 | 31 |
215050 | 42 |
265251 | 52 |
320167 | 63 |
370057 | 73 |
424872 | 84 |
475087 | 94 |
504298 | 100 |
... | 100 |
1012019 | 100 |
由于已完工的汽车仍然没有从表中删除,因此状态为 100 (=完工)的汽车数量比所有处于其他状态的汽车总和还多。已完工的汽车占表中所有记录的 50%。
注意: 在实际情况下,已完工的汽车数量甚至还要更多(例如超过 99%)。在后文中的具体例子中可看到这种情况。
上表显示了 “STATE” 列的分位数统计信息。有了这种关于有多少行分别小于和大于确定值的信息,优化器即可计算出用于测试小于(小于等于)、大于(大于等于)或介于两值之间的谓词的过滤因子。例如:
SELECT * FROM CARS WHERE STATE < 100
SELECT * FROM CARS WHERE STATE BETWEEN 50 AND 70
根据已有的分位数统计信息计算出来的过滤因子不是很精确,但即使只收集 20 个值,其误差仍然低于 5%。
DB2 优化器对分布统计信息的使用 —— 示例
我们来看一个完整的示例,在此例中,DB2 优化器可以使用分布统计信息来更合理地估计过滤因子,以便生成更好的访问计划。
这个示例查询从已经定义好的 CARS 表中读取数据。对于表 CARS 中的汽车数据,有以下假设:
该表的基数为 1,000,000,也就是说该表包含 1,000,000 行。
表中 99.9% 的汽车是已经完工(“STATE” 列 = 100)的,这些汽车的信息相关必须保留,以用于后续处理(投述管理、质量保证等)。剩下的 1,000 辆汽车目前还处在制造流程中。
在该表中,制造商提供的从 A 到 J 的 10 种不同汽车型号(“TYPE” 列)几乎以相同的频率出现。
注意: 脚本 create_table_cars.sql 用于创建前述 CARS 表,包括 “STATE” 列和 “TYPE” 列上的索引,该脚本可以通过本文 下载。这个示例表是在 DB2 SAMPLE 数据库中(命令 db2sampl),使用 DBM CFG 和 DB CFG 的默认设置创建的。
示例查询选择型号为 A 且正处在制造流程中、尚未完工的所有汽车:
SELECT * FROM CARS WHERE STATE < 100 AND TYPE = 'A'
首先来分析一下,在没有分布统计信息,而只有 CARS 表的基本统计信息及其索引的情况下,优化器选择的访问计划是怎样的。
图 1. 没有分布统计信息时示例查询的访问计划
由于优化器不知道 STATE 值不是均匀分布的,因此它决定使用索引 I_TYPE。使用这个索引会带来较高的成本,因为在访问 CARS 表之前,需要从索引中读取大约 100,000 个 RID(记录 ID)。此外,对于查询返回行数的假设也是错误的。因为优化器认为所有制造步骤(从 1 到 100)都有相同的频率,所以它无法预知谓词 STATE < 100 将过滤掉大量有价值的行。但是您知道,事实是在所有 1,000,000 辆汽车中,只有 1,000 辆汽车正处在生成流程中。
在没有分布统计信息的情况下,执行该查询时,动态 SQL 的一个快照返回以下监视器值(假定所需的监视器开关已激活):
清单 1. 在没有分布统计信息时示例查询的快照
Number of executions = 1
Number of compilations = 1
Worst preparation time (ms) = 9
Best preparation time (ms) = 9
Internal rows deleted = 0
Internal rows inserted = 0
Rows read = 99336
Internal rows updated = 0
Rows written = 0
Statement sorts = 0
Statement sort overflows = 0
Total sort time = 0
Buffer pool data logical reads = 8701
Buffer pool data physical reads = 8131
Buffer pool temporary data logical reads = 0
Buffer pool temporary data physical reads = 0
Buffer pool index logical reads = 165
Buffer pool index physical reads = 155
Buffer pool temporary index logical reads = 0
Buffer pool temporary index physical reads = 0
Total execution time (sec.ms) = 0.530903
Total user cpu time (sec.ms) = 0.280403
Total system cpu time (sec.ms) = 0.230332
Statement text = SELECT ID, TYPE, STATE FROM SAMPLE.CARS
WHERE STATE < 100 AND TYPE = 'A'
在此,我们不会进一步分析这些值,但是请记住它们,以便与有分布统计信息时相同查询得到的监视器值相比较。
接下来,为 CARS 表生成分布统计信息,并再次执行查询。此时,优化器选择了以下访问计划:
图 2. 有分布统计信息时的访问计划
这个访问计划的成本明显低于没有分布统计信息时的成本:前者为 203.809,而后者为 3242.63。这是因为优化器现在知道,谓词 STATE < 100 有一个较高的过滤因子,因而只会返回大约 1,000 辆正处在生产流程中尚未完工的汽车。因此,在这种情况下,CARS 表不是使用索引 I_TYPE 来访问的,而是使用索引 I_STATE 来访问的。此外,现在可以正确地估计结果集中的总行数。现有 1,000 辆汽车尚未完工,不同的型号出现频率相同。故结果集中包含大约 100 行。
有分布统计信息时的访问计划要优于没有分布统计信息时的访问计划。但是,这是否会影响查询的执行时间?清单 2 包含相应的监视器数据快照:
清单 2. 有分布统计信息时的示例查询快照
Number of executions = 1
Number of compilations = 1
Worst preparation time (ms) = 9
Best preparation time (ms) = 9
Internal rows deleted = 0
Internal rows inserted = 0
Rows read = 1000
Internal rows updated = 0
Rows written = 0
Statement sorts = 1
Statement sort overflows = 0
Total sort time = 5
Buffer pool data logical reads = 11
Buffer pool data physical reads = 10
Buffer pool temporary data logical reads = 0
Buffer pool temporary data physical reads = 0
Buffer pool index logical reads = 12
Buffer pool index physical reads = 9
Buffer pool temporary index logical reads = 0
Buffer pool temporary index physical reads = 0
Total execution time (sec.ms) = 0.014597
Total user cpu time (sec.ms) = 0.000000
Total system cpu time (sec.ms) = 0.010014
Statement text = SELECT ID, TYPE, STATE FROM SAMPLE.CARS
WHERE STATE < 100 AND TYPE = 'A'
表 3 比较了有分布统计信息和没有分布统计信息这两种不同情况下的快照监视器值:
表 3. 比较快照监视器值
快照值 | 无分布统计信息 | 有分布统计信息 |
Rows read | 99,336 | 1,000 |
Buffer pool data logical reads | 8,701 | 11 |
Buffer pool index logical reads | 165 | 12 |
Total execution time (sec.ms) | 0.530903 | 0.014597 |
您可以看到,有分布统计信息的情况下,DB2 执行查询时需要计算的行数更少。这对于 CPU 成本和 I/O 成本都有积极的影响。最重要的是总执行成本,因为总执行成本关系到应用程序的响应时间。在具有分布统计信息的情况下,这个时间是 0.014597 秒,而在没有分布统计信息的情况下,这个时间是 0.530903,相差 36 倍之多。
在我们的示例中,两种情况下的执行时间分别为 0.014597 秒和 0.530903 秒,这个差距还不够明显,因为这两个值只是次秒级的。然而,这样的差距不应被忽略。如果要执行更复杂的查询,或者要连续执行多个查询,那么执行时间的差距就不是次秒级的,而是以秒甚至分钟来计算的。
分布统计信息的生成
如前所述,在使用 RUNSTATS 命令生成统计信息时,并不是 总会收集分布统计信息。这是有意义的,因为仅在存在很多重复值或者数据分布不均匀的情况下,分布统计信息才重要。而在其他情况下,分布统计信息并不能带来多大的好处。
下面的 RUNSTATS 命令只收集 CARS 表(在模式 SAMPLE 中)和相应索引的基本统计信息:
RUNSTATS ON TABLE SAMPLE.CARS AND INDEXES ALL
此外,如果需要收集 CARS 表中所有列的分布统计信息(频率统计信息和分位数统计信息),那么可以执行以下命令:
RUNSTATS ON TABLE SAMPLE.CARS WITH DISTRIBUTION AND INDEXES ALL
生成分布统计信息意味着给 DB2 带来额外的、可观的开销,从而影响 RUNSTATS 命令的执行时间。所以,应该只为那些需要分布统计信息的列生成分布统计信息。
RUNSTATS ON TABLE SAMPLE.CARS WITH DISTRIBUTION ON COLUMNS (TYPE, STATE) AND INDEXES ALL
应为满足以下条件的列收集分布统计信息:
该列有很多重复的值(频率统计信息),或者该列的值分布不均匀,即它们在某些局部是聚合的(分位数统计信息)。
检查等于或不等于的谓词中使用到该列(频率统计信息),或者检查小于(小于等于)、大于(大于等于)或介于两个值之间的谓词中使用到该列(分位数统计信息)。
对于频率统计信息,重要的是定义好收集多少个值的重复数。如果为一个列中的所有值收集频率统计信息,那么成本就太高了。如果在执行 RUNSTATS 时没有显式定义数量,那么 DB2 将使用由数据库参数 NUM_FREQVALUES 提供的默认数量。由于 NUM_FREQVALUES 的默认值为 10,DB2 将为列中出现最频率的 10 个值收集重复次数,这里假定 RUNSTATS 是在没有显式定义数量,且数据库参数 NUM_FREQVALUES 没有被修改的情况下执行的。
与频率统计信息类似,也必须为分位数统计信息定义一个数量,以保证精确性。分位数统计信息定义应该使用多少 “度量值(measurement)“。列中的值被认为是按升序排列的,并且有一个正则的行间隔,相应的值是确定的。使用的度量值越多,优化器对于检查范围(<、>、<=、>=、BETWEEN)的谓词的过滤因子的估计就越准确。如果在执行 RUNSTATS 时没有明确指定一个值,那么 DB2 将使用由数据库参数 NUM_QUANTILES 提供的默认数量。NUM_QUANTILES 的默认值是 20,也就是说使用 20 个度量值。这已经是一个较好的值,因为它可以保证优化器在使用分位数统计信息的情况下对确定过滤因子的估计误差最大只有 5%。
如果数据库配置(DB CFG)不能提供 NUM_FREQVALUES 和 NUM_QUANTILES 的值,那么可以在执行 RUNSTATS 时显式定义:
RUNSTATS ON TABLE SAMPLE.CARS WITH DISTRIBUTION ON COLUMNS (TYPE NUM_FREQVALUES 10 NUM_QUANTILES 20, STATE NUM_FREQVALUES 15 NUM_QUANTILES 30) AND INDEXES ALL
如何检查是否存在分布统计信息
为检查某个表的分布统计信息是否已收集,可以查看分类视图 SYSCAT.COLDIST 的内容:
SELECT * FROM SYSCAT.COLDIST WHERE TABSCHEMA = 'SAMPLE' AND TABNAME = 'CARS'
视图 SYSCAT.COLDIST 结构如下:
表 4. SYSCAT.COLDIST 的结构
列名 | 数据类型 | 是否可以为空 | 描述 |
TABSCHEMA | VARCHAR(128) | 不可以 | 本条目对应的表的限定符 |
TABNAME | VARCHAR(128) | 不可以 | 本条目对应的表的名称 |
COLNAME | VARCHAR(128) | 不可以 | 本条目对应的列的名称 |
TYPE | CHAR(1) | 不可以 | F = Frequency(最大频率) Q = 分位数值 |
SEQNO | SMALLINT | 不可以 | 如果 TYPE = F,则该列中的 N 表示第 N 频繁的值 如果 TYPE = Q,那么该列中的 N 表示第 N 个分位数值 |
COLVALUE | VARCHAR(254) | 可以 | 数据值,其形式为字符字面值,或者一个 NULL 值 |
VALCOUNT | BIGINT | 不可以 | 如果 TYPE = F,那么 VALCOUNT 是 COLVALUE 出现在该列中的次数 如果 TYPE = Q,那么 VALCOUNT 是其值小于或等于 COLVALUE 的行的数量 |
DISTCOUNT | BIGINT | 可以 | 如果 TYPE = Q,那么该列记录小于或等于 COLVALUE 的不同值的数量(如果没有,则为 NULL) |
仅在收集了一个表中至少一个列的分布统计信息时,SYSCAT.COLDIST 才会包含关于该表的条目。如果在没有 WITH DISTRIBUTION 的情况下再次执行 RUNSTATS,那么 SYSCAT.COLDIST 中与该表对应的条目将被删除。
分布统计信息和参数标记/主变量
JDBC 提供了两种途径来执行动态 SQL,因而也提供了两种不同的接口:
java.sql.Statement
java.sql.PreparedStatement
PreparedStatement 是 Statement 的子接口,它允许使用参数标记(= 占位符;在其他编程语言中,此类占位符也被称为主变量) —— 而不是 Statement。在使用 PreparedStatement 的情况下,首先编译要执行的包括参数标记的 SQL 语句,然后将值绑定到参数标记,最后执行 SQL 语句。
下面的代码片段显示使用 Statement 与使用 PreparedStatement 的不同之处。
清单 3. 使用 JDBC Statement 接口执行动态 SQL
java.sql.Connection con = ...;
java.sql.Statement stmt1 = con.createStatement();
String insert1 = "INSERT INTO TABLE_X (COL_Y) VALUES ('ABC')";
stmt1.executeUpdate(insert1);
java.sql.Statement stmt2 = con.createStatement();
String insert2 = "INSERT INTO TABLE_X (COL_Y) VALUES ('XYZ')";
stmt2.executeUpdate(insert2);
con.commit();
清单 4. 使用 JDBC PreparedStatement 接口执行动态 SQL
java.sql.Connection con = ...;
String insert = "INSERT INTO TABLE_X (COL_Y) VALUES (?)";
java.sql.PreparedStatement pstmt = con.prepareStatement(insert);
pstmt.setString(1, "ABC");
pstmt.executeUpdate();
pstmt.setString(1, "XYZ");
pstmt.executeUpdate();
con.commit();
如果一条简单 SQL 语句执行多次(例如示例中的 INSERT 语句),那么使用 PreparedStatement 有优势,因为数据库只需编译该语句一次,即可多次执行,而不需要重复编译。假设在示例中需要插入数千行记录,那么使用 PreparedStatement 可以交付更短的执行时间,因为只需一次准备/编译时间,而不需要数千次。
然而,Java 开发人员使用 PreparedStatement 往往是因为需要在运行时提供值/过滤器标准时,使用这种接口编写的代码更为优雅。请看如下代码片段:
清单 5. 使用字符串串联填充过滤标准
int state = 100;
String type = "A";
...
java.sql.Connection con = ...;
java.sql.Statement stmt = con.createStatement();
String select = "SELECT * FROM CARS WHERE
STATE < " + state + " AND TYPE = '" + type + "'";
java.sql.ResultSet rs = stmt.executeQuery(select);
while (rs.next()) {
...
}
清单 6. 使用参数标记填充过滤标准
int state = 100;
String type = "A";
...
java.sql.Connection con = ...;
String select = "SELECT * FROM CARS WHERE STATE < ? AND TYPE = ?";
java.sql.PreparedStatement pstmt = con.prepareStatement(select);
pstmt.setInt(1, state);
pstmt.setString(2, type);
java.sql.ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
...
}
使用 PreparedStatement 的那种代码片段在编码方面更为优雅,因为在将 STATE 和 TYPE 的值置入 SQL 语句时,不需要进行字符串运算。但这种方法存在一个缺点,在绑定 WHERE 子句中谓词的值之前,需要编译 SELECT(创建访问计划)。为使优化器能够使用可用的分布统计信息,带有具体值的谓词极为重要。
让我们再次看看 CARS 表的查询示例,但这次使用参数标记,而不是具体值:
SELECT * FROM CARS WHERE STATE < ? AND TYPE = ?
CARS 表的分布统计信息仍然可用,因为在此期间这些统计信息也已经被收集。然而,使用参数标记时,将生成另一个访问计划。
图 3. 有参数标记和分布统计信息时示例查询的访问计划
虽然这个访问计划比使用具体值但没有分布统计信息情况下的访问计划好,但不如既使用具体值又有分布统计信息时的访问计划。在清单 7 中,动态 SQL 快照中的监视器输出也显示了这一点:
清单 7. 既使用参数标记又有分布统计信息时的示例查询的快照
Number of executions = 1
Number of compilations = 1
Worst preparation time (ms) = 10
Best preparation time (ms) = 10
Internal rows deleted = 0
Internal rows inserted = 0
Rows read = 99
Internal rows updated = 0
Rows written = 0
Statement sorts = 1
Statement sort overflows = 0
Total sort time = 0
Buffer pool data logical reads = 11
Buffer pool data physical reads = 10
Buffer pool temporary data logical reads = 0
Buffer pool temporary data physical reads = 0
Buffer pool index logical reads = 268
Buffer pool index physical reads = 125
Buffer pool temporary index logical reads = 0
Buffer pool temporary index physical reads = 0
Total execution time (sec.ms) = 0.081908
Total user cpu time (sec.ms) = 0.060086
Total system cpu time (sec.ms) = 0.010014
Statement text = SELECT ID, TYPE, STATE FROM SAMPLE.CARS
WHERE STATE < ? AND TYPE = ?
将这些值与之前收集到的值相比较,可以得到下表:
表 3. 继续比较快照监视器值
快照值 | 没有分布统计信息 | 有分布统计信息 | 既有分布统计信息又有参数标记 |
Rows read | 99,336 | 1,000 | 99 |
Buffer pool data logical reads | 8,701 | 11 | 11 |
Buffer pool index logical reads | 165 | 12 | 268 |
Total execution time (sec.ms) | 0.530903 | 0.014597 | 0.081908 |
在有参数标记时的 “Rows read” 值最小,但实际上这并不重要,因为此时 “Buffer pool index logical reads” 值较高。重要的是,与既有分布统计信息又有具体值的情况相比,“Total execution time” 更差一些,在本例中,其差距达到 8 倍。如前所述,在本例中,我们观察的是对少量数据执行的一个非常简单的查询。因此,执行时间上的差距并不是很明显。但是,如果要执行更复杂的查询,其执行时间以秒/分钟来计算,那么这个差距就会非常明显。
这个示例表明,如果将分布统计信息与参数标记一起使用,分布统计信息的作用就会受到限制。如果存在很多重复/不均匀的数据分布和复杂的查询,这可能会导致执行时间高于未使用参数标记情况下的执行时间。这并不意味着使用 PreparedStatement 总是很糟糕的。相反,如果要反复执行多次一条简单的语句,并且每次使用不同的值执行,建议使用 PreparedStatement,因为语句只需编译一次。在处理复杂的查询时,由于这些查询常常要向系统和 DSS(决策支持系统)报告,因此使用参数标记很可能得不偿失。这是因为分布统计信息得不到充分的利用,编译时间只应用了一次,因为这些查询最多也只是执行一次。
结束语
在本文中,您看到了 DB2 优化器对分布统计信息的使用。如果一个数据库包含很多重复的值,或者数据分布不均匀,那么,如果除了基本统计信息外,另外还生成了分布统计信息,DB2 优化器就可为 SQL 语句产生更好的访问计划。本文解释了不同类型的分布统计信息,展示如何使用 RUNSTATS 命令生成这些统计信息,并提供了一个示例场景,说明分布统计信息在某些情况下的重要性。您还研究了参数标记/主变量的使用会为 DB2 优化器对分布统计信息的考虑带来怎样的限制。
更多精彩
赞助商链接