SQL 查询构成了 Informix® 数据库应用程序的主干。本文将讨论 Informix 的 SQL 查询调优指导原则,查看调优 SQL 查询时需要考虑的因素,同时还将探讨作者本人体验过的一些调优示例。第二部分将讨论访问方法、子查询以及区段管理对于性能的影响,解释如何可以通过理解索引层次、惟一性、分段和 PDQ 优先级来影响性能。并举例说明如何可以应用这些原则
 

简介

通常认为 SQL 查询的调优是程序员和开发人员的主要责任,但是数据库管理员也应积极参与该过程。数据库管理员参与 SQL 查询调优的主要好处之一是,他们可以提出不同的观点。程序员是从应用程序性能的角度来考虑问题的,而 DBA 考虑问题时理解了数据库本身,从而可以对数据库的布局、表和索引的安排,以及 Informix 和系统资源(包括数据分段、PDQ 优先级、CPU 时间、内存利用率和数据存储)的有效使用提出意见和建议。有时,程序员和开发人员就性能而言仅仅需要获取不同的查询视图,因此他们可以修改该查询,以获得更高的效率。

本文将介绍多种调优方法。 第 1 部分将介绍调优标准、工具以及一套通用方法。此外,还将讨论优化器的角色,其中包括 OPTCOMPIND 参数、查询命令和内部统计数据。

第 2 部分仍然是调优,我将介绍查询本身的性能考虑,其中包括访问方法、连接方法、表和索引的区段、索引层次、分段以及 PDQ 优先级。第 2 部分还将包含示例及分析。



调优标准

调优标准基本上是依据个人的业务需求而定的。一般,主要涉及系统和数据库的性能。那么您又该如何配置系统和数据库性能呢?有多方面的考虑,但是最关键的就是 响应时间资源利用率。响应时间指的是用户等待他们的请求(即 SQL 查询)得到完成的时间。资源利用率指的是执行 SQL 查询时,CPU、内存和磁盘等系统资源的使用。

通常,如果完成调优过程之后,调优之后的 SQL 查询的响应时间变短了,而系统资源的利用率降低了,那么您就可以充满信心地推断:您已经调优该 SQL 查询,并获得了更高的效率。如何测量 SQL 查询的响应时间和资源利用率呢?Unix 和 Informix 提供了一些工具和实用程序,帮助我们科学、定量地测量响应时间和资源利用率。




调优工具

Unix 工具

Time 和 timex

您可以使用 time 实用程序来报告一些系统操作的响应时间,例如文件传送、数据库的查询执行以及其他活动。下面的实例展示了如何使用 time 实用程序来测量一个简单数据库查询的响应时间:

sys3523:omcadmin > time dbaccess airgen_cm_db sel.sql
Database selected.
      		(count(*)) 
           	 5958
1 row(s) retrieved.
Database closed.
real    0m0.09s
user    0m0.01s
sys     0m0.06s

上面输出的最后一部分给出了详细的操作时间统计数据,该操作在本例中就是数据库查询:

  • “real”字段告诉您从查询开始到结束时所经过的时间。
  • “user”字段告诉您用户处理器为该操作而占用的 CPU 时间总和。
  • “sys”字段告诉您整个系统占用的 CPU 时间总和。

我们最感兴趣的是“real”字段;该字段表示操作的响应时间。让我们仔细查看一下上面的这个例子。通过输出,我们可以确定该查询的响应时间是 0.09 秒。

Timex 只是 time 实用程序的另一个变种,它将以更易读的格式显示时间。下面是同一查询的 timex 输出:

sys3523:omcadmin > timex dbaccess airgen_cm_db sel.sql
Database selected.
      (count(*)) 
            5958
1 row(s) retrieved.
Database closed.
real        0.09
user        0.02
sys         0.04


Vmstat

vmstat 实用程序深入挖掘系统并报告系统资源使用的统计数据,主要涉及运行的处理器、虚拟内存、磁盘 I/O、中断和 CPU。它还显示一行系统重启或启动以后的虚拟内存活动的摘要信息。下面是一个输出示例:

sys3523:omcadmin > vmstat 1 10
 procs     memory            page            disk          faults      cpu
 r b w   swap  free  re  mf pi po fr de sr m0 m1 m4 m5   in   sy   cs us sy id
 0 0 0 1959208 1294824 141 824 1 1 1  0  0  0  0  0  0  906  946  700  2  3 95
 0 0 0 1995568 1260288 0 46  0  0  0  0  0  0  0  0  0  834  386  213  0  0 100
 0 0 0 1995568 1260288 0 40  0  0  0  0  0  0  0  0  0  884  265  199  0  1 99
 0 0 0 1995568 1260288 0 40  0  0  0  0  0  0  0  0  0  834  325  186  0  0 100
 0 0 0 1995568 1260288 43 286 0 0  0  0  0  0  0  0  0  869 1682  242  0  1 99
 0 0 0 1995352 1260048 658 3503 0 0 0 0  0  0  0  0  0  827 21930 375  3 14 83
 0 0 0 1995408 1260240 662 3495 0 0 0 0  0  0  0  0  0  825 22010 387  4 13 83
 0 0 0 1995568 1260288 121 691 0 0 0  0  0  0  0  0  0  834 4310  261  1  3 96
 0 0 0 1995568 1260288 0 40  0  0  0  0  0  0  0  0  0  824  250  188  0  0 100
 0 0 0 1995568 1260288 0 40  0  0  0  0  0  0  0  0  0  824  365  214  0  0 100

vmstat 实用程序接收两个整数参数:time interval 和 count。Time interval 指定 vmstat 刷新的间隔,而 count 指定它将刷新的最大次数。如果未指定任何参数,vmstat 就只会列出系统的前一次统计数据,而且不会进行刷新。本例中,它所提供的统计数据是不准确的,也不是最新的。如果指定了 interval,vmstat 将立即反复按照最新的间隔总结系统活动的概要信息。如果给定了 count,vmstat 就会重复获取系统的统计数据,直到达到了所指定的 count 次数。

输出中最让我们感兴趣的是“r”、“po”和“id”字段。

  • “r”字段告诉我们系统就绪队列中有多少任务在等待要执行的资源。
  • “po”字段告诉我们当前内存中有多少页面被换出。如果该数字极其庞大,并持续增长,这通常表示物理存储器或 RAM 不足,您可能需要安装更多存储器。
  • “id”字段告诉我们当前使用了多少系统 CPU 资源。

这些字段一起将让您较好地了解当前系统资源的使用情况。

Informix 工具

Informix 提供的用来收集详细 SQL 查询计划和执行统计数据的最全面的工具是 SET EXPLAIN 实用程序。该实用程序将生成一个名为 sqexplain.out 的文件,并详细记录查询的每个执行步骤。此外,它还提供估算的查询成本,并预计查询结果。通过检查 SET EXPLAIN 输出文件,您可以确定是否可以采取措施来提高该查询的性能。以下示例展示了一个极其复杂查询的 set explain 输出:

QUERY:
------
SELECT --+AVOID_FULL(omchn)+AVOID_FULL(daphn)
                omchn.omc_hn_uanc,
                nvl(daphn.gtt_version,"0000000000000000000"),
                nvl(idachn.egt4_version,"0000000000000000000"),
                nvl(ihlrhn.hlr_timestamp,"00000000000000"),
                vsgw_hn.hn_igw_uanc,
                nvl(vsgw_hn.hn_igw_version, "00000000000000")
           FROM omchn, daphn, idachn, ihlrhn, vsgw_hn
          WHERE daphn.dap_hn_inst  = omchn.omc_hn_inst
            AND idachn.idac_hn_inst = omchn.omc_hn_inst
            AND ihlrhn.hlr_hn_inst = omchn.omc_hn_inst
            AND vsgw_hn.vsgw_hn_inst = omchn.omc_hn_inst
DIRECTIVES FOLLOWED:
AVOID_FULL ( omchn )
AVOID_FULL ( daphn )
DIRECTIVES NOT FOLLOWED:
Estimated Cost: 8
Estimated # of Rows Returned: 1
  1) root.idachn: SEQUENTIAL SCAN
  2) root.daphn: INDEX PATH
    (1) Index Keys: dap_hn_inst   (Serial, fragments: ALL)
        Lower Index Filter: root.daphn.dap_hn_inst = root.idachn.idac_hn_inst
NESTED LOOP JOIN
  3) root.vsgw_hn: SEQUENTIAL SCAN
NESTED LOOP JOIN
  4) root.omchn: INDEX PATH
        		Filters: root.vsgw_hn.vsgw_hn_inst = root.omchn.omc_hn_inst
    		(1) Index Keys: omc_hn_inst   (Serial, fragments: ALL)
      Lower Index Filter: root.idachn.idac_hn_inst = oot.omchn.omc_hn_inst
NESTED LOOP JOIN
 	 5) root.ihlrhn: INDEX PATH
    		(1) Index Keys: hlr_hn_inst   (Serial, fragments: ALL)
      Lower Index Filter: root.ihlrhn.hlr_hn_inst = root.omchn.omc_hn_inst
NESTED LOOP JOIN

可将以上输出分成三个部分:

  • 第一部分展示了查询语法。
  • 第二部分展示了该查询的估算成本。
  • 第三部分详细地解释了所执行查询的每一步。

我们最感兴趣的是第二和第三部分。估算成本是优化器用来比较查询计划的成本单元。这些单元不直接转换成时间;它们表示的是典型磁盘访问的相对时间。

优化器将选择该查询计划,因为其执行的估算成本是所有被估计划中最低的。与具有较低估算成本的运行相比,具有较高估算成本的查询的运行时间通常更长一些。第三部分对于查询调优是至关重要的,因为它提供了大量有用的信息,例如查询所用的数据访问方法和连接方法。以上示例展示了如何使用连续扫描和索引来进行数据检索,以及如何使用嵌套循环连接方法来连接所有表。稍后我将在本文中详细讨论这一点。

该实用程序易于使用。如果需要知道某个 SQL 查询的详细查询执行计划,只需在最初的查询之前添加如下所示 SET EXPLAIN ON 语句即可:

set explain on;
select count(*) from acg;

然后,Informix 服务器将在用户 home 目录中生成一个名为 sqexplain.out 的文件,正如上面谈到的,其中将记录详细的查询执行计划及其成本。该文件是累积的,换言之,如果 SET EXPLAIN ON 语句之后有多个 SQL 查询,每个查询的执行计划及其成本都会被追加到文件中,直到将之删除。同样,对于存储过程,您需要对原始的存储过程执行 UPDATE STATISTICS,以获取详细的执行计划,因为在更新统计数据时,存储过程只可以更新它们的查询执行计划。例如,如果您需要查看存储过程 dap_int 的详细执行计划,就需要进行下列操作:

set explain on;
update statistics for stored procedure dap_int();

从版本 9.3 开始,该实用程序得到了较好的提高;您可以获取详细的查询执行计划,而不必执行该查询。这使您可以在真正的生产环境中获取查询执行计划。要使用这项新功能,需要在 SET EXPLAIN ON 语句中使用关键字 AVOID_EXECUTE,如下所示:

set explain on avoid_execute;
select count(*) from act;

关于如何使用该实用程序的详细信息,请参阅 IBM Informix Dynamic Server Administrator's Guide, Version 9.4



通用方法

那么我们如何在 SQL 查询的调优中应用那些工具呢?不同的人可能有不同的方法,但是,一般应按照下列方法和步骤进行:

  1. 收集关于原始 SQL 查询的统计数据。在这一步中,需要使用上面讨论的工具来获取该查询的统计数据:其响应时间、详细的执行计划和成本,以便稍后进行深入分析。
  2. 分析统计数据。在这一步中,需要挖掘上面所收集的统计数据,并仔细查看查询执行计划。如上所述,性能是查询调优中主要关心的问题。在检查查询计划时,需要考虑影响性能的所有因素:访问方法、连接方法、子查询、表和索引区段、表和索引分段等。稍后我将在本文的第 2 部分中逐个详细地讨论这些因素。
  3. 设置测试环境。这是一个极其重要的步骤。就硬件和软件配置而言,应将测试环境设置为与运行查询的生产系统完全相同或极其相似。例如,如果生产机器有 6 个 400HM 的 CPU,那么测试机器也应该有 6 个 400HM 的 CPU;否则,随后的测试将是无效且不可靠的。请记住,所有那些查询最终都将在生产中运行。
  4. 修改并测试新的查询。这是调优中的一个主要步骤,同时也是最冗长乏味的一个步骤。一次修改原始查询的一个地方,并进行测试,以查看是否提高了性能(减少了响应时间)。记录测试细节,例如您所做的修改、响应时间和执行计划。进行了修改之后,如果查询性能没有优于您原始查询的,就撤销修改。测试必须是有效且可靠的;换言之,测试必须是可重复的。例如,如果对同一查询进行两次相同的测试,第一次输出了一个极佳的响应时间(比如说 10 秒),但是第二次,响应时间就增加到 30 秒,那么您的测试就是不可重复的,因为响应时间的差异过大。您需要重新检查测试过程,并识别那两次测试之间的差异。如果测试是可重复的,那么测试结果之间的差异应该是极小的。
  5. 分析测试结果。在分析测试结果时,我们需要检查测试结果的有效性和可靠性。我们需要检查硬件、软件、工作负载以及其他所有因素,以确保测试结果是有效且可靠的。
  6. 在生产系统中实现改进。在实现之前,您需要进行最新的详细检查,并确保新的查询在生产中不会导致任何问题。



优化器及其角色

与 Oracle 和 SQL Server 等其他关系数据库管理系统一样,Informix 也有内部优化器,负责选择最佳的查询执行计划。分析了 SQL 查询之后,优化器将通过分析磁盘 I/O 和 CPU 成本等因素,考虑所有执行查询的可能方法。然后,它会用由下至上、宽度优先(bottom up, breadth-first)的搜索策略,同时构造所有可行的计划。

换言之,优化器首先将构造所有可能的连接计划,然后删除所有花费较高的冗余对(redundant pair),这些冗余对是包含相同表以及产生与另一连接对相同的行集的连接对。如果某查询使用了附加的表,那么优化器就将每个剩余对连接一个新表,以形成所有可能的连接三元组,排除代价更为昂贵的冗余三元组,如此一来就连接了每个附加的表。当生成了非冗余的可能连接组合集时,优化器就选择看上去具有最低执行成本的计划。例如,优化器必须确定是否应使用索引。如果该查询包含了连接,那么优化器就必须确定连接计划(hash、sort merge 或 nested loop),以及评估或连接表的次序。

优化器将根据在每个表中检索的行数来评估查询成本。被评估的行数则基于 WHERE 子句里所使用的每个条件表达式的选择性。优化器使用 UPDATE STATISTICS 所生成的数据分布信息来计算查询中筛选器的选择性。然而在缺乏数据分布信息的情况下,优化器将基于表索引计算不同类型筛选器的选择性。例如,如果索引列包含字面值和 NULL 值,那么其选择性就等于索引中不同的键的数目。关于在缺乏数据分布的情况下,优化器用以计算选择性的详细表,请参阅 Performance Guide 的第 10 章。但是,使用该方式计算的选择性不如使用数据分布计算的选择性准确。

因此,选择性估算的准确性显然基于您运行 UPDATE STATISTICS 的频率。如果频繁地运行 UPDATE STATISTICS,优化器就会更准确地计算出选择性,因为每次运行 UPDATE STATISTICS 时,都会更新数据分布,除了运行带有 low 选项的 UPDATE STATISTIC 之外。

当优化器创建查询计划时,它将使用下列系统目录信息:

  • 从最新的 UPDATE STATISTICS 语句起,表中的行数。
  • 将哪一列限定为惟一的。
  • 当请求 UPDATE STATISTICS 语句中带有 MEDIUM 或 HIGH 关键字时,列值的分布情况。
  • 包含行数据的磁盘页的数目。
  • 存在于表上的索引,包括它们索引的列,哪些是升序或降序的,以及哪些是群集的。
  • 索引结构的深度(衡量执行索引查找所需的工作量)。
  • 索引项占用的磁盘页数目。
  • 索引中的惟一项数目,可用于估算等式筛选器所返回的行数。
  • 索引列中第二大和第二小的键值。

优化器的行为受三个关键因素的影响:Informix 配置文件中 OPTCOMPIND 参数的值、查询命令和内部统计数据的准确性。

OPTCOMPIND 参数

OPTCOMPIND 是一个环境变量,或者是 Informix 配置文件中的一个参数。优化器用其值来选择数据访问方法。其值是 0、1 和 2 这三个中的一个,分别表示下列意思:

  • 如果将其值设置为 0,那么当存在合适的索引时,优化器就选择索引扫描而非表扫描,甚至不考虑估算成本。
  • 如果将其值设置为 1,那么当事务隔离模式不是可重复读(Repeatable Read)时,优化器的行为就与值为 0 时一样。如果事务隔离模式是可重复读(Repeatable Read),那么优化器会将其选择完全基于估算成本。
  • 如果将其值设置为 2,优化器就会使用估算成本来确定执行计划,而不管事务隔离模式如何。

您可以将 OPTCOMPIND 设置为环境变量,也可以设置为配置文件中的参数,但是,将之设置为参数将优先执行。

查询命令

影响优化器的另一种方式就是使用查询命令。查询命令是 SQL 查询中的提示,指示优化器如何执行查询。一共有 4 种类型的查询命令,如下所示:

  • 访问计划指示,强制优化器使用指定的访问方法进行数据检索,要么是连续扫描,要么是索引扫描。
  • 连接次序指示,强制优化器按照指定次序连接表。
  • 连接计划指示,强制优化器使用指定的连接方法来连接查询中的表,要么是嵌套循环连接、分类合并连接,要么是动态哈希连接。
  • 目标指示,强制优化器使用指定的规则来返回查询结果。

关于如何使用那些指示的详细信息,请参阅 IBM Informix Dynamic Server Performance's Guide, Version 9.4 的第 11 章。

内部统计数据

通过内部统计数据,我指的是系统目录中的统计数据,优化器可以确定最低成本的查询执行计划。为了确保优化器选择最佳的查询计划,使内部统计数据保持为最新并且最准确的十分重要。数据库服务器将初始化表、索引、存储过程和触发器等数据库对象的统计配置文件,并在创建数据库表时,将数据分布置于系统目录中,但不自动更新那些统计数据。

要使统计配置文件保持最新,您需要定期运行 UPDATE STATISTICS;否则,您系统的统计配置文件可能无法反映您系统的当前状态,而优化器则可能无法在众多查询执行计划中做出正确选择。UPADTE STATISTICS 有三种运行模式,一般每次执行修改了大量表数据的大量批作业之后,以及每次向表添加索引之后,您都需要运行 UPDATE STATISTICS。关于如何执行 UPDATE STATISTICS 的详细信息,请参阅 IBM Informix Dynamic Server Administrator's Guide, Version 9.4。其经验法则就是:越频繁地运行 UPDATE STATISTICS,系统的统计配置文件将越新、越准确,而优化器则越可能选择最佳的查询执行计划。

虽然如上面所谈论的,优化器的行为受 OPTCOMPIND 和查询命令的影响,但优化器通常是基于下列指导方针来选择查询计划:

  • 如果查询从一个表检索大量数据,优化器就不会使用索引。例如,如果您公司的客户非常均匀地分布在所有 50 个州中,而您需要检索除了纽约之外的每个州的客户信息,那么您需要执行下列查询:
    SELECT * FROM customer WHERE STATE <> "NEW YORK";
    

    优化器将立刻检测您可能要检索的表中的 98% 的数据,它认为连续读或扫描表要比遍历索引(以及随后的数据页)更加高效,然后它将检索相关数据。
  • 如果表上定义了多个索引,那么优化器就使用可以排除表中最多数据的索引。例如,如果您公司拥有 200,000 位纽约的客户,而只有约 1000 位客户在同一天发出了订单,比如 1997 年 1 月 20 日,那么您可以使用下列查询来获取他们的姓名和地址:
    SELECT name, address FROM customer 
    WHERE state = "NEW YORK" AND order_date = "01/20/97"
    

    优化器极可能会选择使用 order_date 上的索引,而不是选择使用 state 上的索引。
  • 如果查询中没有指示,那么优化器通常会首先从带有最多限制性筛选器的表中检索数据。让我们查看下列查询:
    SELECT * FROM customer, orders
    WHERE customer.customer_num = orders.customer_num
    AND   customer.state = "NEVADA";
    

    本例中,优化器所做的第一件事就是评估 state 等于 NEVADA 的这个条件,因为这将排除表中的大量数据行。然后,它将连接两个表。其思想就是尽可能多地减少数据库服务器的工作负载。如果优化器首先连接两个表,那么连接结果可能十分庞大,并可能使用大量系统资源,如 CPU 和内存。如果您拥有 1,000,000 位活动客户,平均每人每月发出一份订单,那么连接结果将至少返回 1,000,000 条记录,这肯定会损害您的系统性能。
  • 如果连接列上不存在索引,那么优化器将选择动态哈希连接。在前一个实例中,如果 customer.customer_num 和 orders.customer_num 都没有被索引,那么优化器将选择动态哈希连接来作为最佳的执行计划。
  • 如果满足下列条件,优化器将选择嵌套循环连接:
    • 在数据库服务器使用所有表筛选器之后,从外部表检索出的行数将很少,而内部表具有一个可用于执行连接的索引。
    • 可以用最外面的表上的索引,以 ORDER BY 子句的次序返回行,从而消除排序的需要。

第 2 部分: 调优 Informix SQL

简介

调优 Informix SQL 时要考虑许多性能问题。其中的两个主要问题是 访问方法连接方法。本文中,我将讨论您应该选择什么,以及如何确定每种选择的成本。一旦理解了自己的选择,就可以通过调整 OPTCOMPIND 参数,或者通过添加查询指示或索引,来影响优化器,从而选择最佳的查询执行计划。



访问方法

访问方法是指优化器从数据库表中读取(更确切地说是检索)数据的方法。基本上有两种方法。

  • 最简单的方法是连续读取表中的数据,就是按照我们通常调用它的方法来执行表扫描。当无论如何都必须读取表中大多数数据时,或者当表没有索引时,优化器就会选择执行表扫描。
  • 另一种方法就是使用索引。如果一些列上有索引,那么优化器也许能使用键索引扫描(key-only index scan)。

优化器将比较每种方法的成本,确定使用最好的一种。它将评估查询执行成本的方方面面,例如所需的磁盘 I/O 操作数目、将被检索的行数、排序成本等。

如果表中的数据进行了群集化处理,或者是有物理次序的,那么表扫描的成本就可能相当低。当请求磁盘页上的第一行时,就将该磁盘页读入内存中的一个缓冲页(buffer page)。下次请求该页上的数据时,就不需要再从磁盘进行读取该页。对该页上的后续行的请求都可以在这个缓冲区中得到满足,直到将该页上的所有行都处理完为止。当已经用完一个页面时,就必须读入下一行集的页。

为了加速这个过程,当第一个页面仍然在内存中时,Informix 就可以将下一数据页读入内存。为了有效利用这项功能,必须在配置文件中正确设置两个参数:RA_PAGES 和 RA_THRESHOLD。关于如何设置这些参数的细节,请参阅 IBM Informix Dynamic Server Administrator's Guide, Version 9.4。如果您数据库中的表只占用了少数几个区段,那么连续行的磁盘页将被放置在磁盘中的连续位置上,在读取数据页时,磁盘访问臂(access arm)就不必移动太多。此外,延时成本也会十分低。

对于小型表(有 1,000 行或更少行)来说,表扫描的成本不会很昂贵,尤其在 SQL 或应用程序中重复使用某个表的时候,这样,该表会一直保存在内存中。实际上,对内存中的表进行表扫描要比通过索引搜索同一表快得多,尤其是在使用 Informix 轻型扫描机制时。但是如果某个表有 100,000 或更多的行,那么重复的连续扫描将对性能产生致命影响。您可能需要考虑向该表添加索引,并将索引用作从表中访问数据的方法。

实际上,除了群集表之外,几乎所有表中的数据都不是有序的。拥有繁重事务的系统就是如此,例如银行系统。该例中,表扫描极其昂贵,因为 Informix 引擎必须随机地访问数据。当以与磁盘上其物理次序无关的顺序读取表中的行时,磁盘 I/O 成本更高。因为不是从磁盘连续读取页面的,所以在读取每个页面之前,都需要进行查找并存在循环延迟。因此,与按顺序读取相比,按随机顺序读取数据时的磁盘访问时间要长得多。更好的方法就是使用键索引扫描。

索引是用来定位数据页的顺序指针或键的集合。索引扫描是从下面的根页向叶子页面进行的。因为根页使用十分频繁,所以它通常驻留在内存缓冲区中。用以读取叶子页面的成本取决于索引的大小、查询的筛选器以及索引的惟一性。如果每个值仅在索引中出现一次,且查询是一个连接(join),那么所连接的每一行都需要非连续地查找索引,然后非连续地访问表中相关联的行。然而,如果每个不同的索引值都有许多重复的行,而所关联的表是高度群集的,那么通过索引添加的连接成本可能很低。

虽然索引项是连续的,但是不保证包含相邻索引项的行将驻留在同一数据页上。大多数情况下,对于通过索引定位的每一行,都必须进行单独的磁盘访问来读取该页。如果某个行的大小大于页大小(即大于 2 KB),那么包含该行的页面可能被置换出内存。换言之,在后续数据页被读入内存并被处理之前,它们可能已经从缓冲区中删除,并写回磁盘。那么,在以后处理这些后续页时,还必须再次将它们读入内存。

优化器将决定是否可以使用索引来评估筛选器。为此,索引列是一个包含索引的单列,或者是复合索引中指定的第一个列。如果只需要索引中包含的值,而不需要读取这些行。那么,每次可以直接从索引中读取值时,省略数据页的查找方法都要快得多。

在下列情况下,优化器将使用索引:

  • 当索引了一列时。数据库服务器首先通过查找索引中的行,定位表中的相关行。本例中,键索引将比表扫描快得多。
  • 当索引了一列,且要进行比较的值是另一表中的一行(连接表示)时。数据库服务器使用索引来查找匹配的值。
  • 当处理 ORDER BY 子句时。如果该子句中所有的列都是以用单个索引要求的顺序出现的,那么数据库服务器就使用索引来按顺序读取行,因而避免了排序。
  • 当处理 GROUP BY 子句时。如果该子句中所有的列都出现在一个索引中,那么数据库服务器就从索引中读取具有相等键的分组,而无需在从其表中检索行之后进行附加的处理。

如何为查询判定哪一种访问方法是最有效的呢?这没有固定的标准,因为每个组织的业务需求都是不同的。然而,有一些通用的指导原则。如果查询中的表极其小(通常少于 1,000 行)或者是群集表,而且查询根本没有选择性的话,那么使用表扫描会更好一些。但是如果这些表极其庞大,有许多行(比如几百万行),而且查询具有一定的选择性的话,那么使用索引就好得多。

优化器在选择访问方法方面通常非常智能,但是如果它没有按您所期望的方式进行,那么您可以通过使用查询命令影响其行为,强迫优化器选择您认为最好的访问方法。您可以分析查询执行计划输出,以确定这是否是必需的。




连接方法

如果查询包含不止一个表,那么就应该使用筛选器来连接那些表,以避免笛卡尔(Cartesian)连接。当评估查询执行计划时,优化器计算并比较每种连接方法的成本,然后选择要使用的最佳方法。最常用的连接方法就是嵌套循环连接、分类合并连接和动态哈希连接。现在,让我们详细查看每一种连接。

嵌套循环连接

在嵌套循环连接中,将扫描第一个(或外部)表,以查找满足查询规则的行。对于在外部表中找到的每一行,数据库服务器将在第二个(或内部)表中搜索其相应的行。通过索引扫描还是表扫描来访问外部表则取决于该表。如果有筛选器,数据库服务器首先会应用它们。如果内部表没有索引,那么数据库服务器就会将在表上构建索引的成本与连续扫描的成本进行比较,然后选择成本最低的那一种方法。总成本取决于连接列上是否有索引。如果连接列上有一个索引,那么其成本会相当低;否则,数据库服务器就必须对所有表(外部和内部表)执行表扫描。

分类合并连接

当连接表的连接列上没有可用索引时,通常使用该连接方法。连接开始之前,如果有筛选器,那么数据库服务器首先会应用它们,然后对连接列上每个表中的行进行分类。一旦实现了对行的分类,连接两个表的算法就十分容易:数据库服务器仅仅连续地读取两个已分类表,并合并所有相匹配的行。因为该方法在进行表连接之前,必须将所有的连接表分类,所以其成本通常极其高。在 IDS 版本 7 以及更高版本中,该连接方法已被动态哈希连接所取代。

动态哈希连接

正如上面所提到的,动态哈希连接是 IDS 版本 7 以及更高版本中的新连接方法。当一个或多个连接表上没有索引时,或者当数据库服务器必须从所有连接表中读取大量行时,就使用这种方法。在该方法中,需要扫描其中的一个表,通常扫描较小的那个表,用它在内存中创建一个哈希表。通过哈希函数,将具有相同哈希值的行放在一个 bucket 中。在扫描完第一个表并将它放在哈希表中之后,就扫描第二个表,并在哈希表中查找该表中的每一行,看是否可以进行连接。如果连接中有更多表,那么数据库服务器将对每个连接表执行相同的操作。

动态哈希连接包含两个动作:构建哈希(或者是我们所称的构建阶段),以及探测哈希表(或探测阶段)。在构建阶段,数据库服务器读取一个表,并且在应用所有现有筛选器之后,在内存中创建一个哈希表。可以在概念上将哈希表认为是一系列的 bucket,每个 bucket 所拥有的地址是通过应用哈希函数从键值导出的。数据库服务器不会在特定的哈希 bucket 中对键进行分类。在探测阶段,数据库服务器将读取连接中的其他表,如果存在筛选器,就应用它们。在满足筛选器条件的每个行中,数据库服务器将对键应用哈希函数,并探测哈希表以查找匹配的键值。哈希连接通常比分类合并连接快,因为它没有涉及到分类操作。




子查询

子查询是 SQL 语句里的 SQL 语句,更确切地说,是嵌套的 SQL 语句。这些 SQL 语句可以用于许多情况下,如 SELECT、DELETE、UPDATE 和 INSERT。例如,下列 SQL 语句使用子查询来计算薪水最少的雇员数目:

select count(*) form employee
where salary = (select min(salary) from employees);

有两种类型的子查询: 不相关的 相关的。不相关的子查询不依靠主查询来获取信息;因此,它们增加了主查询的能力和灵活性。另一方面,相关子查询需要一些信息来定位内部表中的行,这暗示在选择符合该查询条件的每一行时,数据库服务器都必须运行相关查询。以上查询包含一个相关子查询,因为主查询和子查询依靠于同一个表,即 employees 表。相关子查询将极大地降低数据检索的速度,对于拥有几百万个行但没有索引的大型表的非选择性查询更是如此。我们需要注意相关子查询,并尽可能地避免使用它们。



表和索引的区段

区段(extent)是一块物理位置连续的空间。然而,无法保证区段本身也是连续的。因此,如果一个表或索引只有一个区段,那么它会将其所有的数据或键保存在一块物理空间中。否则,其数据或键将分散在该表或索引所占用的所有区段中。物理数据或键的连续性对于表扫描或键-索引扫描的速度十分重要。当数据连续时,用于访问磁盘上数据的时间最少,数据库服务器可以更快地读取数据。这对于键来说也是一样的。如果表拥有太多区段,那些区段就很可能是交错的。这通常会损害性能,因为当您为某个表检索数据时,磁盘头必须查找属于该表的多个非连续的区段,而不是查询一个包含连续物理页面的大型区段。这会相当大地减慢磁盘查找速度。

如果表或索引有十个或更多的区段,那么您需要考虑重新构建除大型分段表之外的表和索引,合并其区段,以获得更好的性能。关于如何对表和索引进行评估并为它们分配区段大小的细节,请参阅 IBM Informix Dynamic Server Performance's Guide, Version 9.4




索引层次和惟一性

索引的层次和惟一性也会影响数据的访问速度。索引层次越多,Informix Dynamic Server 就必须执行越多的读取来获取索引叶子节点,并且就将花更多的时间来获得真正的数据。此外,如果在执行数据插入、删除或更新操作的过程中拆分或合并叶子节点,那么可能要花费更长的时间来让整个索引适应其更改。例如,如果一个索引只有两层,那么只需调整这两个层即可,但是,如果它有四个层,那么如果在索引的任一部分中进行了节点拆分或合并,那么就需要对所有的四层进行相应的调整。四层索引的调整时间当然就比仅仅两层索引的调整时间要长得多。OLTP 环境中尤其如此,该环境中的事务十分巨大,并且不断地要插入、删除和更新一些数据。因此,如果索引超过四层,那么您可能需要考虑删除一些层或用较少的层次来重新构建它,以便获得更好的性能。

索引的惟一性是指一个索引拥有多少个重复键。一个高度重复的索引可能会严重影响访问、更新和删除数据的速度。假设 customer 表中的 customer_type 列上有一个索引,那么只会有 5 种可能的 customer_type 代码。如果该表有一百万行,那么平均 200,000 行就会有相同的 customer_type 代码。B-树将存储键值,以及一个指向每个物理行的指针列表。在必须删除或更新任一键值时,就会出现问题。Informix Dynamic Server 必须搜索所有重复键,删除或更新它们,以找出正确的键,这样的话,要搜索 200,000 个键!

理想情况是,索引中的每个键值都是惟一的。现在,我们如何查明索引是不是惟一的呢?可以通过查询两个系统表来获得这方面的信息:systables 和 sysindexes。下面是该查询:

select nrows, nunique from systables a, sysindexes b
where a.tabid = b.tabid
and idxname = index_name




分段和 PDQ 优先级

分段和 PDQ 优先级也对于数据的检索速度有极大的影响,并有助于表扫描。分段是一种将数据库表和索引巧妙地划分成更小单位(叫作“分段表”)的方法;每个分段表都是表或索引中的一组行或索引键。用于分段的算法称作“分布模式”。Informix Dynamic Server 支持两种分布模式:轮循和基于表达式的分布模式。轮循分布模式使用 Informix 的内部规则将表和索引分段。而基于表达式的分布模式则使用用户定义的规则将表和索引分段。

分段的最终目标就是通过直接访问来包含满足 SQL 查询的数据分段表,减少检索时间。例如,如果将一个 100,000 行的大型表分成 10 个分段表,每个分段表包含 10,000 行,那么 Informix Dynamic Server 就可以直接进入包含查询所请求数据的分段表中,不用对其他分段表进行扫描。这将限制 IDS 仅扫描 100,000 行中的 10,000 个相关行。那是一个极大的节省,不是吗?关于分段的细节,请参阅 IBM Informix Dynamic Server Administrator's Guide, Version 9.4

当应用 PDQ 优先级时,运用分段存储工作得最好。PDQ 优先级的工作原理是“各个击破(divide and conquer)”,Informix Dynamic Server 将复杂查询拆散成多个较小的部分。将每个小部分分配给不同的线程,然后,每个线程则并行地执行部分查询。当它们完成属于它们的那部分查询处理时,数据库服务器就重组结果数据集,并对其进行处理,然后将最后的答案展示给终端用户。因为 PDQ 优先级使用比常规方法更多的线程来处理一个查询,所以它将缩短数据访问时间。接下来的问题是,我们如何维持系统和 Informix 资源的使用平衡,使它不会受损害,更确切地说是不会减慢非 PDQ 查询的执行呢?这在 OLTP 环境中尤为重要,该环境中的事务十分巨大,而且查询响应时间十分关键。



示例和分析

现在,让我们获得一些我在工作中收集的具体的 SQL 调优示例,并看一看如何可以应用上面所讨论的这些调优指导原则。

我们的公司是一家通信公司,其数据库被用来存储通信性能管理数据、事件订阅列表以及网络元素配置信息。数据库中包含几百个表,大约有 40 GB 的数据。它运行在有 6 个 CPU 和 4 GB RAM 的 Sun Solaris 2.8 操作系统上。硬件平台是一台 Sun Enterprise 3500,带有 6 个 18 GB 的磁盘和两个相互镜像的 36 GB 的磁盘。

示例 1

原始查询及其执行计划:

QUERY:
------
SELECT     UNIQUE MNE.MSO_ID, MNE.NE_INST, ANE.MSO_ID, 1
   FROM      NE MNE, NE ANE, CELL, NEIGH, CELL NCELL
 WHERE      MNE.NE_TYPE = 0 {ACG}
         AND  CELL.ACG_INSTANCE   = MNE.NE_INST
         AND  NEIGH.ADV_CELL_INST = CELL.CELL_INSTANCE
         AND  NCELL.CELL_INSTANCE = NEIGH.NEIGH_CELL_INST
         AND  ANE.NE_INST = NCELL.ACG_INSTANCE
         AND  ANE.MSO_ID != MNE.MSO_ID
         AND  MNE.WORKSPACE_ID   = 41
         AND  ANE.WORKSPACE_ID   = 41
         AND  CELL.WORKSPACE_ID  = 41
         AND  NEIGH.WORKSPACE_ID = 41
         AND  NCELL.WORKSPACE_ID = 41
Estimated Cost: 13555
Estimated # of Rows Returned: 38
  1) root.cell: INDEX PATH
    (1) Index Keys: workspace_id tz_inst   (Serial, fragments: ALL)
        Lower Index Filter: root.cell.workspace_id = 41
  2) omcadmin.mne: INDEX PATH
        Filters: omcadmin.mne.ne_type = 0
    (1) Index Keys: workspace_id ne_inst   (Serial, fragments: ALL)
        Lower Index Filter: (root.cell.acg_instance = omcadmin.mne.ne_inst AND omcadmin.m
ne.workspace_id = 41 )
NESTED LOOP JOIN
  3) root.neigh: INDEX PATH
    (1) Index Keys: workspace_id adv_cell_inst neigh_cell_inst   (Key-Only)  (Serial, fra
gments: ALL)
        Lower Index Filter: (root.neigh.adv_cell_inst = root.cell.cell_instance AND root.
neigh.workspace_id = 41 )
NESTED LOOP JOIN
  4) omcadmin.ncell: INDEX PATH
    (1) Index Keys: workspace_id cell_instance   (Serial, fragments: ALL)
        Lower Index Filter: (omcadmin.ncell.cell_instance = root.neigh.neigh_cell_inst AN
D omcadmin.ncell.workspace_id = 41 )
NESTED LOOP JOIN
  5) omcadmin.ane: INDEX PATH
        Filters: omcadmin.ane.mso_id != omcadmin.mne.mso_id
    (1) Index Keys: workspace_id ne_inst   (Serial, fragments: ALL)
        Lower Index Filter: (omcadmin.ane.ne_inst = omcadmin.ncell.acg_instance AND omcad
min.ane.workspace_id = 41 )
NESTED LOOP JOIN

优化的查询及其执行计划:

QUERY:
------
select       unique mne.mso_id, mne.ne_inst, ane.mso_id, 1
 from        ne mne, ne ane, cell, neigh, cell ncell
where      cell.workspace_id = 41
         and  cell.acg_instance = mne.ne_inst
         and  cell.workspace_id = mne.workspace_id
         and  mne.ne_type = 0
         and  mne.mso_id !=ane.mso_id
         and  mne.workspace_id=ane.workspace_id
         and  ane.ne_inst=ncell.acg_instance
         and  cell.cell_instance = neigh.adv_cell_inst
         and  ncell.cell_instance = neigh.neigh_cell_inst
         and  cell.workspace_id = ncell.workspace_id
         and  ncell.workspace_id = neigh.workspace_id
Estimated Cost: 6555
Estimated # of Rows Returned: 38
  1) root.cell: INDEX PATH
    (1) Index Keys: workspace_id tz_inst   (Serial, fragments: ALL)
        Lower Index Filter: root.cell.workspace_id = 41
  2) omcadmin.mne: INDEX PATH
        Filters: omcadmin.mne.ne_type = 0
    (1) Index Keys: workspace_id ne_inst   (Serial, fragments: ALL)
        Lower Index Filter: (root.cell.acg_instance = omcadmin.mne.ne_inst AND root.cell.
workspace_id = omcadmin.mne.workspace_id )
NESTED LOOP JOIN
  3) root.neigh: INDEX PATH
    (1) Index Keys: workspace_id adv_cell_inst neigh_cell_inst   (Key-Only)  (Serial, fra
gments: ALL)
        Lower Index Filter: (root.cell.cell_instance = root.neigh.adv_cell_inst AND omcad
min.mne.workspace_id = root.neigh.workspace_id )
NESTED LOOP JOIN
  4) omcadmin.ncell: INDEX PATH
    (1) Index Keys: workspace_id cell_instance   (Serial, fragments: ALL)
        Lower Index Filter: (omcadmin.ncell.cell_instance = root.neigh.neigh_cell_inst AN
D omcadmin.mne.workspace_id = omcadmin.ncell.workspace_id )
NESTED LOOP JOIN
  5) omcadmin.ane: INDEX PATH
        Filters: omcadmin.mne.mso_id != omcadmin.ane.mso_id
    (1) Index Keys: workspace_id ne_inst   (Serial, fragments: ALL)
        Lower Index Filter: (omcadmin.ane.ne_inst = omcadmin.ncell.acg_instance AND root.
cell.workspace_id = omcadmin.ane.workspace_id )
NESTED LOOP JOIN

正如您可以从以上输出中观察到的,我们进行的惟一更改就是添加了一个条件来限制原始查询(为 cell 表指定 workspace_id),并将原始查询中的所有表连接在一起。那些更改将原始查询的成本缩减了一半,同时也极大地减少了查询响应时间。

示例 2

原始查询及其执行计划:

QUERY:
------
select min(ds0_start)
   from srate
  where srate.line_instance = 99930
    and srate.workspace_id = 41
    and srate.ds_instance in ( select ds_inst from ds
                                    where stream_type = 10
                                      and workspace_id = 41)
Estimated Cost: 5175
Estimated # of Rows Returned: 1
  1) root.srate: INDEX PATH
        Filters: root.srate.ds_instance = ANY <subquery>
    (1) Index Keys: workspace_id line_instance   (Serial, fragments: ALL)
        Lower Index Filter: (root.srate.line_instance = 99930 AND root.srate.workspace_id
 = 41 )
    Subquery:
    ---------
    Estimated Cost: 5171
    Estimated # of Rows Returned: 6400
      1) root.ds: INDEX PATH
            Filters: root.ds.stream_type = 10
        (1) Index Keys: workspace_id owner_ne   (Serial, fragments: ALL)
            Lower Index Filter: root.ds.workspace_id = 41

优化的查询及其执行计划:

QUERY:
------
select       min(ds0_start)
   from      srate , ds
 where     srate.line_instance = 99930
        and   srate.workspace_id = 41
        and   srate.ds_instance = ds.ds_inst
        and   ds.stream_type = 10
        and   srate.workspace_id = ds.workspace_id
Estimated Cost: 7
Estimated # of Rows Returned: 1
  1) root.srate: INDEX PATH
    (1) Index Keys: workspace_id line_instance   (Serial, fragments: ALL)
        Lower Index Filter: (root.srate.line_instance = 99930 AND root.srate.workspace_id
 = 41 )
  2) root.ds: INDEX PATH
        Filters: root.ds.stream_type = 10
    (1) Index Keys: workspace_id ds_inst   (Serial, fragments: ALL)
        Lower Index Filter: (root.srate.ds_instance = root.ds.ds_inst AND root.srate.work
space_id = root.ds.workspace_id )
NESTED LOOP JOIN

这里我们所做的惟一更改就是去掉了原始查询中的子查询。我们直接将两个表连接在一起,而不再使用子查询。因为该连接是在索引上执行的,所以其成本要低得多,而数据访问则快得多。

示例 3

原始查询及其执行计划:

QUERY:
------
SELECT workspace_impctlog.ne_instance, 1, br_status
        FROM   workspace_impctlog, ne
       WHERE   workspace_impctlog.workspace_id = 40
         AND   ((workspace_impctlog.impct_type = 10
                 AND workspace_impctlog.trans_type =! 2)
                  OR workspace_impctlog.impct_type = 30)
         AND   workspace_impctlog.ne_instance = ne.ne_inst
         AND   ne.workspace_id = 40
         AND   ne.ne_type =! 8
         AND   ne.ne_inst NOT IN
              (SELECT ne_inst from ne
               WHERE  ne.workspace_id = 40
                        and ne_type = 8)
Estimated Cost: 7880
Estimated # of Rows Returned: 1
  1) root.workspace_impctlog: SEQUENTIAL SCAN
        Filters: (root.workspace_impctlog.workspace_id = 40 AND ((root.workspace_impctlog
.impct_type = 10 AND root.workspace_impctlog.trans_type != 2 ) OR root.workspace_impctlog
.impct_type = 30 ) )
  2) root.ne: INDEX PATH
        Filters: root.ne.ne_type != 8
    (1) Index Keys: workspace_id ne_inst   (Key-First)  (Serial, fragments: ALL)
        Lower Index Filter: (root.workspace_impctlog.ne_instance = root.ne.ne_inst AND ro
ot.ne.workspace_id = 40 )
        Key-First Filters:  (root.ne.ne_inst != ALL <subquery> )
NESTED LOOP JOIN
    Subquery:
    ---------
    Estimated Cost: 7878
    Estimated # of Rows Returned: 16
      1) root.ne: INDEX PATH
            Filters: root.ne.ne_type = 8
        (1) Index Keys: workspace_id manager_ne_inst   (Serial, fragments: ALL)
            Lower Index Filter: root.ne.workspace_id = 40

优化的查询及其执行计划:

QUERY:
------
SELECT ne_inst from ne
               WHERE  ne.workspace_id = 40
union
SELECT ne_inst from ne
               WHERE  ne.workspace_id = 40
                 AND  ne_type = 8
                into  temp ne_temp1
Estimated Cost: 103
Estimated # of Rows Returned: 10957
  1) root.ne: INDEX PATH
    (1) Index Keys: workspace_id ne_inst   (Key-Only)  (Serial, fragments: ALL)
        Lower Index Filter: root.ne.workspace_id = 40
Union Query:
------------
  1) root.ne: INDEX PATH
        Filters: root.ne.ne_type = 8
    (1) Index Keys: workspace_id manager_ne_inst   (Serial, fragments: ALL)
        Lower Index Filter: root.ne.workspace_id = 40
QUERY:
------
SELECT workspace_impctlog.ne_instance, 1, br_status
        FROM   workspace_impctlog, ne
        WHERE  workspace_impctlog.workspace_id = 40
          AND  workspace_impctlog.impct_type = 10
          AND  workspace_impctlog.trans_type =! 2
          AND  workspace_impctlog.ne_instance = ne.ne_inst
          AND  ne.workspace_id = 40
          AND  ne.ne_type =! 8
          AND  ne.ne_inst NOT IN
               (SELECT * from ne_temp1)
union
SELECT workspace_impctlog.ne_instance, 1, br_status
        FROM   workspace_impctlog, ne
        WHERE  workspace_impctlog.workspace_id = 40
          AND  workspace_impctlog.impct_type = 30
          AND  workspace_impctlog.ne_instance = ne.ne_inst
          AND  ne.workspace_id = 40
          AND  ne.ne_type =! 8
          AND  ne.ne_inst NOT IN
               (SELECT * from ne_temp1)
Estimated Cost: 5
Estimated # of Rows Returned: 2
  1) root.workspace_impctlog: SEQUENTIAL SCAN
        Filters: ((root.workspace_impctlog.impct_type = 10 AND root.workspace_impctlog.wo
rkspace_id = 40 ) AND root.workspace_impctlog.trans_type != 2 )
  2) root.ne: INDEX PATH
        Filters: root.ne.ne_type != 8
    (1) Index Keys: workspace_id ne_inst   (Key-First)  (Serial, fragments: ALL)
        Lower Index Filter: (root.workspace_impctlog.ne_instance = root.ne.ne_inst AND ro
ot.ne.workspace_id = 40 )
        Key-First Filters:  (root.ne.ne_inst != ALL <subquery> )
NESTED LOOP JOIN
    Subquery:
    ---------
    Estimated Cost: 1
    Estimated # of Rows Returned: 1
      1) omcadmin.ne_temp1: SEQUENTIAL SCAN
Union Query:
------------
  1) root.workspace_impctlog: SEQUENTIAL SCAN
        Filters: (root.workspace_impctlog.impct_type = 30 AND root.workspace_impctlog.wor
kspace_id = 40 )
  2) root.ne: INDEX PATH
        Filters: root.ne.ne_type != 8
    (1) Index Keys: workspace_id ne_inst   (Key-First)  (Serial, fragments: ALL)
        Lower Index Filter: (root.workspace_impctlog.ne_instance = root.ne.ne_inst AND ro
ot.ne.workspace_id = 40 )
        Key-First Filters:  (root.ne.ne_inst != ALL <subquery> )
NESTED LOOP JOIN
    Subquery:
    ---------
    Estimated Cost: 1
    Estimated # of Rows Returned: 1
      1) omcadmin.ne_temp1: SEQUENTIAL SCAN

我们首先尽可能多地替换原始查询中的子查询,然后使用 UNION 集合运算符代替 OR 运算符。优化后的执行计划输出显示出了极大的提高。

示例 4

下列查询是找出 Willson Market 公司发出了多少订单,并检索其合同信息,例如地址、电话等。该查询及其执行计划如下所示:

QUERY:
------
select companyname, address, city, state, country, phone, fax, email, count(*)
from customer a, order b
where a.custid = b.custid
and a.custid = 'WILMK'
group by 1,2,3,4,5,6,7,8
Estimated Cost: 1135675
Estimated # of Rows Returned: 1
Temporary Files Required For: Group By
  1) omcadmin.a: SEQUENTIAL SCAN
        Filters: informix.a.custid = 'WILMK'
  2) omcadmin.b: SEQUENTIAL SCAN
        Filters:
        Table Scan Filters: informix.b.custid = 'WILMK'
DYNAMIC HASH JOIN
    Dynamic Hash Filters: informix.a.custid = informix.b.custid

其成本太高。在检查这些表时,我们发现两个没有索引的连接列:customer.custid 和 order.custid。这导致 Informix server 执行连续扫描,本例中,连续扫描要比索引键扫描昂贵得多,因为 customer 和 order 表非常大;每个都包含几百万条记录。因此,我们在这些连接列上添加了索引,从下列查询执行计划中我们可以看到,查询成本减少了很多。

QUERY:
------
select companyname, address, city, state, country, phone, fax, email, count(*)
from cust a, order b
where a.custid = b.custid
and a.custid = 'WILMK'
group by 1,2,3,4,5,6,7,8
Estimated Cost: 15
Estimated # of Rows Returned: 1
Temporary Files Required For: Group By
  1) omcadmin.a: INDEX PATH
    (1) Index Keys: custid   (Serial, fragments: ALL)
        Lower Index Filter: informix.a.custid = 'WILMK'
  2) omcadmin.b: INDEX PATH
    (1) Index Keys: custid   (Key-Only)  (Serial, fragments: ALL)
        Lower Index Filter: informix.a.custid = informix.b.custid
NESTED LOOP JOIN

该查询的响应时间也从 10 分钟减至了 30 秒钟。这是一个极佳的性能提高!



结束语

SQL 查询的调优并不简单;它包含计划和设计都很好的测试策略、细致的观察和深入的分析。此外,各种平台上的测试结果可能不同,因此,您需要在测试环境中反映您的生产环境。但是“一分耕耘,一分收获”,当看到性能获得较大的提高时,您会感到十分兴奋。我希望上面的示例和分析证明是对您有所帮助的,并希望本文能充当 SQL 查询调优的简介。

 

本日志由 flyinweb 于 2010-09-14 09:32:33 发表,目前已经被浏览 4222 次,评论 0 次;

作者添加了以下标签: informix tuning

引用通告:http://www.517sou.net/Article/523/Trackback.ashx

评论订阅:http://www.517sou.net/Article/523/Feeds.ashx

相关文章

评论列表

    暂时没有评论
(必填)
(必填,不会被公开)