使用实例化的查询表加速 DB2 UDB EEE 中的查询
|
|
级别: 初级
Alexander Kuznetsov
, 芝加哥,伊利诺斯州
2002 年 8 月 01 日
有时候,物理数据库结构中的一次简单更改会引人注目地改进查询性能。除了索引外,DB2 UDB 还为您提供了总结表(实例化的查询表),在许多情况下,这些表比索引更有效。本文将提供一些示例来演示使用总结表的优点。
简介
有时候,物理数据库结构中的一次简单更改会显著地改进查询性能。除了索引外,DB2® Universal Database™ 还为您提供实例化的查询表(在版本 7.2 和更早的发行版中,称为“总结表”),在许多情况下,这些表比索引更有效。其实,实例化的查询表(materialized query table,MQT)是根据查询结果定义的表。本文将描述一些示例,在这些示例中,与单独使用索引相比,MQT 提供更有效的性能改进。
优点:避免重复计算
MQT 可以帮助您避免对于每次查询重复计算(如 SUM)。让我们假设有一个名为 CUSTOMER_ORDER 的表,它存储了好几年的客户订单。该表的记录超过一百万条,平均行宽为 400 个字节。现在,假设我们必须对 2001 年的订单运行多次查询,并且我们只需要表中的三列,如下所示:
select SUM(AMOUNT), trans_dt
from db2inst2.CUSTOMER_ORDER
where trans_dt between '1/1/2001' and '12/31/2001'
group by trans_dt
|
或
select SUM(AMOUNT), status
from db2inst2.CUSTOMER_ORDER
where trans_dt between '1/1/2001' and '12/31/2001'
group by status
|
如果有适当的索引,那么这些查询被作为索引扫描来执行。 清单 1是执行计划的摘录,它表明使用索引扫描运行查询的预计成本是 152455。
清单 1. 对 CUSTOMER_ORDER 表运行查询的成本
-------------------- SECTION ---------------------------------------
Section = 1
SQL Statement:
select SUM(AMOUNT), trans_dt
from db2inst2.CUSTOMER_ORDER
where trans_dt between '1/1/2001' and '12/31/2001'
group by trans_dt
Estimated Cost = 152455
Estimated Cardinality = 378
(这里省略了一些行)
Subsection #2:
Access Table Name = DB2INST2.CUSTOMER_ORDER ID = 2,591
| #Columns = 1
| Index Scan: Name = DB2INST2.CUST_ORD_TRANS_DT ID = 4
| | Index Columns:
| | | 1: TRANS_DT (Ascending)
(这里省略了一些行)
End of section
|
现在,让我们创建一个 MQT,它包含我们所需的列和行,包括总和计算。
CREATE TABLE DB2INST2.SUMMARY_CUSTOMER_ORDER_2001 AS
(SELECT SUM(AMOUNT) AS TOTAL_SUM,
TRANS_DT,
STATUS
FROM DB2INST2.CUSTOMER_ORDER
WHERE TRANS_DT BETWEEN '1/1/2001' AND '12/31/2001'
GROUP BY TRANS_DT,
STATUS)
DATA INITIALLY DEFERRED REFRESH DEFERRED;
|
子句 DATA INITIALLY DEFERRED
表示:数据不作为 CREATE TABLE 语句的一部分插入到表中。而是您必须执行 REFRESH TABLE 语句来填充表。子句 REFRESH DEFERRED
表示:表中的数据仅作为发出 REFRESH TABLE 语句时的快照反映查询结果。有关创建 MQT 的更多信息,请参阅 SQL Reference。
当我们准备填充刚才创建的 MQT 时,发出下面的语句:
REFRESH TABLE DB2INST2.SUMMARY_CUSTOMER_ORDER_2001;
|
现在,对 MQT 的查询速度快很多,因为 MQT 的大小相当小,它的行很短(与基表的 400 个字节相比,它才 45 个字节)。 清单 2 显示了由 dynexpln 生成的执行计划的摘录,它表明了一个显著的性能改进,与上一个计划的预计成本 152455 相比,它只有 101。
清单 2. 对 MQT 运行查询减少了成本
-------------------- SECTION ---------------------------------------
Section = 1
SQL Statement:
select sum(total_sum), trans_dt
from db2inst2.summary_customer_order_2001
where trans_dt between '1/1/2001' and '12/31/2001'
group by trans_dt
Estimated Cost = 101
Estimated Cardinality = 25
这里省略了一些行
Subsection #1:
Access Summary Table Name = DB2INST2.SUMMARY_CUSTOMER_ORDER_2001 ID = 2,44
| #Columns = 2
| Relation Scan
(这里省略了一些行)
| | Sortheap Allocation Parameters:
| | | #Rows = 21
| | | Row Width = 45
| | Piped
(这里省略了一些行)
|
注:如果 2001 年 CUSTOMER_ORDER 中的数据在刷新之后又进行了更新,则需要再次刷新 MQT。
优点:避免资源集中式扫描
让我们假设,我们经常需要最新的 2002 年总计。过去在 2002 年 1 月 3 日运行得非常快的报告,在 5 月就运行得慢多了,因为 2002 年的数据量增加了。正如我们前面所描述的那样,查询在 CUSTOMER_ORDER 表上作为索引扫描执行。
现在,我们应该考虑 MQT 能如何帮助我们改进性能。然而,因为数据始终在更新而且我们需要最新数据,所以不能使用 REFRESH DEFERRED,因为下一次更新基表时,MQT 将不与基表同步。现在,让我们用 REFRESH IMMEDIATE 和 ENABLE QUERY OPTIMIZATION 选项来创建 MQT。
CREATE TABLE DB2INST2.SUMMARY_CUSTOMER_ORDER_2002 AS(
SELECT
TRANS_DT,
STATUS,
COUNT(*) AS COUNT_ALL,
SUM(AMOUNT) AS SUM_AMOUNT,
COUNT(AMOUNT) AS COUNT_AMOUNT
FROM DB2INST2.CUSTOMER_ORDER
GROUP BY TRANS_DT,
STATUS)
DATA INITIALLY DEFERRED
REFRESH IMMEDIATE
ENABLE QUERY OPTIMIZATION;
|
REFRESH IMMEDIATE
表示:在用 REFRESH TABLE 语句填充 MQT 后,MQT 的内容始终是最新的。
重要事项:为了使优化器能够自动选择 MQT,ENABLE QUERY OPTIMIZATION 必须是有效的(这是缺省值)。
其它语法说明:所有聚合都出现在 SELECT 列表的末尾。另外,虽然我们的业务只关注 SUM(AMOUNT),但我们仍必须将 COUNT(*) 和 COUNT(AMOUNT) 包括在全查询中。原因很容易记住。让我们假设正在从基表中删除一个给定日期的所有记录:
DELETE FROM DB2INST2.CUSTOMER_ORDER WHERE TRANS_DT = ?/1/2002?
|
现在,DB2 必须检测到特定日期的所有记录都已消失并删除 MQT 中的所有相应记录。有了 COUNT 字段就可以使 DB2 快速执行它,而不必扫描表或其索引。仅当 AMOUNT 列可空时,才需要 COUNT(AMOUNT)。
现在,该填充 MQT 并刷新其统计信息了:
REFRESH TABLE DB2INST2.SUMMARY_CUSTOMER_ORDER_2002;
RUNSTATS ON TABLE DB2INST2.SUMMARY_CUSTOMER_ORDER_2002 WITH DISTRIBUTION;
|
现在,让我们看一下查询性能是如何改进的(Estimated Cost = 392)。 清单 3是查询执行计划的摘录:
清单 3. 优化器选择使用 MQT
-------------------- SECTION ---------------------------------------
Section = 1
SQL Statement:
select SUM(AMOUNT), trans_dt
from db2inst2.customer_order
where trans_dt >= '1/1/2002'
group by trans_dt
Estimated Cost = 392
Estimated Cardinality = 268
(这里省略了一些行)
Subsection #1:
Access Summary Table Name = DB2INST2.SUMMARY_CUSTOMER_ORDER_2002 ID = 2,46
| #Columns = 2
| Relation Scan
|
注:表 CUSTOMER_ORDER(不是总结表)是在查询中指定的。优化器已经自动选择使用 MQT。
无论何时修改 CUSTOMER_ORDER 表,互斥的表锁就会在 SUMMARY_CUSTOMER_ORDER_2002 上保留,直到事务结束为止。只有同时具有聚合函数和 REFRESH IMMEDIATE 选项的 MQT 才会这样。因此,修改 CUSTOMER_ORDER 中相关字段(包括所有插入和删除)的事务必须很短,以减少锁争用。这个问题不适用于用 REFRESH DEFERRED 选项创建的 MQT,也不适用于复制的 MQT(在下一节中描述)。
优点:通过使用复制的 MQT 避免广播
让我们假设在分区环境中有一个名为 CUSTOMER_DATA 的大表。该表 CUSTOMER_DATA 与它的子表并置(Collocate)。分区键是系统生成的整数 CUSTOMER_ID。表 CUSTOMER_DATA 有一个对另一个表 ZIP_CODE 的引用。表 CUSTOMER_DATA 和 ZIP_CODE 未被并置。然而,这两个表常常连接在一起。让我们研究一下 清单 4中所示的访问计划。
清单 4. 与 ZIP_CODE 的连接会引起跨节点广播
-------------------- SECTION ---------------------------------------
Section = 1
SQL Statement:
select c.*, z.zip, z.state_name, z.country_name
from db2inst2.customer_address c join db2inst2.zip_code z on
c.zip_cd = z.zip_cd
Estimated Cost = 100975
Estimated Cardinality = 255819
Coordinator Subsection:
Distribute Subsection #2
| Broadcast to Node List
| | Nodes = 0, 1
Distribute Subsection #1
| Broadcast to Node List
| | Nodes = 0, 1
Access Table Queue ID = q1 #Columns = 38
Return Data to Application
| #Columns = 38
Subsection #1:
Access Table Queue ID = q2 #Columns = 4
| Output Sorted
| | #Key Columns = 1
| | | Key 1: (Ascending)
Nested Loop Join
| Access Table Name = DB2INST2.CUSTOMER_ADDRESS ID = 2,591
| | #Columns = 35
| | Index Scan: Name = DB2INST2.CU_ZIP_CD ID = 2
| | | Index Columns:
| | | | 1: ZIP_CD (Ascending)
| | | #Key Columns = 1
| | | | Start Key: Inclusive Value
| | | | | 1: ?
| | | | Stop Key: Inclusive Value
| | | | | 1: ?
| | | Data Prefetch: Eligible 162
| | | Index Prefetch: Eligible 162
| | Lock Intents
| | | Table: Intent Share
| | | Row : Next Key Share
| | Insert Into Asynchronous Table Queue ID = q1
| | | Broadcast to Coordinator Node
| | | Rows Can Overflow to Temporary Table
Insert Into Asynchronous Table Queue Completion ID = q1
Subsection #2:
Access Table Name = DB2INST2.ZIP_CODE ID = 2,590
| #Columns = 4
| Relation Scan
| | Prefetch: Eligible
| Lock Intents
| | Table: Intent Share
| | Row : Next Key Share
| Insert Into Sorted Temp Table ID = t1
| | #Columns = 4
| | #Sort Key Columns = 1
| | | Key 1: ZIP_CD (Ascending)
| | Sortheap Allocation Parameters:
| | | #Rows = 4479
| | | Row Width = 36
| | Piped
Sorted Temp Table Completion ID = t1
Access Temp Table ID = t1
| #Columns = 4
| Relation Scan
| | Prefetch: Eligible
| Insert Into Asynchronous Table Queue ID = q2
| |
Broadcast to All Nodes of Subsection 1
| | Rows Can Overflow to Temporary Table
Insert Into Asynchronous Table Queue Completion ID = q2
End of section
|
ZIP_CODE 表不会经常更新(因为不常有新的邮政编码),但会经常成为连接目标。每次发出导致连接的查询时,必须将 ZIP_CODE 表广播到每个节点。
这对于要使用 复制的 MQT 来说,可能是个好情况,它基于可能已经在单个分区节点组中创建的表,但您需要在节点组中的所有数据库分区中进行复制,以便启用频繁访问的数据的并置。要创建复制的 MQT,调用带 REPLICATED 关键字的 CREATE TABLE 语句。
CREATE TABLE DB2INST2.SUMMARY_ZIP_CODE AS (SELECT * FROM DB2INST2.ZIP_CODE)
DATA INITIALLY DEFERRED REFRESH IMMEDIATE ENABLE QUERY OPTIMIZATION REPLICATED;
|
定义中不允许有聚合。ZIP_CODE 表在 ZIP_CD 上有唯一的索引。让我们填充该表,在其上创建索引并更新统计信息:
REFRESH TABLE DB2INST2.SUMMARY_ZIP_CODE;
CREATE INDEX AAA_TTT ON DB2INST2.SUMMARY_ZIP_CODE(ZIP_CD);
RUNSTATS ON TABLE DB2INST2.SUMMARY_ZIP_CODE WITH DISTRIBUTION AND DETAILED INDEXES ALL;
|
现在,优化器自动选择使用复制的表,这样,每次运行查询时,不必将 ZIP_CODE 表广播到每个节点。
清单 5. 通过使用复制的 ZIP_CODE 表,避免某些跨节点广播
-------------------- SECTION ---------------------------------------
Section = 1
SQL Statement:
select c.*, z.zip, z.state_name, z.country_name
from db2inst2.customer_address c join db2inst2.zip_code z on
c.zip_cd = z.zip_cd
Estimated Cost = 101171
Estimated Cardinality = 255819
Coordinator Subsection:
Distribute Subsection #1
| Broadcast to Node List
| | Nodes = 0, 1
Access Table Queue ID = q1 #Columns = 38
Return Data to Application
| #Columns = 38
Subsection #1:
Access Summary Table Name = DB2INST2.SUMMARY_ZIP_CODE ID = 2,47
| #Columns = 4
| Relation Scan
| | Prefetch: Eligible
| Lock Intents
| | Table: Intent Share
| | Row : Next Key Share
| Insert Into Sorted Temp Table ID = t1
| | #Columns = 4
| | #Sort Key Columns = 1
| | | Key 1: ZIP_CD (Ascending)
| | Sortheap Allocation Parameters:
| | | #Rows = 8958
| | | Row Width = 36
| | Piped
Sorted Temp Table Completion ID = t1
Access Temp Table ID = t1
| #Columns = 4
| Relation Scan
| | Prefetch: Eligible
Nested Loop Join
| Access Table Name = DB2INST2.CUSTOMER_ADDRESS ID = 2,591
| | #Columns = 35
| | Index Scan: Name = DB2INST2.CU_ZIP_CD ID = 2
| | | Index Columns:
| | | | 1: ZIP_CD (Ascending)
| | | #Key Columns = 1
| | | | Start Key: Inclusive Value
| | | | | 1: ?
| | | | Stop Key: Inclusive Value
| | | | | 1: ?
| | | Data Prefetch: Eligible 162
| | | Index Prefetch: Eligible 162
| | Lock Intents
| | | Table: Intent Share
| | | Row : Next Key Share
| | Insert Into Asynchronous Table Queue ID = q1
| | | Broadcast to Coordinator Node
| | | Rows Can Overflow to Temporary Table
Insert Into Asynchronous Table Queue Completion ID = q1
End of section
|
虽然在我们的示例中,使用复制的 MQT 的预计成本稍微高了点(101171 vs. 100975)(因为我们正在另外一种空闲系统上运行,这种系统将两个分区放同一台计算机上。)然而,当节点驻留在不同计算机上并且它们之间的网络很忙时,在这种情况下使用复制的 MQT 的性能优点会变得明显。
所以,当您从以下这样的表中复制数据时,使用复制的 MQT 会有性能方面的优势:
- 是经常连接的。
- 很少更新(即使曾经更新过)。
- 不太大(虽然如果并置的性能优势可以抵消复制的一次性成本,您可能会考虑复制不太更新的大表。)
另外,对于复制的 MQT,不会发生针对 REFRESH IMMEDIATE 表所描述的锁定问题。
REFRESH IMMEDIATE vs. REFRESH DEFERRED
REFRESH IMMEDIATE MQT 会象索引那样影响查询的性能。这些影响包括:
- 加速相关选择(select)语句的性能。
- 只要有意义,就由优化器自动选择它们。
- 会降低插入(insert)、更新(update)和删除(delete)语句的性能。
- 不能直接更新。
- 可能会占用相当大的磁盘空间。
- 在更新其基表期间,可能会保留互斥锁。
要查看对更新性能的影响,请参阅 清单 6(仍没有 MQT)中所示的 INSERT 语句的 EXPLAIN 输出。
清单 6. 对基本 ZIP_CODE 表执行的 INSERT 操作
-------------------- SECTION ---------------------------------------
Section = 1
SQL Statement:
insert into db2inst2.zip_code(zip_cd, zip, state_cd, state_name,
country_name) values (60606, '60606', 'IL', 'Illinois',
'United States')
Estimated Cost = 25
Estimated Cardinality = 1
(这里省略了一些行)
|
现在,让我们添加用 REFRESH IMMEDIATE 选项创建的 MQT,并查看 清单 7 中所示的 EXPLAIN 输出。
清单 7. 用 REFRESH IMMEDIATE 创建的 MQT 上的 INSERT 会增加性能成本
-------------------- SECTION ---------------------------------------
Section = 1
SQL Statement:
insert into db2inst2.zip_code(zip_cd, zip, state_cd, state_name,
country_name) values (60606, '60606', 'IL', 'Illinois',
'United States')
Estimated Cost = 50
Estimated Cardinality = 1
(这里省略了一些行)
|
在这个特殊示例中,当存在 REFRESH IMMEDIATE MQT 时,插入记录的预计成本是双倍的。另一方面,REFRESH DEFERRED MQT 没有降低插入、更新和删除语句的性能。
适度使用 REFRESH IMMEDIATE MQT,以仅仅优化频繁运行且当前数据很重要的查询。一些 MQT 不适于立即刷新条件。可以在 SQL Reference中找到准确的规则。
让优化器决定
优化器可以根据以下条件选用用 REFRESH IMMEDIATE 选项创建的 MQT 来代替其基表:
- 基表、MQT 及其索引的当前统计信息。
- CURRENT QUERY OPTIMIZATION 设置的值。
如果 CURRENT REFRESH AGE 设置选项设置为 ANY,则优化器可以使用用 REFRESH DEFERRED 选项创建的 MQT。在 SQL Reference中详细描述了 CURRENT QUERY OPTIMIZATION 和 CURRENT REFRESH AGE 设置选项。
为优化器提供 MQT、创建适当的索引并使统计信息保持最新。并让优化器选择是使用基表还是使用总结表。在某些情况下,优化器将选择不使用 MQT。
不管 CURRENT REFRESH AGE 和 CURRENT QUERY OPTIMIZATION 设置选项的值是什么,您都可以直接在 SELECT 语句的 WHERE 子句中用 REFRESH DEFERRED 和 REFRESH IMMEDIATE 来指定 MQT。
结束语
正如我们所看到的那样,如果正确应用了 MQT,那么它们在各种情况下会非常有用。上面的示例演示了如何应用 MQT 来改进查询性能。虽然 MQT 使用起来十分方便,但需要额外的磁盘空间。用 REFRESH DEFERRED 选项创建的 MQT 不会影响对基表执行插入、更新和删除的性能,而用 REFRESH IMMEDIATE 选项创建的 MQT 会影响。
关于作者
|
|
|
Alexander Kuznetsov 在软件设计、开发和数据库管理方面已经有十四年的经验。目前,他正在设计 DB2 UDB EEE 中多 TB 级群集数据库。Alexander 是 IBM 认证的高级技术专家(DB2 群集)和 IBM 认证的解决方案专家(数据库管理和应用程序开发)。可以通过 comp.databases.ibm-db2 新闻组与他联系。
|