db2 索引优化

在数据库应用程序开发期间,开发人员倾向于在表上定义大量索引,以保证每个查询能够良好地执行。当应用程序开发完成并且数据库投入到生产环境中之后,存在过多索引将导致数据库性能下降。大量的索引意味着数据库系统在执行 UPDATE、INSERT 和 DELETE (UID) 操作时要做更多的工作。另外,存在大量索引时,RUNSTATS 和 REORG 等常规维护活动的运行也显著变慢。因此, 要实现最佳的数据库性能,最关键的是确定哪些索引对查询执行是必要的,哪些是可以安全删除而且不影响查询运行时的。本文解释了几种方法,DB2® for Linux®, UNIX®, and Windows® (DB2 LUW) 数据库管理员(DBA)可以通过它们识别未使用和不常使用的索引。

DB2 Design Advisor (db2advis)

DB2 Design Advisor 是识别未使用索引的简单方法。您可以使用 Design Advisor 分析一组 SQL 语句及其执行频率。分析完成之后,Design Advisor 返回以下信息:
额外的索引定义,它们通过减少查询的运行时间改进查询性能
DB2 用于查询执行的现有索引的名称
DB2 未用于查询执行的现有索引的名称

让我们看看基于 DB2 测试数据库 SAMPLE 的 Design Advisor 使用场景。

首先,通过在命令行上执行 db2sampl 命令创建 SAMPLE 数据库,如清单 1 所示。

清单 1. 创建 SAMPLE 数据库                       
db2sampl



在调用 Design Advisor 之前,您必须运行 EXPLAIN.DDL 脚本来创建它需要的 Explain 表。您可以在 DB2 安装的 sqllib/misc 子目录中找到该脚本。您仅需运行该脚本一次。清单 2 显示了运行该脚本所需的命令。

清单 2. 创建 Explain 表                       
db2 "CONNECT TO SAMPLE"
db2 -tf "C:\Program Files\IBM\SQLLIB\MISC\EXPLAIN.DDL"



当调用 Design Advisor 时,您必须定义需要对其进行分析的 SQL 语句组。您可以通过几种方法定义 SQL 语句组。其中一种方法是提供一个包含需要分析的 SQL 语句的文本文件。例如,清单 3 显示了一个名为 queries.sql 样例文件,它包含一组 SQL 语句。

清单 3. DB2 Design Advisor 输入文件 queries.sql                       
-- EMPLOYEE queries

--#SET FREQUENCY 123
SELECT LASTNAME FROM EMPLOYEE WHERE EMPNO = '000010';
--#SET FREQUENCY 456
SELECT LASTNAME FROM EMPLOYEE WHERE WORKDEPT = 'A00';


-- DEPARTMENT queries

--#SET FREQUENCY 245
SELECT DEPTNAME FROM DEPARTMENT WHERE DEPTNO = 'A00';
--#SET FREQUENCY 678
SELECT DEPTNAME FROM DEPARTMENT WHERE MGRNO = '000010';


-- PROJECT queries

--#SET FREQUENCY 345
SELECT PROJNAME FROM PROJECT WHERE PROJNO = 'OP1000';



在创建 Design Advisor 需要分析的 SQL 语句文本文件时,您需要考虑以下事项:
注释行以 -- 开始,Design Advisor 将忽略它们。
如果知道的话,您可以选择指定每个查询的大致执行频率。这能够帮助 Design Advisor 更好地估计创建新的索引能够对数据库性能有多大的改进。为此,在查询定义之前添加另外一个以 --#SET FREQUENCY 开始的行(尽管这些行以 -- 开始,但不被看作是注释行)。
文本文件中的每个 SQL 语句都必须以分号结束(;)。
SQL 语句可能包含参数标记。可以存在参数标记,因为 Design Advisor 并没有实际执行 SQL 语句,它仅为它们计算其他可行的访问计划。
对于所有没有完全限定的表和视图(不带有显式模式),调用方的授权 ID 被用作默认的模式。在调用 Design Advisor 时,您也可以选择使用 -q 选项覆盖默认模式。

清单 4 显示了一个样例命令,您可以使用它调用 Design Advisor 和将 queries.sql 指定为包含需要分析的 SQL 语句的输入文件。

清单 4. 使用输入文件 queries.sql 调用 DB2 Data Studio                       
db2advis -d sample -i queries.sql -m I -l -1 -t 0 -o db2advis_file.txt



以上命令的选项包含一些含义:
-d,数据库名。
-i,包含需要分析的 SQL 语句的输入文件。
-m,除了相关的索引之外,Design Advisor 还可以推荐 Materialized Query Tables (MQTs)、Multidimensional Clustering (MDC) 表和对分区表进行重新分区。在这个示例场景中,-m 的值为 I,这表示仅建议使用相关的索引(同时也是默认值)。
-l,新的索引或 MQT 的定义的大小限制。值 -1 表示没有大小限制。
-t,执行 Design Advisor 的运行时限制。值 0 表示没有时间限制。
-o,输出文件,用于写 Design Advisor 的建议的位置。

Design Advisor 创建的输出文件包含 3 个部分:建议的额外索引、建议的现有索引和未使用索引。对于这个场景,您主要对列出未使用索引的部分感兴趣。清单 5 显示了未使用索引部分的示例。

清单 5. DB2 Design Advisor 输出中的未使用索引部分                       
-- UNUSED EXISTING INDEXES
-- ============================
-- DROP INDEX "FECHNER "."XDEPT3";
-- DROP INDEX "FECHNER "."XPROJ2";
-- ===========================



对于这个场景,Design Advisor 表明索引 XDEPT3 和 XPROJ2 未被使用。不过要记住,该建议仅基于 Design Advisor 分析的一组 SQL 语句(包含在输入文件中的 SQL 语句)。除了输入文件中的 SQL 语句之外,还可能存在其他依赖于识别到的索引改善执行时间的语句。因此,在删除识别到的未使用索引之前,一定要小心检查和认真考虑依赖它们的其他 SQL 语句。

包含 SQL 语句的输入文件有一个代替办法,即让 Design Advisor 计算数据库的 SQL 语句缓存的内容。为此,您需要在 db2advis 上使用 -g 选项,如清单 6 所示。

清单 6. 调用 DB2 Design Advisor 计算 SQL 语句缓存内容                       
db2advis -d sample -g -m I -l -1 -t 0 -o db2advis_sql_cache.txt



以这种方式调用 Design Advisor 比手动地创建包含 SQL 语句的输入文件要省事得多。不过,您仍然要记住,分析得到的结果仅取决于 SQL 语句缓存的当前内容。调用 Data Studio 时未驻留在缓存中的内容将未被分析。因此,当使用 SQL 语句缓存作为输入时,应该在当天的不同时间点运行 Design Advisor,以计算更大范围的 SQL 语句。


     回页首





db2pd 实用程序

db2pd 实用程序通常用于问题诊断和监控,但也可用于查询关于数据库活动的信息。使用 db2pd 能够获得的信息之一是:自从数据库活动之后索引被访问的次数。清单 7 显示了如何使用 db2pd 实用程序为数据库中的所有表上的索引获取该信息。

清单 7. 使用 db2pd 实用程序查询表和索引的度量指标                       
db2pd -db sample -tcbstats all -file db2pd_tab_all.txt



以上调用 db2pd 的选项包含以下含义:
-db 数据库名
-tcbstats all 显示所有表和索引度量指标
-file 输出文件

如果您想要限制 db2pd 实用程序的输出,让它仅显示某些表及其索引,那么使用 -tcbstats 选项指定表空间 ID 和表 ID。为此,您首先需要在 SYSCAT.TABLES 目录视图上执行 SELECT 语句来确定表空间 ID 和表 ID,如清单 8 所示。

清单 8. 查询数据库目录以确定表的表空间 ID 和表 ID                       
db2 "SELECT TBSPACEID, TABLEID
    FROM SYSCAT.TABLES
    WHERE TABSCHEMA = 'FECHNER' AND TABNAME = 'DEPARTMENT'"



清单 9 显示了来自类似于以上的表空间 ID 和表 ID 查询的样例结果。

清单 9. 表空间 ID 和表 ID 查询的结果集                       
TBSPACEID TABLEID
--------- -------
      2     5

1 record(s) selected.



在确定您需要查询的表的表空间 ID 和表 ID 之后,您可以将 db2pd 输出仅限制到该表。为此,您可以在 -tcbstats 选项之后包含 tbspaceid 和 tableid 子选项,如清单 10 所示。

清单 10. 将 db2pd -tcbstats 输出限制到指定的表                       
db2pd -db sample -tcbstats all tbspaceid=2 tableid=5 -file db2pd_tab_dept.txt



清单 11 显示了来自类似于以上的 db2pd 查询的样例结果集。

清单 11. db2pd 实用程序显示的索引度量指标                       
Database Partition 0 -- Database SAMPLE -- Active -- Up 0 days 00:20:34

TCB Table Information:
Address   TbspaceID TableID PartID MasterTbs MasterTab TableName       SchemaNm
ObjClass DataSize   LfSize     LobSize   XMLSize  
0x797DF2B8 2       5     n/a   2       5       DEPARTMENT       FECHNER
Perm     1       0       0       0      

TCB Table Stats:
Address   TableName       SchemaNm Scans     UDI       RTSUDI           PgReorgs
NoChgUpdts Reads     FscrUpdates Inserts   Updates   Deletes   OvFlReads OvFlCrtes
RowsComp   RowsUncomp CCLogReads StoreBytes BytesSaved
0x797DF2B8 DEPARTMENT       FECHNER 0       0       0               0
0       951       0         0       0       0       0       0
0       0       0       -       -      

TCB Index Information:
Address   InxTbspace ObjectID PartID TbspaceID TableID MasterTbs MasterTab
TableName       SchemaNm IID   IndexObjSize
0x797E0330 2       5       n/a   2       5     2       5
DEPARTMENT       FECHNER 3     8      
0x797E0330 2       5       n/a   2       5     2       5
DEPARTMENT       FECHNER 2     8      
0x797E0330 2       5       n/a   2       5     2       5
DEPARTMENT       FECHNER 1     8      

TCB Index Stats:
Address   TableName       IID   PartID EmpPgDel   RootSplits BndrySplts PseuEmptPg
EmPgMkdUsd Scans     IxOnlyScns KeyUpdates InclUpdats NonBndSpts PgAllocs   Merges
PseuDels   DelClean   IntNodSpl
0x797E0330 DEPARTMENT       3     n/a   0       0       0       0
0       0       0       0       0       0       1       0
0       0       0      
0x797E0330 DEPARTMENT       2     n/a   0       0       0       0
0       678       0       0       0       0       1       0
0       0       0      
0x797E0330 DEPARTMENT       1     n/a   0       0       0       0
0       245       0       0       0       0       1       0
0       0       0      



与索引使用相关的结果集部分包含在 TCB Index Stats 部分中。以上的样例输出显示了 DEPARTMENT 表有 3 个索引。这些索引是通过索引 ID 来表示的(IID 列),而不是索引名。Scans 列显示了自数据库激活之后索引被访问的次数:
ID 1 索引被访问了 245 次。
ID 2 索引被访问了 678 次。
ID 3 索引从未被访问过。

因为索引 ID 3 从未被访问过,所以可以推断该索引为未使用或很少使用的索引。要获得 ID 3 索引的名称,请使用 SELECT 语句查询 SYSCAT.INDEXES 目录视图,如清单 12 所示。

清单 12. 根据索引 ID (IID) 查询数据库目录以确定索引名                       
db2 "SELECT INDSCHEMA, INDNAME
    FROM SYSCAT.INDEXES
    WHERE TABSCHEMA = 'FECHNER' AND TABNAME = 'DEPARTMENT' AND IID = 3"



清单 13 显示了以上的 SELECT 语句的输出。

清单 13. 索引名查询的结果集                       
INDSCHEMA               INDNAME
------------------------------ ------------------------------
FECHNER                 XDEPT3

1 record(s) selected.



除了 Scans 列之外,db2pd 实用程序的 TCB Index Stats 部分还有一个 IxOnlyScns 列,它显示仅扫描索引的数量。仅扫描索引是未包含表访问的索引访问,因为索引本身包含了所有被请求的数据。因此,如果 IxOnlyScns 计数器不为 0,您就必须将 Scans 列和 IxOnlyScns 列的计数合并起来,以得到索引被访问的总次数。

在讨论 Design Advisor 时已经提到,在解释索引使用信息时必须格外小心。使用 db2pd 方法时也要持有这种态度。某个索引可能要在特定时间点才被使用,但这不一定就意味着该索引永远未被使用。因此,在决定是否删除当前未使用的索引时,一定要仔细考虑。如果您决定删除索引,那么首先要保存它的 CREATE INDEX 语句,以便在需要时轻松地重新创建它。


     回页首





DB2 Workload Manager (WLM)

DB2 Design Advisor 和 db2pd 实用程序都是在 DB2 Version 8 中引入的,并且在 DB2 Version 9 中仍可使用。从 DB2 V9.5 开始,您还可以使用 DB2 Workload Manager (WLM) 来收集索引使用信息。一般而言,WLM 是一个收费的 DB2 特性,必须单独购买使用许可证。不过,作为 WLM 的一部分的扩展数据库监控选项可以免费使用,不需要单独的使用许可证。这个小节不介绍 DB2 WLM,而是通过一个逐步的说明,教您使用特定于 WLM 的事件监控器和两个 Perl 脚本(DB2 安装的一部分)收集和计算索引使用信息。

像 Design Advisor 一样,WLM 要求您在使用它之前创建 Explain 表(参见 清单 2 了解关于运行 EXPLAIN.DLL 脚本创建 Explain 表的细节)。同样,您必须首先运行位于 sqllib/misc 子目录中的 wlmevmon.ddl 脚本。该脚本创建并启用特定于 WLM 的事件监控器:DB2ACTIVITIES、DB2STATISTICS 和 DB2THRESHOLDVIOLATIONS。这些事件监控器将收集到的数据写到表中。默认情况下,这些表在表空间 USERSPACE1 中创建。如果您想让表位于另一个表空间中,可以在运行之前对脚本进行相应的修改。清单 14 显示了运行 wlmevmon.ddl 脚本的命令。

清单 14. 创建特定于 WLM 的事件监控器                       
db2 -tf "C:\Program Files\IBM\SQLLIB\MISC\wlmevmon.ddl"



默认情况下,事件监控器创建之后带有 AUTOSTART 选项,这表示在数据库下一次被激活时将自动运行事件监控器。在使用首次创建的事件监控器之前,您必须首先禁用数据库然后再激活它,或手动运行事件监控器,如清单 15 所示。

清单 15. 启动特定于 WLM 的事件监控器                       
db2 "SET EVENT MONITOR DB2ACTIVITIES STATE 1"
db2 "SET EVENT MONITOR DB2STATISTICS STATE 1"
db2 "SET EVENT MONITOR DB2THRESHOLDVIOLATIONS STATE 1"



要理解下一个命令,您需要基本了解服务类 的 WLM 概念。WLM 允许您将具有不同响应时间需求的工作负载分配给不同的服务类和服务子类。从 DB2 Version 9.5 开始,服务类的使用紧密地集成到 DB2 引擎中。即使 WLM 没有显式地使用服务类(即数据库管理员没有定义任何服务类),所有用户事务都是在预定义的服务类上下文中执行的。预定义的服务类为 SYSDEFAULTUSERCLASS,它的对应子类为 SYSDEFAULTSUBCLASS。特定于 WLM 的事件监控器通常针对特定的服务类或服务子类进行激活。这意味着它们仅为这些服务类或服务子类收集数据。

在这个样例场景中,您将为服务子类 SYSDEFAULTSUBCLASS 激活 DB2ACTVITIES 事件监控器。这样,您将为所有用户事务收集监控器信息,因为没有定义特定于应用程序的服务类。由于不存在特定于应用程序的服务类,所有用户事务都在默认服务类及其子类的上下文中执行。清单 16 显示了为默认的服务类及其子类激活事件监控器的命令。

清单 16. 为默认的服务类及其子类激活事件监控器                       
db2 "ALTER SERVICE CLASS SYSDEFAULTSUBCLASS UNDER SYSDEFAULTUSERCLASS
    COLLECT ACTIVITY DATA ON ALL DATABASE PARTITIONS WITH DETAILS"



监控器数据收集应该包含一个时间框架,您期望在这段时间内发生普通数据库活动(例如,每个工作日的 9 a.m. 到 5 p.m.)。在监控器数据收集完成之后,需要禁用事件监控器,如清单 17 所示。

清单 17. 禁用针对默认服务类及其子类的事件监控器                       
db2 "ALTER SERVICE CLASS SYSDEFAULTSUBCLASS UNDER SYSDEFAULTUSERCLASS
    COLLECT ACTIVITY DATA NONE"

           运行 Perl 样例程序
在运行 Perl 样例程序之前,必须为 Perl DBI 安装 Perl 解释器和 DBD::DB2 驱动程序。关于如何获得最新的驱动程序的信息,请查看 DB2 Perl Database Interface for LUW。



为了根据收集到的 WLM 事件监控器数据计算索引的使用,DB2 提供了两个 Perl 脚本,它们是 wlmhist.pl 和 wlmhistrep.pl。您可以在 DB2 安装目录下的 samples\perl 子目录中找到它们。这两个脚本都要求使用数据库名、数据库用户名和密码作为输入参数。

首先运行 wlmhist.pl 脚本。它将读取从事件监控器表执行的 SQL 语句,然后对每个语句运行 Explain 实用程序以生成对应的访问计划。在读取 SQL 语句之后,脚本将从 Explain 表提取访问计划信息并将其写到名为 WLMHIST 的表中(如果还没有 WLMHIST 表,就创建它)。

接下来,运行 wlmhistrep.pl 脚本以计算储存在 WLMHIST 表中的数据。该脚本创建一个包含关于表和索引使用的细节的报告。当运行 wlmhistrep.pl 时,除了数据库名、数据库用户名和密码之外,您还必须指定生成的报告文件的名称。

清单 18 显示了运行 Perl 脚本的命令,该脚本帮助您根据 WLM 事件监控器数据计算索引使用。

清单 18. 执行 wlmhist.pl 和 wlmhistrep.pl Perl 脚本                       
cd "C:\Program Files\IBM\SQLLIB\samples\perl"
perl wlmhist.pl sample userid password
perl wlmhistrep.pl sample userid password wlmhistrep.txt



wlmhistrep.pl 生成的报告包含 4 个部分:已使用(命中)表、未使用(未命中)表、已使用(命中)索引和未使用(未命中)索引。对于这个场景,您主要对未使用索引部分感兴趣。清单 19 显示了一个样例报告。

清单 19. wlmhistrep.pl 生成的样例报告                       
          INDEXES HIT REPORT FOR DATABASE sample
          _______________________________________________________


TABLE NAME       TABLE SCHEMA   OBJECT NAME       OBJECT SCHEMA   TOTAL HITS
__________________ _______________ __________________ _______________ __________

...
DEPARTMENT       FECHNER       PK_DEPARTMENT     FECHNER           245
DEPARTMENT       FECHNER       XDEPT2         FECHNER           678
EMPLOYEE         FECHNER       PK_EMPLOYEE       FECHNER           123
EMPLOYEE         FECHNER       XEMP2           FECHNER           456
PROJECT         FECHNER       PK_PROJECT       FECHNER           345
...

          INDEXES NOT HIT REPORT FOR DATABASE sample
          ___________________________________________________________


TABLE NAME       TABLE SCHEMA   INDEX NAME       INDEX SCHEMA   INDEX TYPE
__________________ _______________ __________________ _______________ __________

...
DEPARTMENT       FECHNER       XDEPT3         FECHNER       REG
PROJECT         FECHNER       XPROJ2         FECHNER       REG
...



INDEXES HIT REPORT 部分有一个 TOTAL HITS 列,它表明索引被访问的次数。从未被访问过的索引显示在 INDEXES NOT HIT REPORT 部分。


     回页首





MON_GET_INDEX 表函数

本文的 db2pd 实用程序 小节演示了如何使用 db2pd 实用程序获取索引度量指标。不过,如果您使用的是 DB2 Version 9.7,可以通过查询新的 MON_GET_INDEX 表函数获取相同的信息。使用表函数取代 db2pd 实用程序简化了结果计算,因为可以通过与 SYSCAT.INDEXES 目录视图结合将索引 IDs (IIDs) 转换成索引名。清单 20 显示了如何调用 MON_GET_INDEX 表函数的样例。

清单 20. MON_GET_INDEX 表函数(文件名 mon_get_index.sql)的样例调用                       
SELECT
    SUBSTR(SI.INDSCHEMA, 1, 30) AS INDSCHEMA,
    SUBSTR(SI.INDNAME, 1, 30) AS INDNAME,
    MGI.INDEX_SCANS,
    MGI.INDEX_ONLY_SCANS
FROM
  TABLE(MON_GET_INDEX('FECHNER', 'DEPARTMENT', -2)) as MGI,
  SYSCAT.INDEXES AS SI
WHERE
  MGI.TABSCHEMA = SI.TABSCHEMA
  AND MGI.TABNAME = SI.TABNAME
  AND MGI.IID = SI.IID
ORDER BY
  MGI.INDEX_SCANS DESC;



MON_GET_INDEX 期望接收以下参数:
表模式
表名
数据库分区号

对于单分区数据库,您可以对数据库分区号仅指定 -1(当前分区)或 -2(所有分区),结果是一样的。如果您省略了表名(即指定 NULL 或空字符串),那么为指定的模式中的所有表返回信息。如果您省略了表名和表模式,那么为数据库中的所有表返回信息。

清单 21 显示了来自类似于以上查询的样例结果。

清单 21. 来自 MON_GET_INDEX 表函数的结果                       
db2 -tf mon_get_index.sql

INDSCHEMA         INDNAME           INDEX_SCANS       INDEX_ONLY_SCANS
-------------------- -------------------- -------------------- --------------------
FECHNER           XDEPT2                       678               0
FECHNER           PK_DEPARTMENT                 245               0
FECHNER           XDEPT3                       0               0

3 record(s) selected.



MON_GET_INDEX 表函数和 db2pd 实用程序提供相同的索引度量指标(扫描索引和仅扫描索引的数量)。不过,您可能发现,处理 MON_GET_INDEX 表函数返回的结果集比计算 db2pd 实用程序的输出更加容易。


     回页首





结束语

未使用和很少使用的索引可能导致 UID 操作(UPDATE、INSERT 和 DELETE)和数据库维护活动(比如 RUNSTATS 和 REORG)的运行时间更长。这些索引还占用表空间容器和备份映像中的存储空间,您可以通过删除这些索引来更好地利用它们所占用的空间。本文展示了几种方法,用于识别在数据库中定义的未使用和很少使用的索引。您可以根据通过这些方法获得的信息删除不需要的索引。删除不必要的索引能够显著改善 DB2 LUW 数据库的性能。

你可能感兴趣的:(sql,应用服务器,db2,脚本,perl)