以文本方式查看主题 - W3CHINA.ORG讨论区 - 语义网·描述逻辑·本体·RDF·OWL (http://bbs.xml.org.cn/index.asp) -- 『 XML 与 数据库 』 (http://bbs.xml.org.cn/list.asp?boardid=17) ---- 智能化的优化器(作数据库的可以参考一下里面的技巧) (http://bbs.xml.org.cn/dispbbs.asp?boardid=17&rootid=&id=23064) |
-- 作者:卷积内核 -- 发布时间:10/14/2005 3:45:00 PM -- 智能化的优化器(作数据库的可以参考一下里面的技巧) 为什么两个几乎相同的查询的运行方式却大相径庭 让我们考虑一个非常典型的方案:查询 SELECT * FROM CUSTOMER WHERE STATE = 'IN' 的运行速度非常慢。一个非常相似的查询 SELECT * FROM CUSTOMER WHERE STATE = 'MI' 反复运行的速度却要快 10 多倍。我们首先检查显而易见的情况:STATE 列上是否有索引?有的。下一步,我们检查每个州的客户数量是否差别很大。以下查询显示了每个州的客户数量看上去相差不多:
SELECT STATE, COUNT(*) AS NUM_CUST FROM CUSTOMER GROUP BY STATE WHERE STATE IN('IN', 'MI')
STATE NUM_CUST ----- ---------- IN 19071 MI 18554
SELECT COUNT(*) AS NUM_CUST FROM CUSTOMER
NUM_CUST --------- 2007931
当我们研究执行计划时,我们发现较慢的查询是作为表空间扫描来执行的,而较快的查询使用了索引。两者的区别在哪里呢?优化器为什么不为这两个查询选择同一个有效的计划呢?
让我们仔细研究与该表相关的统计信息。在 DB2 中,通过 SYSCAT 和 SYSSTAT 模式中的系统视图可以披露统计信息。(有关统计信息的更多详细信息,请参考 DB2 Administration Guide 中关于性能(Performance)的章节。)在我们的示例中,统计信息并不是最新的(在以下的清单中,请参阅 STATS_TIME,它是两个月之前的)。此外,自上次收集统计信息(在下面的清单中,请参阅 CARD)之后,记录的总数(现在是 2007931)已经大大增加了:
SELECT STATS_TIME, CARD FROM SYSCAT.TABLES WHERE TABNAME = 'CUSTOMER' STATS_TIME CARD -------------------------- ------ 2002-10-01-08.49.49.117405 59616
虽然表已经超过了两百万行,但是优化器查找统计信息并且估计表中的记录仍然少于 60000 条。另外,STATE 列中值的分布也已经有很大的改变(TYPE = 'F' 代表最频繁出现的值):
SELECT TYPE,SEQNO, VALCOUNT, CAST(COLVALUE AS CHAR(30)) AS COLVALUE FROM SYSSTAT.COLDIST WHERE TABSCHEMA='DB2INST1' AND TABNAME='CUSTOMER_DATA' AND COLNAME='STATE' AND TYPE = 'F'
TYPE SEQNO VALCOUNT COLVALUE ---- ------ -------------------- -------------------------- F 1 19071 'IN' F 2 18554 'SC' F 3 11061 'CA' F 4 5857 'TN' F 5 2741 'KY' F 6 1065 'MO' F 7 220 'IL' F 8 90 'WI' F 9 26 'MI' F 10 4 'FL'
(该查询检索该列中最频繁出现的 10 个值。)注:上一次收集统计信息时,印地安那州的客户(STATE='IN')在所有客户中超过了 30%,而密歇根州的客户(STATE='MI')只占 0.05%。让我们刷新该统计信息,包括分布:
RUNSTATS ON TABLE MYSCHEMA.CUSTOMER WITH DISTRIBUTION AND DETAILED INDEXES ALL
在刷新之后,优化器使用索引访问来执行原来比较慢的查询,现在该查询的运行速度快多了。(有关 RUNSTATS 命令的完整语法,请参考 Command Reference。)正如我们所见,使统计信息保持最新是必要的。
但我们不要认为:如果存在某种适当的索引,使用索引始终是较好的选择。 为什么有时表空间扫描比索引扫描更可取 考虑相同的查询 SELECT * FROM CUSTOMER WHERE STATE = ?。不管您是否相信,有时执行表空间扫描比通过索引访问记录更有效。听起来让人吃惊吧?是的,也许要进行一些分析来得出这个结论。让我们从一些基准测试开始;然后加以说明。
CUSTOMER 表中约 18% 的记录与条件 WHERE STATE='IL' 匹配。通过对查询 SELECT * FROM CUSTOMER WHERE STATE = 'IL' 进行分析,优化器选择表空间扫描来执行它。让我们将该查询保存到名为 select.sql 的文件中,使用基准测试工具(db2batch)来测量执行该查询的实际代价。
db2batch -d MY_DB -f select.sql -r benchmark.txt -o p3
Number of rows retrieved is: 19998 Number of rows sent to output is: 19998
Elapsed Time is: 5.540 seconds
Locks held currently = 0 Lock escalations = 0 Total sorts = 0 Total sort time (ms) = 0 Sort overflows = 0 Buffer pool data logical reads = 2721 Buffer pool data physical reads = 2580 Buffer pool data writes = 0 Buffer pool index logical reads = 0 Buffer pool index physical reads = 0
(有关 db2batch 的更多详细信息,请参阅[Command Reference]。)
现在,让我们欺骗优化器,让它选择索引扫描来执行相同的查询。让我们使统计信息看上去象有一个虚构的州(STATE='IM'),而且在这个州里有许多客户,再让我们使伊利诺斯州的客户数量(如果有的话)变得很小。因为 SYSSTAT 模式中的视图是可更新的,让我们更新其中一个:
UPDATE SYSSTAT.COLDIST SET COLVALUE='IM' WHERE TABSCHEMA='DB2INST1' AND TABNAME='CUSTOMER_DATA' AND COLNAME='STATE' --AND TYPE = 'F' AND COLVALUE='''IL'''
提示:在 WHERE 子句中,必须用引号括起列值(COLVALUE='''IL''')。
现在,根据这些统计信息,优化器推断出只有很少的记录可能拥有 STATE='IL'。因此,它选择使用 STATE 上的索引的存取方案(请参阅较小的估计基数):
SQL Statement:
SELECT * FROM CUSTOMER_DATA WHERE STATE='IL'
Estimated Cost = 50 Estimated Cardinality = 1
Access Table Name = DB2INST1.CUSTOMER_DATA ID = 2,5 | #Columns = 13 | Index Scan: Name = DB2INST1.CUST_STATE ID = 5 | | Index Columns: | | | 1: STATE (Ascending) | | #Key Columns = 1 | | | Start Key: Inclusive Value | | | | 1: 'IL' | | | Stop Key: Inclusive Value | | | | 1: 'IL' | | Data Prefetch: None | | Index Prefetch: None
现在,让我们使用 db2batch 来执行第二个基准测试:
Number of rows retrieved is: 19998 Number of rows sent to output is: 19998
Elapsed Time is: 5.976 seconds
Locks held currently = 0 Lock escalations = 1 Total sorts = 0 Total sort time (ms) = 0 Sort overflows = 0 Buffer pool data logical reads = 19998 Buffer pool data physical reads = 2614 Buffer pool data writes = 0 Buffer pool index logical reads = 138 Buffer pool index physical reads = 28
显然,欺骗并没有给我们带来任何好处。在这种情况下,使用表空间扫描所耗费的时间实际上比使用索引扫描更少。
重要事项:我们已经手工更新了统计信息来对测试数据库执行一些“假定方案(what if)”分析。这对 SYSSTAT 模式的可更新视图是完全合理的用法。但是,在生产数据库中,我们绝对不应在正常的情况下更新统计信息。
现在,让我们解释发生了什么。我曾经听一个 5 岁的男孩说:“在监狱里待一秒钟不会有什么不良影响,所以在监狱里待两秒钟也不会有什么不良影响,那么在监狱里待三秒钟也不会有什么不良影响……”同样,通过索引读取一条记录会快一点,通过索引读取两条记录也会快一点,依此类推,但最多只能到某个数量,不能再多了。
根据统计信息,优化器估计 18% 的记录将匹配条件 STATE='IL'。它还预期这些记录在整个表中差不多是均匀分布的,因为 STATE 上索引的群集比率是非常低的,小于 0.1。(有关群集比率的更多信息,请参考 DB2 Administration Guide 中关于性能的章节。)这意味着:无论如何,几乎表中的每一页都至少有一条匹配的记录。表空间扫描使用预取,这意味着数据库引擎在一次有效的读操作中会读取几个相邻的页面。表空间扫描是读取表中所有页面的最有效的方法。无论索引扫描可能会多么有效,仍然存在扫描索引的额外工作。
有关预取的更多信息,请参考:
SQL Reference 中 CREATE TABLESPACE 语句的语法及其 PREFETCHSIZE 选项。 DB2 Administration Guide 中关于性能的章节中缺省预取大小(DFT_PREFETCH_SZ)配置参数。 因此,无论看起来有多令人吃惊,优化器选择表空间扫描最终是正确的。我们已经了解了在这种情况下,索引访问肯定效率比较低。 为什么有时计算 MIN 比计算 MAX 快很多 查询 SELECT MIN(TOTAL_AMOUNT) FROM CUSTOMER 查找 TOTAL_AMOUNT 上的现有索引中的值,并立即返回答案。但是,一个非常相似的查询 SELECT MAX(TOTAL_AMOUNT) FROM CUSTOMER 却需要耗费多得多的时间。执行计划指出优化器选择了扫描整个索引来计算 MAX。为什么?
在这种特殊情况下,没有更好的选择。TOTAL_AMOUNT 上的索引不允许反向扫描:
SELECT REVERSE_SCANS FROM SYSCAT.INDEXES WHERE INDNAME = 'CUSTOMER_AMT'
REVERSE_SCANS ------------- N
在删除索引并用选项 ALLOW REVERSE SCANS 重新创建它之后,这两个查询开始运行得一样快了。
CREATE INDEX CUSTOMER_AMT ON CUSTOMER(TOTAL_AMOUNT) ALLOW REVERSE SCANS RUNSTATS ON TABLE MYSCHEMA.CUSTOMER FOR INDEX MYSCHEMA. CUSTOMER_AMT
缺省情况下,DB2 索引不允许反向扫描。
提示:每当您在 CREATE TABLE 语句中创建 PRIMARY KEY、FOREIGN KEY 或 UNIQUE 约束时,就会隐式地创建一个索引。该索引不允许反向扫描。
您可以覆盖缺省行为:
创建一个没有约束的表(或删除现有约束)。 创建适当的索引。 使用 ALTER TABLE SQL 语句创建约束。 例如:
ALTER TABLE CUSTOMER DROP PRIMARY KEY; --or create a table not defining a primary key CREATE UNIQUE INDEX CUSTOMER_ID ON CUSTOMER(ID) ALLOW REVERSE SCANS; ALTER TABLE CUSTOMER ADD PRIMARY KEY(ID);
DB2 会给出一条警告,并重用第 2 步中创建的索引。
正如我们所见,在一些十分常见的情况下,允许反向扫描的索引是必要的。 消除不必要的连接 让我们考虑以下视图: CREATE VIEW CUSTOMER_ORDER_LIST AS SELECT CUSTOMER_ORDER.CUSTOMER_ID CUSTOMER.LAST_NAME CUSTOMER.FIRST_NAME CUSTOMER.PHONE CUSTOMER.EMAIL CUSTOMER_ORDER.ORDER_DT CUSTOMER_ORDER.AMOUNT CUSTOMER_ORDER.STATUS FROM CUSTOMER JOIN CUSTOMER_ORDER ON CUSTOMER.ID = CUSTOMER_ORDER.CUSTOMER_ID
CUSTOMER_ORDER 表中的所有记录在 CUSTOMER 表中都有父记录。该业务规则是由触发器维护的,而不是由外键约束维护的。(不要问我为什么。我能说的就是我在生产数据库中已经很多次看到它了。)
考虑查询:
SELECT CUSTOMER_ID, ORDER_DT, AMOUNT, STATUS FROM CUSTOMER_ORDER_LIST
您可能会认为根本不需要访问 CUSTOMER 表,因为所有必需的信息都在 CUSTOMER_ORDER 表的视图中,对吗?
事实并非这样。出于某些原因,优化器选择访问 CUSTOMER 表上的索引:
Estimated Cost = 25693
Access Table Name = DB2INST1.CUSTOMER ID = 2,5 | #Columns = 1 | Index Scan: Name = SYSIBM.SQL021126111001110 ID = 3 | | Index Columns: | | | 1: ID (Ascending) | | #Key Columns = 0 | | | Start Key: Beginning of Index | | | Stop Key: End of Index | | Index-Only Access | | Index Prefetch: Eligible 199 | Lock Intents | | Table: Intent Share | | Row : Next Key Share Merge Join | Access Table Name = DB2INST1.CUSTOMER_ORDER ID = 2,6
(这只是部分输出。)
究竟为什么要访问 CUSTOMER 表呢?优化器的选择实际上非常有道理:您可能轻易地删除了触发器,将一条违反引用完整性的记录插入 CUSTOMER_ORDER 表中,并重新创建了触发器。记录将保留在 CUSTOMER_ORDER 表中,这意味着存在这种情况:触发器不保证引用完整性。这就意味着优化器必须假设 CUSTOMER_ORDER 表中可能有一些记录在 CUSTOMER 表中没有匹配的记录,因此查找 CUSTOMER 表上的记录是必要的。
现在,让我们创建适当的约束,看看会发生什么:
ALTER TABLE CUSTOMER_ORDER ADD FOREIGN KEY(CUSTOMER_ID) REFERENCES CUSTOMER(ID)
如果有任何记录违反了该约束,那么这条语句就会失败。现在,优化器能消除不必要的连接,而且查询可以运行得更快:
Estimated Cost = 18067
Access Table Name = DB2INST1.CUSTOMER_ORDER ID = 2,6 | #Columns = 1 | Relation Scan | | Prefetch: Eligible | Lock Intents | | Table: Intent Share | | Row : Next Key Share | Return Data to Application | | #Columns = 1 Return Data Completion
正如我们所见,添加外键约束向优化器提供了一些非常有用的数据。优化器则向我们提供更有效的执行计划作为报答。 什么时候好的决策比快速的决策更好 以前当我在寻找新工作时,我曾无意中看到两个空缺职位,它们是同一家公司提供的,而且是针对同一个项目的。对于该项目,他们需要一个项目经理和一个技术负责人。在众多要求中,他们列出了:
◆ 对于项目经理:“能够做出快速的决策。” ◆对于技术负责人:“能够做出好的决策。” ◆确有其事!
对于低的优化级别,优化器必须动作迅速。无论我们打算提供什么样的最新和详细的统计信息,优化器也许没有足够的时间对它进行分析。前面几章中的所有示例都是在缺省优化级别 5 下运行的。如果我们在低优化级别 1 下重新考虑前面的示例,添加引用完整性约束将不会产生更好的计划。 如果您想要好的决策,而不是快速的决策,请相应地设置优化级别。 有关优化级别的更多信息,请参考 DB2 Administration Guide 中关于性能和实现(Implementation)的章节。 结束语 DB2 优化器是非常智能化的。但是,根据不正确的信息,它也许会得出优化程度较低的结论。我们已经知道了如何: 检测不正确或不完整的统计信息。 向优化器提供正确且完整的统计信息。 在测试环境中更新 SYSSTAT 模式的视图,并执行“假定方案”实验。 性能调优从来就不容易。在查询优化中没有一成不变的规则。只要有可能,就应检查优化级别,使统计信息保持最新,并确保业务规则作为约束实现。我希望本文在数据库开发人员处理许多问题时有所帮助。 祝您好运! 感谢 作者衷心感谢 Mike Pittinger 的帮助。 相关信息 Command Reference 下载地址: ftp://ftp.software.ibm.com/ps/products/db2/info/vr8/pdf/letter/db2n0e80.pdf Administration Guide: Implementation 下载地址: ftp://ftp.software.ibm.com/ps/products/db2/info/vr8/pdf/letter/db2d2e80.pdf Administration Guide: Performance 下载地址: ftp://ftp.software.ibm.com/ps/products/db2/info/vr8/pdf/letter/db2d3e80.pdf 页面顶部 关于作者 Alexander Kuznetsov 在软件设计、开发和数据库管理方面已有 15 年的经验。目前,他正在设计 DB2 UDB EEE 中多 TB 级群集数据库。Alexander 是 IBM 认证高级技术专家(DB2 群集)(IBM Certified Advanced Technical Expert (DB2 Cluster))和 IBM 认证解决方案专家(数据库管理和应用程序开发)(IBM Certified Solutions Expert (Database Administration and Application Development))。可以通过 alkuzo@mindspring.com 和 comp.databases.ibm-db2 新闻组与他联系。 |
-- 作者:berock -- 发布时间:10/30/2005 11:39:00 AM -- 楼主贴了一篇好文,请教楼主对此文有何感想? |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
46.875ms |