通过 Optim Query Tuner 调优 SQL,第 2 部分: 调优单个查询

学习如何监控和调优查询及负载以提升应用程序性能

本系列的第 1 部分介绍了访问路径的概念,并演示了如何在 Optim Query Tuner 中读取访问路径图。本文将介绍调优单个查询的方法。本文将使用一个查询样例向您演示使用 Optim™ Query Tuner 的整个过程。此过程包括查询格式化和注释,以及访问计划、统计数据、谓词和索引的分析。我们的目标是确保 IBM® DB2® 优化器得到制定基于性能的最佳决策所需的信息,并为您提供一些建议,使 DB2 优化器有更多的选择可以改进访问,如创建必需的索引等。

Gene Fuh, IBM 杰出工程师, IBM

Gene Fuh 照片Gene Fuh 自 1994 开始就一直从事 IBM 数据库技术工作。在 DB2 LUW 组织工作了六年半之后,他于 2000 年加入了 DB2 for z/OS 组织。2004 年,Gene 组建了一个研发团队,负责研发 DB2 Optimization Expert and Optimization Service Center (DB2 OE/OSC)。在 2007 年该产品同时可用于 DB2 9 for z/OS 之前,他一直都是该项目的首席架构师和监管人。在 2008 年,Gene 开始从 DB2 OE/OSC 技术过渡到 Optim 调优解决方案,其现在被称为 Optim Query Tuner 和 Optim Query Workload Tuner。在 IBM 工作的 17 年间,Gene 提交了 48 项专利申请并在学术和 IBM 会议上发表过超过 20 篇技术论文。



Kendrick Ren, 咨询软件工程师, IBM

Kendrick Ren 照片Kendrick Ren 是 IBM Optim Query Tuner 和 Optim Query Workload Tuner 产品的技术领导,他在 IBM 多伦多实验室外工作。自从 2004 年团队组建以来,他就一直致力于这些产品的前身(DB2 Optimization Expert and Optimization Service Center 产品)的研发工作。Kendrick 与使用这些产品的客户和业务合作伙伴密切合作,并在查询优化领域协助他们。在加入 Optimization Expert 团队以前,Kendrick 从事了两年 IBM WebSphere Commerce Server 产品的工作。



Kathy Zeidenstein, 高级软件工程师,Enablement, IBM

作者照片:Kathy ZeidensteinKathy Zeidenstein 在 IBM 工作了很多年。目前她在 IBM Data Studio 支持团队工作。在担任这个职务之前,她是 IBM OmniFind Analytics Edition 的产品营销经理。



Qiang Song, 顾问软件工程师, IBM

Qiang Song 的照片Qiang Song 是 IBM Optim Query Tuner 和 Optim Query Workload Tuner 产品的技术负责人,从 2005 年起在 IBM 中国开发实验室工作。他还领导着产品的客户参与和服务工作。Qiang 在 SQL 性能调优和 Eclipse 开发方面有着丰富经验。



2011 年 7 月 18 日

简介

在系列第 1 部分 通过 Optim Query Tuner 调优 SQL,第 1 部分:了解访问路径 中,已经介绍了访问路径 的概念。对于一个给定的 SQL 语句,往往有多个访问路径选择,而不同的访问路径具有不同的性能特点。在执行 SQL 之前,DB2 优化器会评估候选访问路径的成本,然后选择成本最低的路径。对于动态 SQL 语句,在 PREPARE 步骤中处理,对于静态 SQL 语句,在 BIND 步骤中处理。

尽管 DB2 优化器对选择最佳访问路径有所帮助,但结果还是依赖于输入数据,优化器无法访问或控制这些数据。如果您是开发人员或 DBA,了解如何调优查询对您还是会有所帮助的,您可以为 DB2 优化器提供最佳的输入。

本文将介绍调优单个查询的方法,其中还包括为什么理解如何调优查询很重要(即使 DB2 有世界一流的优化器)。然后您可以使用一个查询样例来了解调优查询的方法,使用 Optim Query Tuner 的相关特性,这些特性可以极大地帮助您理解、分析和调优单个查询。

请注意,本文主要介绍 DB2 for z/OS 上的 SQL 调优,但其中大部分查询优化概念和 SQL 调优方法也可用于 DB2 for Linux®、UNIX® 和 Windows®。

如果您想自己尝试一下本文中的查询样例,可以下载本文 下载小节 中的样例项目文件,然后将项目文件导入 Data Studio(单独版本或带有 Fix Pack 1 或后续版本的 IDE 包)。

按以下步骤导入样例项目:

  1. 打开您的 Studio 或 Optim Query TunerOpen 产品中的 IBM Query Tuning Perspective。
  2. 选择 File > Import
  3. 在 Import 向导中,导航至 Query Tuner >Projects,然后单击 Next
  4. 单击 Browse,选择包含下载 zip 文件的目录,会看到 Projects 窗口中有一个项目列表。
  5. 选择 samplequerytuningproject,单击 Finish
  6. 现在样例项目应该会出现在 Project Explorer 中。如果您仍看不到 Project Explorer 窗口,那么确定您是在 IBM Query Tuning Perspective 中,并选择 Window > Reset Perspective。或者,您还可以选择 Window > Show View > Project Explorer

关于 Optim 查询调优解决方案

Optim 查询调优解决方案提供了一个环境,通过 advisor 和引导您制定出解决方案的工具来确定和调优性能较差的 SQL 语句。在以下产品中具有查询调优功能:

  • Data Studio 2.2.1(单机版和 IDE)中有基本的单个查询调优和查询格式化功能。本产品可免费用于 DB2 for z/OS 和 DB2 for Linux, UNIX, and Windows。要知道,尽管本系列文章中解释了您如何使用 Data Studio 来解析访问路径图,但 Data Studio 中也不是包含所有提到的功能。
  • 在 Optim Query Tuner 中可以使用单个查询调优、查询格式化,以及大量 advisor。本产品可免费用于 DB2 for z/OS 和 DB2 for Linux, UNIX, and Windows。
  • Optim Query Workload Tuner 中可使用查询负载调优、单个查询调优以及完整 advisor。本产品也可用于 DB2 for z/OS(截至撰写本文时)。

简单起见,本文使用 Optim Query Tuner 名称来指代 Optim 查询调优解决方案提供的 advisor 和工具。以下将根据情况,在描述非上述产品提供的功能时,会用具体的产品名称。

请注意,本文主要关注的是查询调优方法,并使用 Optim Query Tuner 的屏幕截图来演示要点。本文不会提供过多如何使用 Query Tuner 的具体信息。要了解关于各种产品特性的更多信息,以及在 QQT 中如何启动各种功能的详细介绍,请查阅 参考资料 一节。

查询优化概览

如图 1 所示,DB2 优化器选择最佳访问路径。

图 1. DB2 优化器概览
DB2 优化器概览。上方文字是详细描述。

优化器根据多个输入的信息来对比候选访问路径的成本,如下例所示:

  • 目录统计数据
    DB2 优化器是基于成本的优化器。基于成本的优化的基础是一组统计数据,此数据可用来准确评估所有候选访问路径的成本,并将高效与低效的访问路径区分开来。用 DB2 中的目录表来评估访问路径的成本。例如,SYSTABLES 和 SYSTABLESPACE 表中的信息会告诉您使用了多少行多少页来保存表中的数据。
  • 物理数据库设计
    物理数据库设计包括表设计、索引设计、物化查询表设计,以及其他物理数据库对象的设计。索引设计对访问路径选择至关重要。在第 1 部分中提到过,有两种类型访问方法:表空间扫描(TBSCAN)和索引扫描 (IXSCAN)。一般来说,索引扫描是访问数据的最有效的方法,尤其是表很大但符合条件的行很少的时候。
  • SQL 语句
    SQL 语句本身也会影响访问路径选择。例如,使用不正确的谓词会妨碍优化器使用索引扫描,即使在索引可用的情况下。还有,在选择访问路径之前,优化器首先会执行一系列查询转换以增加可用访问路径的数目。如果 SQL 语句设计得很糟,优化器就很难转换查询,在选择最佳访问路径时的选择就很少。
  • 选择访问路径的其他影响因素
    除了要考虑目录统计、物理数据库设计和 SQL 语句本身,DB2 优化器还受中央处理器模型、中央处理器数目、缓存池大小、RID 池大小及其他资源设置影响。例如,如果两个系统的缓存池大小不一样,即使目录统计一样,访问路径也可能不同。

DB2 优化器很全面,而且很强大。既然有了 DB2 优化器,那么为什么还需要查询调优?什么时候进行查询调优呢?为什么还需要查询调优,有两个理由。

  • DB2 优化器不是无所不知的。
    尽管 DB2 优化器中包含很多信息可用来设置计划,但它却不知道不存在的内容。例如,优化器不会知道数据的特点,除非运行 RUNSTATS 将相关统计数据填入目录。此外,有些内容到运行时才能知道。例如,优化器到执行时才会知道主机变量或参数标记的值(如果查询中包含的话)。
  • DB2 优化器不能控制一切。
    如前文所述,物理数据库设计、SQL 语句以及系统资源会影响优化器如何选择最佳访问路径,而且数据库和查询设计都超出 DB2 优化器控制范围。这就是 DBA 和开发人员所能提升或降低 SQL 性能之处。

性能调优的目的是尽可能为优化器提供最好的输入,以便优化器能选择最好的访问路径。这包含了应用程序开发人员和 DBA 双方的努力。

对于应用程序开发人员:

  • 遵循 SQL 编码标准和指导原则。
    当您编写 SQL 语句的时候,需要符合 SQL 编码标准和指导原则。例如,编写可索引或一阶谓词,尽量不要编写没有连接谓词(也称为 Cartesian 连接)的查询。
  • 合理使用 REOPT 绑定选项。
    对于带变量的 SQL 语句,优化器会使用一个默认过滤因子在绑定时确定最佳访问路径。某些情况下,如果语句包含主机变量、参数标记或特殊寄存器那么访问路径在运行时的性能不会很好。您可以使用 REOPT 绑定选项在绑定时或运行时重新优化 访问路径。

对于 DBA:

  • 收集充分、准确的统计数据。
    不充分或不准确的统计数据会导致对候选访问路径评估不准确,这也是选择低效访问路径的最常见原因。同时,收集和刷新所有统计数据会消耗很多不必要的资源。您需要根据 INSERT、UPDATE 和 DELETE 操作的数目和数据分布的变化,在资源消耗尽量少的情况下定期收集正确的统计数据。
  • 优化索引设计
    您需要使用本地谓词和连接谓词设计索引,以支持高效访问。您也许还要设计索引以避免数据排序并提供只带索引的访问。
  • 从整体上调优应用程序。
    为了确保应用程序具有良好性能,需要从整体上对应用程序调优。通过评估每个语句来对整个应用程序调优的工作量是巨大的。还有,某一个语句的性能提升可能会降低应用程序中其他语句的性能。因此,对应用程序整体调优至关重要,这也称作负载调优。本文着重介绍单个查询调优;本系列的下一篇文章将会扩展本文中的技术,详细介绍负载调优。

本文还介绍了潜在的查询性能问题以及如何解决这些潜在问题的方法。使用 Optim Query Tuner 会让处理过程更加简单。

查询调优方法

查询调优方法概览

执行查询调优之前,您首先要知道对哪些内容调优,本例中优化器选择查询本身和现有的访问计划,然后找出调优的办法。

根据这个思路,您应该执行以下步骤对查询进行完全的调优,这些都可以在 Query Tuner 中完成。

  • 对有问题的查询进行格式化,这样可以更容易阅读和理解查询逻辑。
  • 使用相关统计数据来注释有问题的查询,这样可以更好地理解 DB2 使用哪些内容来进行评估。
  • 分析查询访问计划,从而可以切实看到优化器对访问数据作出的选择。
  • 执行统计数据分析,确保 DB2 优化器一直具有最新最需要的统计数据。
  • 执行谓词分析,看看能否尽量被选中。
  • 执行索引分析,确保索引正确,从而能避免不必要的表扫描。

在以下章节中,将会使用清单 1 中的 SQL 语句来作为示例,详细讲解每个查询任务。正如您所能想象到的,这些任务之间有很多相互依赖关系。例如,改变获取到的统计数据很有可能影响谓词分析的结果。还有,您可能要反复执行其中的一个或多个任务,直到某一个特定的性能问题解决。

清单 1. 本文中使用的查询样例
SELECT CCUS.CUST_FIRST_NAME
     , CCUS.CUST_LAST_NAME
     , CINT.CUST_INTEREST_RANK
     , CILO.CUST_INTERST
FROM CUST_CUSTOMER AS CCUS
     , CUST_INTEREST_LOOKUP AS CILO
     , CUST_INTEREST AS CINT
WHERE ( CCUS.CUST_CITY = 'Singapore' 
        AND CCUS.CUST_PROV_STATE = 'Singapore'            
        AND CCUS.CUST_CODE IN ( 
                                SELECT COHE.CUST_CODE
                                FROM CUST_ORDER_HEADER AS COHE
                                     , CUST_ORDER_STATUS AS COST
                                WHERE ( COHE.CUST_ORDER_DATE 
                                            >='2009-01-01 00:00:00.001'            
                                        AND COST.CUST_ORDER_STATUS IN ( 'Shipped', 
                                            'Back-ordered', 'In-process' ) 
                                        AND COHE.CUST_ORDER_STATUS_CODE 
                                            = COST.CUST_ORDER_STATUS_CODE
                                      )
                              )
        AND CCUS.CUST_CODE = CINT.CUST_CODE
        AND CINT.CUST_INTEREST_CODE = CILO.CUST_INTEREST_CODE
      )
ORDER BY CCUS.CUST_LAST_NAME ASC 
         , CCUS.CUST_FIRST_NAME ASC 
         , CINT.CUST_INTEREST_RANK ASC

格式化问题查询

在对一个查询调优之前,您要了解问题查询的以下方面。

  • 查询的语义:此查询访问哪些表?每个引用表中使用了哪些种类谓词?使用了哪些种类谓词来连接引用表?
  • 查询的访问路径:如何访问这些表?是否扫描了整个表或是否由索引访问?如果是索引,那么是哪个索引或是哪些索引?连接顺序或连接方法是什么?

如您在图 2 中所见,原始未格式化的查询很难阅读,很难理解。

图 2. 未格式化的查询
未格式化的查询

Optim Query Tuner 可以格式化问题查询,这为以后的分析提供了一个良好开端。在格式化查询中,SELECT 子句下每个表引用、每个列引用,以及每个谓词都会在各自行上显示。对于本文中的查询样例,格式化查询如图 3 所示。

图 3. 格式化的查询
格式化的查询。图下方文字是详细描述。

正如您所想象,对于很长的复杂的 SQL,只是对查询格式化就可以为 DBA 节省很多时间。现在很容易发现访问了哪些表,查询中的有多少,以及这些表如何连接。格式化的查询可以提供以下功能:

  • 通过扩展和收缩复杂 SQL 的一部分,来深入获取更详细的查询部分,例如引用视图和子查询。
  • 很容易看到如何在 SQL 中访问特定的表。当您单击格式化查询中任意一行时,其他包含来自同一张表的列或表引用的查询的行也会高亮显示。
  • 根据各种标准,如本地谓词或连接谓词,以及表引用来定制谓词的格式化顺序。

回到图 3 中的已格式化的查询,您可以看到以下内容:

  • 查询访问以下表:CUST_CUSTOMER、CUST_INTEREST 和 CUST_INTEREST_LOOKUP 来取得符合条件的客户名和投资信息。
  • 三个表用等值连接谓词连接。
  • CUST_CUSTOMER 上有三个谓词。前两个谓词是简单的等值谓词(CCUS.CUST_CITY = 'Singapore'、CCUS.CUST_PROV_STATE = 'Singapore')。第三个谓词是 IN 列表谓词,其中又包含了一个不相关子查询:
    • 子查询访问 CUST_ORDER_HEADER 和 CUST_ORDER_STATUS 表以获得符合条件的客户号,这两个表用等值连接谓词连接起来。
    • CUST_ORDER_HEADER 上有个范围本地谓词,CUST_ORDER_STATUS 上有个 IN 列表本地谓词。
  • 另外两个表(CUST_INTEREST 和 CUST_INTEREST_LOOKUP)上没有本地谓词。
  • 结果根据客户名和投资排列。

Query Tuner 还能使发现 DB2 优化器在何处转换查询变得简单。在此提醒您,转换是 DB2 优化器对查询作出的调整,以提升查询的性能;例如,可能为传递性闭包添加一个谓词,以方便评估连接顺序。

为了演示,给定一个谓词,例如 A.CUSTNO BETWEEN ? AND ? AND C.CUSTNO = A.CUSTNO,DB2 可以推导出谓词 C.CUSTNO BETWEEN ? AND ? 必须也为真,因此 DB2 查询转换可以添加谓词让它能考虑另一个索引。

对于本文中的查询样例,转换后的查询如图 4 所示。

图 4. 已转换查询
格式化的查询。上方文字是详细描述。

如您所见,优化器创建了一个虚拟表来处理 IN 列表子查询。另外,不相关子查询被转换成了相关子查询。这是 DB2 for z/OS V9.1 中 引入的优化,它能让 DB2 将查询作为一个整体,而非几个单独的查询段来优化。将查询作为整体优化时,DB2 会考虑一个查询段对另一个的影响,并且会考虑重新排序的查询段以确定一个最优查询路径。

对问题查询注释

除了格式化的 SQL 谓词和表引用,Query Tuner 还包含对相关目录统计数据和成本评估信息的注释,例如基数和评估符合条件的行,如图 5 所示。能够随时了解这些信息能帮助 DBA 加快分析并减少紧急情况下的停机时间。

图 5. 已注释的问题查询
注释的问题查询。图下方的文字是详细描述。

对于查询中的每个表引用,Query Tuner 会添加以下注释:

  • CARDF(见图 5 中 1):表基数,表示表中总行数。CUST_CUSTOMER 是最大的表(31,284),而 CUST_INTEREST_LOOKUP 是最小的表(338)。
  • QUALIFIED_ROWS(见图 5 中 2):对表应用本地谓词后符合条件的行数目。尽管 CUST_CUSTOMER 和 CUST_INTEREST 的表基数几乎一样,CUST_CUSTOMER 表中符合条件的行只超过 1 个,这说明表中的本地谓词的选择性很强。与 CUST_INTEREST 相比,符合条件的行数与基数一样(31,255)。这表示对表未过滤。这是说得通的,因为 CUST_CUSTOMER 表上有两个本地谓词,而 UST_INTEREST 上没有本地谓词。
  • NPAGESF(见图 5 中 3):显示了此表行所占的总页数。

对于查询中的每个谓词,Query Tuner 为谓词中的引用行和谓词的成本评估添加了统计数据注释:

  • COLCARDF(见图 5 中 4):列基数,表示评估的列中不同值的数目。如果谓词包含不止一列,那么每个引用列的列基数会用反斜杠(“/”)分隔,其顺序与谓词中列出现的顺序相同。
  • MAX_FREQ (见图 5 中 5):所有可能列值的最大频度。某个特定列值的频度是指包含此列值的行出现的次数。例如,某一列有五个不同值(COLCARDF=5),如果数据均匀分布,则 MAX_FREQ 大约为 20%,因为每个不同列值在表格行中出现的可能性为 20%。如果列基数为 5 并且 MAX_FREQ 远大于 20%,那说明表中的数据不是根据列均匀分布的。换句话说,列上的数据有偏离。
  • FF(见图 5 中 6):谓词的过滤因子。过滤因子是 0 到 1 之间的一个数值,它用来评估表中谓词为真的行所占的比例。过滤因子表示谓词的选择性有多强。谓词的选择性越强,应该越早应用谓词。

分析查询访问计划

在 Query Tuner 中可以看到您的数据服务器运行查询的处理过程。这些可见的内容称为访问计划图。从这张访问计划图中,您可以看到优化器对如何处理查询作出何种选择以及理由。此图表包含表示表、索引、操作和返回数据的节点。这些节点由表示处理流程的连接线连接排列。此图片从左至右,从下到上。每个节点都有统计数据、评估成本、选择信息等用来确定访问计划流的注释。

了解访问计划对于理解性能、影响性能以及稳定性能都有重要。请参考本系列之前的文章,了解更多阅读和理解访问路径的文章。

图 6 是 Query Tuner 为本文中大的查询样例生成的访问路径图。

图 6. 访问计划图
访问计划图。图下方的文字是详细描述。

(查看图 6 的 大图)。

在图 6 中访问计划图中,您可以看到以下内容:

  • 此查询包含两个查询段,QB1(见图 6 中 1)和 QB2 (图 6 中 2)。QB2 代表 IN 列表子查询,而 QB1 代表主子查询。
  • 在外查询段 QB1 中,QB2 与 CUST_CUSTOMER 表连接,这表示 IN 列表子查询已转换为相关子查询,而在原查询中它则是非相关子查询。
  • QB1 的访问计划总结如下: TBSCAN(CUST_CUSTOMER) NLJ ISCAN(CUST_INTEREST) NLJ ISCAN(CUST_INTEREST_LOOKUP)
    • QB1 中三个表用嵌套循环(NLJ)连接。
    • QB1 中三个表按以下顺序连接: CUST_CUSTOMER -> CUST_INTEREST -> CUST_INTEREST_LOOKUP
    • CUST_CUSTOMER 通过表扫描访问,而 CUST_INTEREST 和 CUST_INTEREST_LOOKUP 是通过索引扫描访问。
  • QB2 的访问计划总结如下: TBSCAN(CUST_ORDER_HEADER) NLJ ISCAN(CUST_ORDER_STATUS)。
    • QB2 中两个表用嵌套循环(NLJ)连接。
    • QB2 中两个表按以下顺序连接: CUST_ORDER_HEADER -> CUST_ORDER_STATUS
    • CUST_ORDER_HEADER 通过表扫描访问,而 CUST_ORDER_STATUS 是通过索引扫描访问。

在图 6 的访问计划图中,您可以做一些初始性能分析:

  • 对内部表 CUST_INTEREST 和 CUST_INTEREST_LOOKUP 通过索引扫描进行访问。这是相当高效的访问计划。
  • 对外部子查询(CUST_CUSTOMER)和内部子查询(CUST_ORDER_HEADER)的主表通过表扫描,这会有潜在的问题:
    • CUST_CUSTOMER 的表基数大约是 30000。尽管如此,由于它是主表,而且只用表扫描访问一次,因此会引发一些性能问题,但不会太严重。
    • 内部子查询是相关子查询。根据外部表(CUST_CUSTOMER)返回的符合条件的行数不同,可能会多次访问。无论是从图 5 的注释,还是图 6 中的访问计划图,您能看到 CUST_CUSTOMER 评估符合条件的行都是 1;换句话说,优化器认为 CUST_ORDER_HEADER 只被扫描一次。假设表基数大约是 50000;这也将会是性能上的灾难。

更进一步,您确定 CUST_CUSTOMER 将只被扫描一次,因为这是外部子查询中的主表。但是您不确定 CUST_ORDER_HEADER 是否只被扫描一次,因为 CUST_CUSTOMER 中符合条件的行是用现有的统计数据和谓词计算得出的:

  • CUST_CUSTOMER 的表基数是 31284(见图 5 中 7)。
  • 表中有两个本地谓词,选择性很强:
    • CCUS.CUST_CITY = 'Singapore', FF=0.00727(见图 5 中 8)
    • CCUS.CUST_PROV_STATE = 'Singapore', FF=0.004(见图 5 中 9)
  • 优化器评估符合条件的行大约是 31284*0.00727*0.004 = 1。

如果 CUST_CUSTOMER 的评估符合条件的行不正确,那怎么办?例如,如果这两个本地谓词选择性没有优化器评估的强,怎么办?这会造成严重的性能问题,因为 CUST_ORDER_HEADER 会被访问很多次。

验证可疑性能瓶颈的一个方法是查看查询的运行时统计数据,这可以通过打开 IFCID 318 上的性能跟踪获得。另一个选择是使用 Query Tuner 从状态缓存获取状态,并查看状态运行时信息,如图 7 所示。

图 7. 运行时统计数据
运行时统计数据。图下方的文字是详细描述。

(查看图 7 的 大图)。

突出显示的行(图 7 中以 “B” 结尾)显示了本文中查询样例的运行时信息。如您所见,查询执行了三次,平均运行时间大约是 307 秒,非常慢。表扫描(STAT_RSCN)的总数是 1764,即每次执行大于 580(1764/3 )。这比访问计划图评估的要大得多,图中是 2(CUST_CUSTOMER 一次,CUST_ORDER_HEADER 一次)。这进一步加深了我们对 CUST_CUSTOMER 中的符合条件行数不真实的怀疑。

还有一个验证方法,就是发出以下查询,计算符合条件的行的实际值。

清单 2. 计数查询
SELECT COUNT(*)
FROM CUST_CUSTOMER AS CCUS
WHERE CCUS.CUST_CITY = 'Singapore' AND CCUS.CUST_PROV_STATE = 'Singapore'

以上计数查询的结果是 CUST_CUSTOMER 表中有 588 行符合条件记录。换句话说,优化器过高估计了本地谓词对表格的选择性。在本文的以下章节中,您将会从统计数据、谓词和索引等方面分析问题查询,然后看看过高估计如何产生,以及如何解决。

执行统计数据分析

从查询注释信息中,您可以很容易地看到哪些统计数据可用,哪些统计数据丢失。对于本文中的查询,收集了引用表、列、索引的统计数据,如表基数、列基数等等。

另一方面,有些分布统计数据(如列频度)并未收集。如图 8 所示,CUST_CITY 的 MAX_FREQ 在谓词 CCUS.CUST_CITY = 'Singapore' 中丢失。丢失的统计数据会让优化器高估或低估谓词的选择性,最终会选择低效的访问路径。

图 8. 统计数据分析
统计数据分析。图下方的文字是详细描述。

如果统计数据是以前收集的,但很长时间没有刷新且已经过时,也会出现这个问题。尤其是最后一次收集后数据发生巨大变化,会造成这种问题。在访问计划图的表或索引节点上会有一个 RUNSTATS TIMESTAMP 属性,它表示收集统计数据的时间。请注意,您可以在 Query Tuner 优先权中设置一个阈值,定义多旧的数据可认为过时。默认情况下,超过 1 年的数据就认为是过时的。

如果不同时期收集的数据不同,可能会导致 DB2 for z/OS 上各个统计数据不一致。例如,如果已经收集了某个表的表级数据,然后插入大量行,那么收集索引的统计数据时,索引列基数会比表基数大很多。不一致的统计数据还会误导优化器选择糟糕的访问路径。

执行谓词分析

同时,您还可以执行谓词分析,查找注释视图中是否有潜在问题,如图 9 所示。

图 9. 谓词分析
谓词分析。图下方的文字是详细描述。

(查看图 9 的 大图)。

根据图中的注释信息,优先表 CUST_CUSTOMER (CCUS) 有两个本地谓词,其选择性很强,各自的过滤因子分别是 0.00727 和 0.004。

由于未收集到频度信息(MAX_FREQ=(missing)),每个谓词的过滤因子是在假设数据均匀分布在各列的基础上计算的。例如,CUST_CITY 的列基数是 1376,因此谓词 CCUS.CUST_CITY = 'Singapore' 的过滤因子计算为 1/1376=0.00727。

在谓词选择型评估中,一个会有多大比重?如果列上的数据有偏离,假设大多数城市名为 Singapore,那么当前的选择性可能会被大大高估。为了得到更精确的评估,您需要收集 CUST_CUSTOMER 表上的 CUST_CITY 和 CUST_PROV_STATE 列的频度统计数据。

另一个潜在问题是内部子查询中 CUST_ORDER_HEADER (COHE) 表引用上的本地谓词(见图 9)。由于它是范围谓词,而且没有频度和直方图统计数据可用,过滤因子是用基础统计信息 HIGH2KEY 和 LOW2KEY 计算得出的。如果列上有数据偏离,评估结果就可能不正确。为了得到更精确的评估,您需要收集 CUST_ORDER_STATUS_CODE 表上的 CUST_ORDER_DATE 列的直方图统计数据。

已经分析了本地谓词的选择性,我们再看看表的选择性。

对于外部子查询中的三个表,在 CUST_INTEREST_LOOKUP (CILO) 和 CUST_INTEREST (CINT) 上没有本地谓词;因此如果其中任何一个都没有作为主表处理,那么表选择性为零,这非常糟糕。

如果 CUST_CUSTOMER (CCUS) 作为主表处理,根据这两个本地谓词,CUST_CUSTOMER 表的选择性是 0.00727*0.004, CUST_CUSTOMER 中符合条件的行是 1 (31284*0.00727*0.004)。这看上去选择性很强,但这是基于(CUST_CITY, CUST_PROV_STATE)这两列互不关联的假设,这不是实际情况。由于 Singapore 也是新加坡一个城市的名称,为了得到表选择性的更精确的评估结果,您需要收集列组(CUST_CITY, CUST_PROV_STATE)的基数和频度统计数据。

CUST_CUSTOMER 处理后,连接谓词 CCUS.CUST_CODE = CINT.CUST_CODE 可以被认为是 CUST_INTEREST(CINT) 表的本地谓词。假设 CUST_INTEREST 的表基数是 31255,您可以看到对于 CUST_CUSTOMER 中每个符合条件的行,在 CUST_INTEREST 中只有一条匹配记录。即,通过连接谓词,CUST_INTEREST 表的选择性非常强。

CUST_CUSTOMER 和 CUST_INTEREST 处理后,连接谓词 CINT.CUST_INTEREST_CODE = CILO.CUST_INTEREST_CODE 可以被认为是 CUST_INTEREST_LOOKUP(CILO) 表的本地谓词。CUST_INTEREST_LOOKUP 表的基数是 338,您可以看到对于 CUST_INTEREST 中符合条件的每一行,CUST_INTEREST_LOOKUP 中大约有 13 (338/26) 条匹配记录,其选择性相当不错。

有了以上的统计数据和谓词分析,您应该清楚了解要收集哪些统计数据。您收集了这些统计数据之后,会得到如图 10 所示的新访问计划图。

图 10. RUNSTATS 之后的访问计划图
RUNSTATS 后的访问计划图。图下方的文字是详细描述。

在这张新访问计划图中,您可以看到以下内容:

  • 由于 CUST_CUSTOMER 表上的列组(CUST_CITY and CUST_PROV_STATE)的统计数据已收集,DB2 优化器已知道本地过滤后正确的数字是 590 而不是 1。
  • 内部子查询的连接顺序已经更改,原来的顺序是 CUST_ORDER_HEADER-> CUST_ORDER_STATUS,现在是 CUST_ORDER_STATUS -> CUST_ORDER_HEADER;因为 CUST_ORDER_HEADER 表上 CUST_ORDER_DATE 列的直方图统计数据已收集。
  • 有了 CUST_CUSTOMER 中正确的符合条件的行,内部子查询会作为非相关子查询进行处理,会写入工作文件,然后会用内存中的稀疏索引扫描。

图 11 中以 “B” 结尾的行显示了 RUNSTATS 之后查询样例的运行时信息。平均运行时间是 2.8 秒,比 RUNSTATS 之前(大约 307 秒,大幅提升)。

图 11. RUNSTATS 之后的运行时统计数据
RUNSTATS 后的运行时统计数据。图下方的文字是详细描述。

(查看图 11 的 大图)。

还是如图 11 所示,每次执行查询都有两个表扫描。在图 10 的访问计划图中,您能很容易看到分别对 CUST_CUSTOMER 和 CUST_ORDER_HEADER 表的扫描。下一节中,您会看到如何通过索引分析修复它。

执行索引分析

Optim Query Tuner 提供了 HTML 和文本格式的报表,其中包含表、索引和谓词,这些都包含在一个特定的 SQL 语句中。对于本文的查询样例,图 12 显示了索引报告,它描述了引用表的现有索引。

图 12. 索引报告
索引报告。图下方的文字是详细描述。

为了方便讨论,表 1 对现有索引做了总结。

表 1. 现有索引
表名索引名索引列
CUST_CUSTOMERBQT01_CUST_CUSTOMERCUST_AGE
BQT02_CUST_CUSTOMERCUST_PROV_STATE_CODE
BQT03_CUST_CUSTOMER

GENDER_CODE,

MARITAL_STATUS_CODE,

CUST_AGE

IDX_CUST_CUSTOMERCUST_CODE
CUST_INTERESTCUST_INTEREST_PK

CUST_CODE,

CUST_INTEREST_CODE

CUST_INTEREST_LOOKUPCUST_INT_LOOKUP_PK

CUST_INTEREST_CODE,

CUST_INTEREST_LANGUAGE

CUST_ORDER_HEADERBQT01_CUST_ORDER_HEADERCRDT_METHOD_CODE
CUST_ORDER_HEADER_PKCUST_ORDER_NUMBER
CUST_ORDER_STATUSBQT01_CUST_ORDER_STATUS

CUST_ORDER_STATUS,

CUST_ORDER_STATUS_LANGUAGE

CUST_ORDER_STAT_PK

CUST_ORDER_STATUS_CODE,

CUST_ORDER_STATUS_LANGUAGE

现在我们看一下 CUST_CUSTOMER 表中的谓词,它是从图 3 中的格式化查询中提取出来的:

  • CCUS.CUST_CITY = 'Singapore'
  • CCUS.CUST_PROV_STATE = 'Singapore'
  • CCUS.CUST_CODE = CINT.CUST_CODE

根据表 1 中的现有索引和以上谓词,只有连接谓词(CCUST.CUST_CODE = CINT.CUST_CODE)可以从现有谓词 IDX_CUST_CUSTOMER 中收益。两个本地谓词无法从匹配或筛选现有的索引扫描中获益。

为了支持本地谓词和连接谓词进行索引扫描,需要创建 CUST_CITY、CUST_PROV_STATE 和 CUST_CODE 列上的索引。

对于 CUST_INTEREST 和 CUST_INTEREST_LOOKUP 表,每个都有连接谓词,而且现有索引支持连接谓词上的匹配索引扫描,如下所示。

  • CCUS.CUST_CODE = CINT.CUST_CODE
  • CINT.CUST_INTEREST_CODE = CILO.CUST_INTEREST_CODE

对于 CUST_ORDER_HEADER 表,现有的索引表不支持表上的本地和连接谓词,如下所示。

  • COHE.CUST_ORDER_DATE >= '2009-01-01 00:00:00.001'
  • COHE.CUST_ORDER_STATUS_CODE = COST.CUST_ORDER_STATUS_CODE

为了支持本地谓词和连接谓词进行索引扫描,需要创建 CUST_ORDER_DATE 和 CUST_ORDER_STATUS_CODE 列上的索引。还有,为了获取只有列访问的更好的性能,应该将 CUST_CODE 作为关键列。考虑到此表是 IN 列表子查询的一部分,如果 CUST_CODE 是第一关键列,DB2 优化器可能会考虑全局查询优化,从而将子查询作为关联子查询来访问。

对于 CUST_ORDER_STATUS 表,第一个索引 BQT01_CUST_ORDER_STATUS 支持对本地谓词的索引访问,但不支持连接谓词。第二个索引 CUST_ORDER_STAT_PK 支持连接谓词的索引访问,但不支持本地谓词:

  • COST.CUST_ORDER_STATUS IN ( 'Shipped', 'Back-ordered', 'In-process' )
  • COHE.CUST_ORDER_STATUS_CODE = COST.CUST_ORDER_STATUS_CODE

为了获得 CUST_ORDER_STATUS 表的最佳性能,您需要用 CUST_ORDER_STATUS_CODE 和 CUST_ORDER_STATUS 列创建一个表。

根据之前的索引分析,我们建议创建以下三个索引,以获得最佳性能:

表 2. 新索引
表名索引名索引列
CUST_CUSTOMERCUST_CUSTOMER_NEW_INDEX

CUST_CITY, CUST_PROV_STATE,

CUST_CODE

CUST_ORDER_HEADERCUST_ORDER_HEADER_NEW_IDX

CUST_CODE, CUST_ORDER_DATE,

CUST_ORDER_STATUS_CODE

CUST_ORDER_STATUSCUST_ORDER_STATUS_NEW_IDX

CUST_ORDER_STATUS_CODE

CUST_ORDER_STATUS

创建三个新索引之后,您图 13 中的新访问计划图。

图 13. 创建新索引后的访问计划图
创建索引后的访问计划图。图下方的文字是详细描述。

(查看图 13 的 大图)。

有了新建的索引,所有表都可以用索引扫描访问。子查询再次改成相关子查询,子查询中的两个表只用索引访问,从而达到最佳性能。

图 14 中以 “B” 结尾的行显示了 RUNSTATS 之后查询样例的运行时信息。平均运行时间是 0.246 秒,比 RUNSTATS 之前(大约 2.8 秒大幅提升)。

图 14. 创建索引后的运行时统计数据
创建索引后的运行时统计数据。图下方的文字是详细描述。

(查看图 14 的 大图)。

通过对统计数据、谓词和索引的逐步分析,问题查询的性能大幅提升。表 2 显示的是性能提升总结。第 1 行显示条有钱的运行时统计数据。第 2 行显示收集相关数据后的统计数据,第 3 行显示创建建议索引后的结果。

表 3. 表 3 性能对比
调优任务执行计数STAT_GPAGSTAT_ELAPSTAT_CPUAVG_STAT_ELAPAVG_STAT_CPU
调优之前323486129922.197815145.920307307.39927248.640102
收集统计数据4837611.2001860.880022.8000470.220007
创建索引460840.9833570.0477040.2458390.011926

使用 Optim Query Tuner 加速查询

到目前为止,您已经看到如何在 Optim Query Tuner 各项功能的帮助下分析和调优问题查询,这些功能包括查询注释、访问计划图等等。尽管这些工具非常直观,而且友好,但您仍然需要具有足够的关于 DB2 优化器和 SQL 访问路径的背景知识,从而能够完全利用这些工具提供的强大功能。当然,您还需要在分析上花些时间。

尽管如此,大多数情况下,您不需要手工执行查询调优。Optim Query Tuner 提供了一系列建议工具,通过提供建议来自动化查询调优,从而您可以直接在本产品中评审和执行,从而解决性能问题。

  • 统计数据 advisor 工具:收集所需统计数据以完成访问路径的建议。
  • 查询 advisor 工具:重写查询以获取更高效率的建议。
  • 索引 advisor 工具:能提升性能的索引的建议。

建议:通常情况下,建议您按以下顺序运行 advisor 工具:

  1. 运行统计数据 advisor 工具,并采取行动更新统计数据。
  2. 如果需要,并且可能的话,运行查询 advisor 工具并重写 SQL。
  3. 运行索引 advisor 工具,并创建新索引或调整现有索引。准确而全面的统计数据能帮助生成良好的访问路径建议,当然,索引 advisor 工具执行时要有准确的统计数据。

以下章节详细介绍了此前的 advisor 工具。

执行统计数据 advisor 工具以改善统计数据和收集

如本文开头所提到的,统计数据是优化器作出关于访问路径的决策的基础。因此,如果统计数据不准确或者过期,或者冲突,优化器会对查询计划中各步骤的成本评估不准确,从而导致糟糕的性能。

在 DB 2 中, RUNSTATS TABLE ALL INDEX ALL 命令会收集统一的统计数据,这其中很多对改善查询性能并不必要。同时,工具不会收集某个键的统计数据,例如多列和分布统计数据。列之间还有关联。例如,本文查询样例 CUST_CITY 和 CUST_PROV_STATE 列之间有很强关联。收集单个列的统计数据还不足以提供所需信息,因此您需要收集列组统计数据。

Query Tuner 统计数据通过提供 RUNSTATS 建议使得确定以下问题统计状态,收集所需统计数据变得非常容易。

  • 统计数据丢失:当统计数据丢失后,优化器假设一个默认值来确定成本,这非常不准。
  • 统计数据冲突:不一致的统计数据可能会导致优化器对成本评估错误,作出错误的访问计划评估决策。用户只收集一部分数据可能会造成不一致的统计数据,例如,在不同时间分别收集表和索引统计数据。
  • 统计数据过时:过时的统计数据无法表示表的当前状态。

统计数据 advisor 还会评估建议的相对重要性,并给出如下两种类型建议。

  • 修复:这种类型建议表示重要的统计数据已丢失,或是统计数据之间有冲突。建议中包含一个 RUNSTATS 命令,您可以用它来捕获和修复相关统计数据。
  • 完成:这种类型建议包含修复,还有为了维护而刷新的统计数据。此建议提供一个 RUNSTATS 作业,您也许会想要将其定期包含在您的维护循环中,以维护相关统计数据。

当您深入研究某个具体建议,如图 15 所示,您会发现统计数据 advisor 工具生成一个 RUNSTATS 命令,您可以用它来收集或修复统计数据。您可以将其保存以后执行,或者,如果您有适当权限,可以直接在 Query Tuner 客户端中运行。请注意,为了在 Query Tuner 中执行 RUNSTATS 建议,SYSPROC.DSNUTILU 存储过程要在服务器端可运行。

您还看到了关于建议的解释。在图 15 中,您看到 advisor 工具找到几个冲突或丢失的实例,并提供了为什么 advisor 工具认为其冲突的解释。

图 15. 统计数据 advisor 工具建议
统计数据 advisor 工具建议。图下方的文字是详细描述。

您已经看到,要改善统计数据质量就意味着要给 DB2 优化器提供准确的数据,在此基础上作出正确的优化访问路径的决策,从而提升性能,降低 CPU 占用。尽管如此,还有其他收益。通过只收集需要的数据,您可以避免收集无关统计数据,而这些数据会在有限的维护窗口中提高 CPU 占用,增加不必要的负荷。换句话说,统计数据 advisor 工具能帮助提高统计数据质量并提高统计数据收集效率。

执行查询 advisor 工具来改善查询设计

在选择访问路径之前,DB2 优化器将 SQL 语句转换成语意上一致的形式,例如使用谓词下推或谓词传递闭包。从而,它能提升可能的访问路径。与此相反,Query Tuner 能通过给出建议,进一步限制查询、增加索引利用率和减少数据读取的方式。帮助编写或调优查询找到错误和疏忽之处。查询 advisor 工具在查询中寻找机会做以下工作。

  • 将需要读取的索引页和数据行降到最低。例如,您可以通过把可以仅从索引就确定需要的行的谓词放到查询中来将行读取降到最低。
  • 尽量减少排序操作。例如,查询中需要 ORDER BY 或 GROUP BY 语句,或者它们能通过指数访问解决。

尤其是,查询 advisor 工具要检查以下内容。

  • 丢失连接谓词,只在定义了外键的情况下。
  • 提升性能的二阶谓词,如果重写为一阶谓词或可索引。请查阅 参考资料 中 DB2 for z/OS 信息中心链接,在其中您可以找到关于一阶和二阶谓词的谓词的信息。
  • 提升性能的一阶谓词,如果重写为索引。
  • 其他的非 DB2 自动提供的,而又能提供谓词传递闭包的本地谓词。
  • 下推到嵌套表表达式或物化视图而不会改变结果,并且不是由 DB2 自动完成的谓词。
  • 添加到复杂的 WHERE 语句的额外谓词,包括 ORAND() 括号。这可以在不改变结果的情况下提升性能。
  • SELECT * 的用法也可以用具体的列列表来替代。

在图 16 中,您可以看到查询 advisor 建议,以及建议理由的明细。UI 会自动突出显示与建议相关的查询部分。通过这种方式,DBA 或开发人员会了解他们所做的查询调优。

图 16. 查询 advisor 工具建议
查询 advisor 工具建议。图下方的文字是详细描述。

执行索引 advisor 工具以提高查询效率

Query Tuner 还提供索引建议。它会分析查询和建议附加的索引,从而优化查询访问。索引 advisor 工具会根据以下原因提出建议索引。

  • 外键未定义索引。
  • 为 SQL 语句提供索引过滤和/或扫描的索引。
  • 为 SQL 语句提供进索引访问的索引。
  • 可以帮助避免排序的索引。

图 17 显示了本文中查询样例的索引建议,以及估计的性能提升和 DASD 空间需求。它还生成了创建索引必需的 DDL,并且能立即运行(当然,您需要有合适的权限)或保存进行预览和执行。

图 17. 索引 advisor 工具建议
索引 advisor 工具建议。图下方的文字是详细描述。

您还可以定制建议和 DDL 创建。例如,您可以指定作为索引键的一部分的列的最大数,还可以更改生成 DDL 时所使用的 Creator ID。您还可以指定 REEPAGE、PCTFREE 和CLUSTERRATIO 值用于新索引,而无论索引页能否超过 4 KB。

您还可以在测试一下建议的索引,以及您想要在部署之前进行了 “如果这样会怎样的分析” 的索引。它能让您将各种限制应用到索引 advisor 工具,并看看这些限制条件如何修改建议。“如果这样会怎样的分析” 使用了虚拟索引来评估索引的效果。通过这样做,您可以选择创建能带给您最佳性能的索引。

结束语

本文介绍了调优单个查询的方法,并举例详细说明了此方法的每一步骤。其中包含如何通过使用查询格式化、注释、报告和访问计划图来更好地理解问题查询。您已经了解如何从统计数据、谓词和索引这几方面来分析和调优问题查询。我们还演示了如何使用 Query Tuner advisor 工具来加快查询调优。

本系列的下一篇文章将会把内容扩展到查询 工作负荷 范围,并介绍为什么要对工作负荷调优,以及工作负荷的调优,还将通过一个例子来详细介绍工作负荷调优。


下载

描述名字大小
本文样例项目文件samplequerytuningproject.zip338KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=733423
ArticleTitle=通过 Optim Query Tuner 调优 SQL,第 2 部分: 调优单个查询
publish-date=07182011