分类: DB2/Informix
2010-08-11 15:09:46
当我们发现系统中某一个或一组 SQL 语句性能比较差时,我们往往会使用 set explain 命令来查看一下 SQL 语句的查询计划,看看 SQL 语句性能差的原因是什么并进行相应的调整。在 Informix 中,查询计划主要包括访问计划(access plan)及表连接计划(join plan)。访问计划是指 Informix 数据库是通过什么方法来读取磁盘上的数据。一般来讲,Informix 主要提供以下几种访问计划:
Informix 主要提供两种表连接计划:
哈希连接包含两个动作:构建哈希(或者是我们所称的构建阶段),以及探测哈希表(或探测阶段)。在构建阶段,数据库服务器读取一个表,并且在应用所有现有过滤器之后,在内存中创建一个哈希表。可以在概念上将哈希表认为是一系列的 bucket,每个 bucket 所拥有的地址是通过应用哈希函数从键值导出的。在探测阶段,数据库服务器将读取连接中的其他表,如果存在过滤器,就应用它们。在满足过滤器条件的每个行中,数据库服务器将对键应用哈希函数,并探测哈希表以查找匹配的键值。如果读取的相关数据包含在索引节点中,数据库服务器就只需读取索引,不需要再去读取相应的数据页。
在 Informix 中,我们可以使用 SET EXPLAIN 语句或 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 '--+--+--------------------+-' |
其中:
在发出 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 包含以下内容,它们的顺序与以下列出的顺序相同:
部分 2:
表在这里是按访问它们的顺序列出的。对于每个表,列出了所应用的过滤器。
部分 3:
根据所选存取计划的不同,这一部分的内容会有所不同。对于部分 2 中的每一个表,这一部分会出现一次。
对于顺序扫描:本部分包含要应用的过滤器(如果有的话)。如果子查询是过滤器的一部分,会在这里扩展它,它会象主查询一样包括所有的部分。
对于索引扫描和自动索引扫描:对于索引和自动索引扫描,这一部分包含以下信息:
如果希望仅显示一个 SQL 语句的查询计划时,可以使用 EXPLAIN 伪指令代替 SET EXPLAIN ON 或 SET EXPLAIN ON AVOID_Execute 语句。
使用 EXPLAIN 伪指令按以下方式显示查询计划:
在 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列用在连接和输出中且该列是索引键。
下边显示了 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 |
以下输出行显示了该查询的索引扫描的范围:
Lower Index Filter: virginia.customer.customer_num >= 110
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表上没有过滤器,所以数据库服务器必须读取所有的行。按物理顺序读取表是成本最低的方法。
键优先扫描是一种索引扫描,该扫描使用未列为低索引过滤器和高索引过滤器的键。下边显示了使用键优先扫描的样本查询:
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 信息中心相关内容。