我们知道,在数据库应用系统中,SQL 语句的性能好坏至关重要。如果 SQL 语句性能很差,可能会导致整个数据库应用系统的性能也非常差。那么,如何监控数据库系统中 SQL 语句的性能,导致 SQL 语句性能差的原因是什么? SQL 语句运行过程中对系统资源的使用情况如何?系统资源存在哪些瓶颈?在 Informix 11.5 中,主要提供了两个工具来解决上述问题。一个是 set explain 命令,我们可以通过查看数据库的查询计划来分析导致 SQL 语句性能差的原因并给予相应的调整,另一个是 SQL 下钻查询特性,通过它,我们可以分析系统中哪些 SQL 语句执行比较慢、SQL 语句执行的时间是多少、SQL 语句运行时对资源的占用情况及系统存在的瓶颈是什么并及时进行相应的调整。下面,我们具体来看一下这两种监控工具的具体使用方法,希望对大家能有所帮助。
SET EXPLAIN 命令
当我们发现系统中某一个或一组 SQL 语句性能比较差时,我们往往会使用 set explain 命令来查看一下 SQL 语句的查询计划,看看 SQL 语句性能差的原因是什么并进行相应的调整。在 Informix 中,查询计划主要包括访问计划(access plan)及表连接计划(join plan)。访问计划是指 Informix 数据库是通过什么方法来读取磁盘上的数据。一般来讲,Informix 主要提供以下几种访问计划:
- 顺序扫描(Sequential scan):数据库服务器按照物理顺序读取表中的所有记录。
- 索引扫描(Index scan):数据库服务器读取索引页,并通过相应的 ROWID 来读取相关的记录。
- 键值扫描(Key-only index scan):如果读取的相关数据包含在索引节点中,数据库服务器就只需读取索引,不需要再去读取相应的数据页。
- 键优先扫描(Key-first index scan):键优先扫描是一种索引扫描,它首先使用索引键过滤器来减少查询读取的数据量。
- 自动索引扫描(Auto-index scan):自动索引扫描特性允许数据库服务器在一个或多个字段上自动创建临时索引,数据库服务器通过这个临时索引读取相应的数据。这个临时索引只在查询过程中生效。该特性在一些 OLTP 批处理操作中特别有意义,它一方面利用到索引,另一方面又不需要索引维护的开销。
Informix 主要提供两种表连接计划:
- 嵌套循环连接(nested-loop join):在嵌套循环连接中,将扫描第一个(或外部)表,以查找满足查询规则的行。对于在外部表中找到的每一行,数据库服务器将在第二个(或内部)表中搜索其相应的行。通过索引扫描还是表扫描来访问外部表则取决于该表。如果有过滤器,数据库服务器首先会应用它们。如果内部表没有索引,那么数据库服务器就会将在表上构建索引的成本与连续扫描的成本进行比较,然后选择成本最低的那一种方法。总成本取决于连接列上是否有索引。如果连接列上有一个索引,那么其成本会相当低;否则,数据库服务器就必须对所有表(外部和内部表)执行表扫描。
- 哈希连接(hash join):当一个或多个连接表上没有索引时,或者当数据库服务器必须从所有连接表中读取大量行时,就使用这种方法。在该方法中,需要扫描其中的一个表,通常扫描较小的那个表,用它在内存中创建一个哈希表。通过哈希函数,将具有相同哈希值的行放在一个 bucket 中。在扫描完第一个表并将它放在哈希表中之后,就扫描第二个表,并在哈希表中查找该表中的每一行,看是否可以进行连接。如果连接中有更多表,那么数据库服务器将对每个连接表执行相同的操作。
哈希连接包含两个动作:构建哈希(或者是我们所称的构建阶段),以及探测哈希表(或探测阶段)。在构建阶段,数据库服务器读取一个表,并且在应用所有现有过滤器之后,在内存中创建一个哈希表。可以在概念上将哈希表认为是一系列的 bucket,每个 bucket 所拥有的地址是通过应用哈希函数从键值导出的。在探测阶段,数据库服务器将读取连接中的其他表,如果存在过滤器,就应用它们。在满足过滤器条件的每个行中,数据库服务器将对键应用哈希函数,并探测哈希表以查找匹配的键值。如果读取的相关数据包含在索引节点中,数据库服务器就只需读取索引,不需要再去读取相应的数据页。
如何获取查询计划
在 Informix 中,我们可以使用 SET EXPLAIN 语句或 EXPLAIN 伪指令来显示优化程序所选择的查询计划。
使用 SET EXPLAIN 使用 AVOID_EXECUTE 选项sqexplain.out 文件EXPLAIN 伪指令onmode -Y:动态更改 SET EXPLAIN源代码的权限,那么数据库管理员可以通过使用运行 SQL 代码的 onmode -Y 命令动态地设置 SET EXPLAIN 。对于单独的会话,我们也可以使用 onmode -Y 命令动态更改 SET EXPLAIN 语句的值。
SET EXPLAIN 语句的输出显示了查询优化程序作出的决策。它显示了是否已使用并行扫描、响应查询所需的最大线程数以及用于查询的连接类型。您可以使用 SET EXPLAIN 来查看应用程序的查询计划。
SET EXPLAIN 语句的基本语法:
>>-SET EXPLAIN--------------------------------------------------> >--+-+-OFF-------------------+-------------------------+------->< | '-ON--+---------------+-' | | '-AVOID_EXECUTE-' | '-FILE TO--+-'filename '--+--+--------------------+-' |
其中:
- ON :为每个后续查询生成评估并将结果写入当前目录中的输出文件。如果文件已经存在,那么新输出会附加到现有文件。
- AVOID_EXECUTE :防止 SELECT、INSERT、UPDATE 或 DELETE 语句在数据库服务器将查询计划打印到输出文件中时执行。
- OFF :终止 SET EXPLAIN 语句的活动,以便不再为后续查询生成评估或不再将评估写入输出文件 。
- FILE TO :为每个后续查询生成评估并使您能够指定说明输出文件的位置。
在发出 SET EXPLAIN OFF 语句或程序结束之前,来自 SET EXPLAIN ON 语句的输出将定向到适当的文件。如果没有输入 SET EXPLAIN 语句,那么缺省行为是 OFF,并且数据库服务器不会为查询生成评估。
SET EXPLAIN 语句在数据库服务器优化阶段期间执行,该优化阶段在您启动查询时开始。对于与游标相关的查询,如果查询已准备好且没有主变量,那么优化在您准备期间发生。否则,优化在您打开游标时发生。
如果用户希望了解下述 SQL 语句的查询计划并执行下述 SQL 语句,我们可以执行:
SET EXPLAIN ON;
SELECT * FROM customer, orders
WHERE customer.customer_num = orders.customer_num
AND customer.lname = "Higgins"; |
如果用户希望了解下述 SQL 语句的查询计划但不希望执行下述 SQL 语句,我们可以执行:
SET EXPLAIN ON AVOID_EXECUTE;
SELECT * FROM customer, orders
WHERE customer.customer_num = orders.customer_num
AND customer.lname = "Higgins"; |
如果用户希望了解下述 SQL 语句的查询计划,并将结果输出到自己指定的位置,我们可以执行:
SET EXPLAIN ON AVOID_EXECUTE;
SET EXPLAIN FILE TO '/tmp/explain.out'
SELECT * FROM customer, orders
WHERE customer.customer_num = orders.customer_num
AND customer.lname = "Higgins"; |
如果用户不再希望了解下述 SQL 语句的查询计划,我们可以执行:
SET EXPLAIN OFF; |
当执行 SPL 例程时,它已经优化。要显示包含在 SPL 例程中的每个 SQL 语句的查询计划,请在执行以下任何 SQL 语句(它们总是试图优化 SPL 例程)之前执行 SET EXPLAIN ON 语句:
CREATE PROCEDURE UPDATE STATISTICS FOR PROCEDURE |
例如:使用以下语句显示 SPL 例程的查询计划:
SET EXPLAIN ON; UPDATE STATISTICS FOR PROCEDURE procname; |
SET EXPLAIN ON AVOID_EXECUTE 语句为会话激活 Avoid Execute 选项,或直到执行下一个不带 AVOID_EXECUTE 的 SET EXPLAIN OFF(或 ON)。 AVOID_EXECUTE 关键字将使得 DML 语句无法执行;数据库服务器会将查询计划打印到输出文件中。如果为包含远程表的查询激活 AVOID_EXECUTE,那么查询不会在本地或远程站点执行。
如果设置了 AVOID_EXECUTE,数据库服务器会发送警告消息。如果您正在使用 DB-Access,它会对所有选择、删除、更新或插入查询操作显示文本消息:
Warning! avoid_execute has been set |
根据 ESQL,sqlwarn.sqlwarn7 字符设置为“ W ”。
使用 SET EXPLAIN ON 或 SET EXPLAIN OFF 语句可关闭 AVOID_EXECUTE 选项。 SET EXPLAIN ON 语句将关闭 AVOID_EXECUTE 选项,但会继续生成查询计划并将结果写入输出文件。
如果在 SPL 例程中发出 SET EXPLAIN ON AVOID_EXECUTE 语句,那么 SPL 例程和所有 DDL 语句仍然执行,但该 SPL 例程内的 DML 语句将不执行。数据库服务器会将 SPL 例程的查询计划打印到输出文件。要关闭此选项,必须在 SPL 例程外执行 SET EXPLAIN ON 或 SET EXPLAIN OFF 语句。如果在执行 SPL 例程之前执行 SET EXPLAIN ON AVOID_EXECUTE 语句,那么 SPL 例程内的 DML 语句将不执行,并且数据库服务器不会将 SPL 例程的查询计划打印到输出文件中。
当 AVOID_EXECUTE 生效时仍然计算查询中的恒定函数,因为数据库服务器会在进行优化之前计算这些函数。
例如,尽管没有执行以下 SELECT 语句,仍计算 func( ) 函数:
SELECT * FROM orders WHERE func(10) > 5; |
如果在打开 ESQL/C 程序中的游标之前执行 SET EXPLAIN ON AVOID_EXECUTE 语句,那么每个 FETCH 操作将返回找不到行的消息。但是,如果在 ESQL/C 程序打开游标后执行 SET EXPLAIN ON AVOID_EXECUTE,那么此语句不会影响游标,并将继续返回行。
在 UNIX 系统中,数据库服务器将 SET EXPLAIN ON 语句或 EXPLAIN 伪指令的输出写入到 sqexplain.out 文件。
如果客户机应用程序和数据库服务器在同一计算机上,那么 sqexplain.out 文件存储在当前目录中。如果您正使用版本 5.x 或更早版本的客户机应用程序并且 sqexplain.out 文件没有出现在当前目录中,那么请检查您的主目录查找该文件。
当当前数据库在另一台计算机上时,sqexplain.out 文件将存储在远程主机上的主目录中。
在 Windows 系统中,数据库服务器将 SET EXPLAIN ON 语句或 EXPLAIN 伪指令的输出写入到文件%InformixDIR%\sqexpln\username.out。
sqexplain.out 文件内容。
sqexplain.out 文件主要分为 3 个部分:
部分 1:
部分 1 包含以下内容,它们的顺序与以下列出的顺序相同:
- 用于查询的 SQL 语句。
- 以优化器用来比较计划的单位表示的查询成本估计值。这些单位代表查询执行的相对时间,每个单位大约相当于一次典型的磁盘存取时间。优化器选择某个查询计划是因为执行这个计划的估计成本在所有评估的计划中是最低的。
- 期望查询产生的行数估计。
- 执行查询所派生的最大数量的线程(如果设置了 PDQPRIORITY 的话)。
- 用于执行 ORDER BY 和/或 GROUP BY (如果需要的话)的临时文件可选项。
部分 2:
表在这里是按访问它们的顺序列出的。对于每个表,列出了所应用的过滤器。
- 已访问表的名称
- 数据库服务器读取表所采取的访问计划 — 顺序扫描、索引路径和自动索引。另外,如果表是分段的,则在这里列出对于这一特定查询所要访问的活动分段。
- 列出了每对表的连接计划:嵌套循环连接或动态哈希连接连接。对于动态哈希连接连接也列出了执行哈希连接连接所用到的过滤器。
部分 3:
根据所选存取计划的不同,这一部分的内容会有所不同。对于部分 2 中的每一个表,这一部分会出现一次。
对于顺序扫描:本部分包含要应用的过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它,它会象主查询一样包括所有的部分。
对于索引扫描和自动索引扫描:对于索引和自动索引扫描,这一部分包含以下信息:
- 一些索引键,将对它们应用过滤器,跟着是以下项中的一个或全部:
- 仅键项,如果它是只用到键的索引扫描。
- 聚合项,如果查询聚合了索引键。
- 键优先项,如果对索引键应用键优先过滤器。
- 下限索引过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它,它会象主查询一样包括所有的部分。
- 上限索引过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它,它会象主查询一样包括所有的部分。
- 要应用的键优先过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它,它会象主查询一样包括所有的部分。
如果希望仅显示一个 SQL 语句的查询计划时,可以使用 EXPLAIN 伪指令代替 SET EXPLAIN ON 或 SET EXPLAIN ON AVOID_Execute 语句。
使用 EXPLAIN 伪指令按以下方式显示查询计划:
- EXPLAIN :显示优化程序所选择的查询计划。
- EXPLAIN AVOID_EXECUTE :显示优化程序所选择的查询计划,但是不执行查询。
在 Informix 中,伪指令可以采用如下方法表示:
--+directive text {+directive text} /*+directive text */ |
下边例子显示了使用 EXPLAIN 伪指令的查询语句:
select --+ explain l.customer_num, l.lname, l.company,l.phone, r.call_dtime, r.call_descr from customer l, cust_calls r where l.customer_num = r.customer_num |
下边例子显示了使用 EXPLAIN AVOID_Execute 伪指令的查询语句:
select --+ explain,avoid_execute l.customer_num, l.lname, l.company,l.phone, r.call_dtime, r.call_descr from customer l, cust_calls r where l.customer_num = r.customer_num |
下边显示了使用 EXPLAIN AVOID_EXECUTE 伪指令的查询的 sqexplain.out 文件输出样本:
QUERY: ------ select --+ explain ,avoid_execute l.customer_num, l.lname, l.company,l.phone, r.call_dtime, r.call_descr from customer l, cust_calls r where l.customer_num = r.customer_num DIRECTIVES FOLLOWED: EXPLAIN AVOID_EXECUTE DIRECTIVES NOT FOLLOWED: Estimated Cost: 7 Estimated # of Rows Returned: 7 1) Informix.r: SEQUENTIAL SCAN 2) Informix.l: INDEX PATH (1) Index Keys: customer_num (Serial, fragments: ALL) Lower Index Filter: Informix.l.customer_num = Informix.r.customer_num NESTED LOOP JOIN |
下表对上述 sqexplain.out 文件中描述所选的查询计划的相关输出行进行了描述。
输出行 | 所选的查询计划描述 |
DIRECTIVES FOLLOWED: EXPLAIN AVOID_EXECUTE | 使用伪指令 EXPLAIN 和 AVOID_EXECUTE 来显示查询计划,而不执行查询。 |
Estimated # of Rows Returned: 7 | 估计该查询返回 7 行。 |
Estimated Cost: 7 | 该估计成本值为 7,优化程序使用该值来比较不同查询计划并选择成本最低的查询计划。 |
1) Informix.r: SEQUENTIAL SCAN | 将 cust_calls r 表用作外表并对它进行扫描以获取每一行。 |
2) Informix.l: INDEX PATH | 对于外表中的每一行,请使用索引获取内表 customer l 中的匹配行。 |
(1) Index Keys: customer_num (Serial, fragments: ALL) | 使用 customer_num 列的索引,对其进行连续扫描,并扫描所有的分段(customer l 表只有一个分段组成)。 |
Lower Index Filter: Informix.l.customer_num = Informix.r.customer_num | 从外表的 customer_num 值开始进行索引扫描。 |
NESTED LOOP JOIN | 采用嵌套循环连接方式 |
如果用户没有访问 SQL
onmode -Y 命令基本语法:
调用 | 解释 |
onmode -Y sessionid 2 | 打开对 sessionid 的 SET EXPLAIN,并且仅显示查询计划 |
onmode -Y sessionid 1 | 打开对 sessionid 的 SET EXPLAIN |
onmode -Y sessionid 0 | 关闭对 sessionid 的 SET EXPLAIN |
当使用 onmode -Y 命令打开 SET EXPLAIN 时,输出显示在sqexplain.out.sessionid 文件中。
如果希望动态对 session 30 打开 SET EXPLAIN ON AVOID_Execute 特性,我们可以运行:
onmode – Y 30 2 |
如果希望动态对 session 30 打开 SET EXPLAIN ON 特性,我们可以运行:
onmode – Y 30 1 |
如果希望动态对 session 30 打开 SET EXPLAIN OFF 特性,我们可以运行:
onmode – Y 30 0 |
显示查询统计信息查询计划样例分析
在 Informix ONCONFIG 配置文件中的 EXPLAIN_STAT 配置参数用来控制是否在 sqexplain.out 文件中包含“查询统计信息”。 如果启用 EXPLAIN_STAT 配置参数,那么查询统计信息部分将出现在 sqexplain.out 文件中。 sqexplain.out 文件中的“查询统计信息”部分显示了查询计划预期返回的估计行数、返回的实际行数和查询的其他信息。可以使用这些信息(显示查询计划综合流量以及查询的每个阶段通过的行流量数)来调试性能问题。
EXPLAIN_STAT 配置参数可以设置为下列值:
值 | 描述 |
0 | 禁用查询统计信息的显示 |
1 | 启用查询统计信息的显示 |
我们也可以通过修改 onconfig 文件来修改此值,也可以通过 onmode – wf 及 onmode – wm 命令动态设置该值。
onmode – wf EXPLAIN_STAT= 1 onmode – wm EXPLAIN_STAT= 1 |
查询统计信息只有在 SQL 语句执行之后才可以生成。当执行 set explain on 及 onmode – Y session_id 1 时,sqexplain.out 文件中会包含“查询统计信息”。
以下示例显示了 SET EXPLAIN 输出中的查询统计信息。如果已扫描或已连接的估计行数和实际行数相差很大,那么关于这些表的统计信息可能是旧的,应该更新它们。
select * from tab1, tab2 where tab1.c1 = tab2.c1 and tab1.c3 between 0 and 15 Estimated Cost: 104 Estimated # of Rows Returned: 69 1) zelaine.tab2: SEQUENTIAL SCAN 2) zelaine.tab1: INDEX PATH (1) Index Keys: c1 c3 (Serial, fragments: ALL) Lower Index Filter: (zelaine.tab1.c1 = zelaine.tab2.c1 AND zelaine.tab1.c3 >= 0 ) Upper Index Filter: zelaine.tab1.c3 <= 15 NESTED LOOP JOIN Query statistics: ----------------- Table map : ---------------------------- Internal name Table name ---------------------------- t1 tab2 t2 tab1 type table rows_prod est_rows rows_scan time est_cost ------------------------------------------------------------------- scan t1 50 50 50 00:00:00 4 type table rows_prod est_rows rows_scan time est_cost ------------------------------------------------------------------- scan t2 67 69 4 00:00:00 2 type rows_prod est_rows time est_cost ------------------------------------------------- nljoin 67 70 00:00:00 104 |
下边我们例举一些查询计划的样例,希望能够对查询计划能够有一个比较全面的了解。
单表查询多表查询搜索使用customer_num的索引。标志仅键意味着对于 customer 表只需要读取索引,因为只有c.customer_num列用在连接和输出中且该列是索引键。键优先扫描子查询的查询计划SQL 语句优化
下边显示了 customer 表上复杂查询的 SET EXPLAIN 输出:
QUERY: ------ SELECT fname, lname, company FROM customer WHERE company MATCHES 'Sport*' AND customer_num BETWEEN 110 AND 115 ORDER BY lname Estimated Cost: 1 Estimated # of Rows Returned: 1 Temporary Files Required For: Order By 1) virginia.customer: INDEX PATH Filters: virginia.customer.company MATCHES 'Sport*' (1) Index Keys: customer_num (Serial, fragments: ALL) Lower Index Filter: virginia.customer.customer_num >= 110 Upper Index Filter: virginia.customer.customer_num <= 115 |
以下输出行显示了该查询的索引扫描的范围:
- 以索引键值 110 开始索引扫描 :
Lower Index Filter: virginia.customer.customer_num >= 110
- 以索引键值 115 停止索引扫描 :
Upper Index Filter: virginia.customer.customer_num <= 115
下边显示了多表查询的 SET EXPLAIN 输出:
QUERY: ------ SELECT C.customer_num, O.order_num, SUM (I.total_price) FROM customer C, orders O, items I WHERE C.customer_num = O.customer_num AND O.order_num = I.order_num GROUP BY C.customer_num, O.order_num Estimated Cost: 78 Estimated # of Rows Returned: 1 Temporary Files Required For: Group By 1) virginia.o: SEQUENTIAL SCAN 2) virginia.c: INDEX PATH (1) Index Keys: customer_num (Key-Only) (Serial, fragments: ALL) Lower Index Filter: virginia.c.customer_num = virginia.o.customer_num NESTED LOOP JOIN 3) virginia.i: INDEX PATH (1) Index Keys: order_num (Serial, fragments: ALL) Lower Index Filter: virginia.o.order_num = virginia.i.order_num NESTED LOOP JOIN |
SET EXPLAIN 输出列出数据库服务器访问表的顺序和读取每个表的存取计划。上边输出的计划指示数据库服务器将执行以下操作:
- 数据库服务器将首先读取orders表。
因为orders表上没有过滤器,所以数据库服务器必须读取所有的行。按物理顺序读取表是成本最低的方法。
- 对于orders的每一行,数据库服务器将在 customer 表中搜索匹配的行。
- 对于orders中具有一个匹配customer_num的每一行,数据库服务器将使用order_num的索引在items表中搜索匹配行。
键优先扫描是一种索引扫描,该扫描使用未列为低索引过滤器和高索引过滤器的键。下边显示了使用键优先扫描的样本查询:
select * from tab1 where (c1 > 0) and ( (c2 = 1) or (c2 = 2)) Estimated Cost: 4 Estimated # of Rows Returned: 1 1) pubs.tab1: INDEX PATH (1) Index Keys: c1 c2 (Key-First) (Serial, fragments: ALL) Lower Index Filter: pubs.tab1.c1 > 0 Index Key Filters: (pubs.tab1.c2 = 1 OR pubs.tab1.c2 = 2) |
在此示例中数据库服务器将首先通过应用附加的键过滤器尝试减少可能的行数。数据库服务器使用索引来应用附加的过滤器 c2 = 1 OR c2 = 2 之后才读取行数据。
如果连接的成本较低,那么优化程序可自动将子查询更改成连接。下边例子中的 SET EXPLAIN ON 语句的样本输出显示了优化程序将子查询中的表更改成连接中的内表:
QUERY: ------ SELECT company, fname, lname, phone FROM customer c WHERE EXISTS( SELECT customer_num FROM cust_calls u WHERE c.customer_num = u.customer_num) Estimated Cost: 6 Estimated # of Rows Returned: 7 1) virginia.c: SEQUENTIAL SCAN 2) virginia.u: INDEX PATH (First Row) (1) Index Keys: customer_num call_dtime (Key-Only) (Serial, fragments: ALL) Lower Index Filter: virginia.c.customer_num = virginia.u.customer_num NESTED LOOP JOIN (Semi Join) |
当优化程序将子查询更改成连接时,它可以使用存取计划和连接计划的几种变形形式:
- 首行扫描
首行扫描是表扫描的一种变形形式。当数据库服务器找到一个匹配时,表扫描将停止。
- 忽略副本索引扫描
忽略副本索引扫描是索引扫描的一种变形形式。数据库服务器不扫描副本。
- 半连接
半连接是嵌套循环连接的一种变形形式。当第一个匹配找到时,数据库服务器将停止内表扫描。
通过对 SQL 语句查询计划的分析,我们可以知道 SQL 语句在执行过程中是采用什么样的访问方法,是顺序扫描还是索引扫描;表之间连接采用什么样的方法,是嵌套循环连接还是哈希连接;表之间访问的顺序是什么;是否产生了临时表;该查询的成本是多少。依此,我们就可以考虑,为了提高 SQL 语句性能,我们是不是要创建合适的索引, 是不是要调整一下表之间连接的顺序,是不是要修改一下 SQL 语句的写法等。通常,我们在调整时,可以比较一下改变之前及改变之后的查询成本,保证查询成本有一个明显的减少。另外,我们还可以通过设置 OPTCOMPIND 参数来指定数据访问方法 ;通过访问计划指示、连接次序指示、连接计划指示、目标指示来指定数据访问方法、表连接顺序、表连接方法及数据返回结果集;通过执行 update statistics 语句提高 SQL 语句性能。关于 OPTCOMPIND 参数及查询指示的具体使用方法,请参考 Informix 信息中心相关内容。