Informix Dynamic Server 11.5 SQL 语句性能调优最佳实践

袁红涛, 软件工程师, IBM
 

简介: SQL 语言是关系型数据库与应用交互的重要途径,书写高效率的 SQL 是每一个 DBA 和开发人员必备的技能。本文以调整 SQL 执行效率为最终目标,给大家介绍如何查看 Informix 的 SQL 执行计划,如何通过统计信息,SQL Directives 调整执行计划,如何通过 SQL Drill-down 监控 SQL 的执行效率,并且总结了书写 SQL 语句时若干需要注意的地方,可以作为一个实用的 SQL Checklist 使用。

准备实验环境

本文代码使用的硬件环境:

  • 2 × Intel(R) Core(TM)2 Duo CPU T7700 @ 2.40GHz
  • 2G Memory
  • 100G Disk
  • OS: REH 64bits
  • IDS Version: 11.50.FC3

关于执行计划,我们可以把它简单的理解成 SQL 语句执行时访问数据的方式。执行计划的优劣是影响 SQL 执行效率的重要因素。它包括:查询优化器认为最优的数据访问路径,返回记录数的估计值,SQL 的相对开销值,以及其他附属信息。

获取执行计划

获取执行计划有多种方式:

1. 在服务器上直接执行 SQL 语句 set explain on 得到当前 session 中 SQL 的执行计划描述文件,默认名称为:sqexplain.out,

具体方式和操作如下:

在运行 IDS 的服务器上打开 dbaccess,通过 dbaccess 运行如下 SQL 语句:

set explain on; 
select first 10 * from customer c, cust_calls u  
where c.customer_num = u.customer_num order by 1; 

则在当前目录下生成 sqexplain.out 文件,内容如下:

QUERY: (OPTIMIZATION TIMESTAMP: 12-31-2009 10:56:35) 
 ------ 
 select first 10 c.customer_num, c.lname, c.company, 
  c.phone, u.call_dtime, u.call_descr 
  from customer c, cust_calls u 
  where c.customer_num = u.customer_num 
  order by 1; 

 Estimated Cost: 2326384 
 Estimated # of Rows Returned: 4318039 
  1) informix.c: INDEX PATH 
  (1) Index Keys: customer_num (Serial, fragments: ALL) 
  2) informix.u: INDEX PATH 
  (1) Index Keys: customer_num call_dtime (Serial, fragments: ALL) 
  Lower Index Filter: informix.c.customer_num = informix.u.customer_num 
 NESTED LOOP JOIN 


 Query statistics: 
 ----------------- 
  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 c 
  t2 u 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 1 10000028 1 00:00.00 300003 
				 type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t2 10 4318026 10 00:00.00 0 
				 type rows_prod est_rows time est_cost 
  ------------------------------------------------- 
 nljoin 10 4318039 00:00.00 2326384 

2. 如果我们不是直接在服务器上执行 SQL,而是通过应用程序远程连接到服务器,同样可以使用 set explain on,但是此时 sqexplain.out 默认位置为当前用户的主目录下,如果使用 Informix 用户运行,则文件产生在 /home/Informix/ 目录下。

3. 通过 onmode – Y

比如我们有一个 session 连接到服务器,sessionID 为 22,则可以通过执行:

onmode – Y 22

打开针对该 session 所有的 SQL 语句的执行计划生成功能,生成结果在用户主目录下,例如 /home/Informix,文件名称格式为 sqexplain.out.,也就是 /home/Informix/sqexplain.out.22,当然我们也可以通过”set explain file to“参数指定 sqexplain.out 的具体位置。

4. OAT

在打开 SQLtracing 功能的情况下,我们可以通过 OAT 工具查看,具体位置为:Performance Analysis>SQL Explorer>SQL>Query Tree,如下图 :


图 1. 通过 OAT 查看执行计划
图 1. 通过 OAT 查看执行计划

5. 通过第三方工具

例如 ServerStudio:


图 2. ServerStudio 查看执行计划(
图 2. ServerStudio 查看执行计划

6. 通过系统表

如果 SQLtracing 打开,实际上我们可以在以下系统表中找到每条被追踪 SQL 的执行计划:

 syssqltrace 
 sqltrace_iter 
 sqltrace_hvar. 

一般,一条 SQL 语句的执行对应 syssqltrace 中一条记录,对应 sqltrace_iter 的多条记录。

执行计划分析示例

一般来说,执行计划包含以下几个部分的信息:

  • 关于 SQL 的基本信息:包括 SQL 语句本身,估算的执行代价,和估算的返回记录数目。
  • 执行计划:包括表的访问方式,连接方式,访问顺序,对索引和分区的处理等信息。
  • 执行计划的统计信息:执行计划中每一步的更详细的信息,这部分只有 SQL 真正执行以后才有,如果设置了 SET EXPLAIN ON AVOID_EXECUTE,则不产生。

当我们怀疑某个 SQL 的执行效率有问题时,需要首先检查并确认它的执行计划是否合适,从而保证系统的效率。

实例 1:表的访问方式,sequencial scan 与 index scan

我们有 customer_t 数据表,1000000 行数据,在 customer_num 上建立有唯一索引:’ 169_81 ’ ,并且刚刚做过高级别的统计信息更新。以下实例表明 sequencial scan 还是 index scan 在效率上有很大的差别。

 [[email protected] sqltuning]$ time echo "set explain on; 
 select count(city) from customer_t 
 where customer_num < 2000000"| dbaccess demodb 
 Database selected. 
 Explain set. 
  (count) 
  218739 
 1 row(s) retrieved. 

 Database closed. 


 real 0m21.602s 
 user 0m0.007s 
 sys 0m0.011s 

执行计划为:

 QUERY: (OPTIMIZATION TIMESTAMP: 01-05-2010 11:47:04) 
 ------ 
 select count(city) from customer_t where customer_num < 2000000 


 Estimated Cost: 24532 
 Estimated # of Rows Returned: 1 

  1) informix.customer_t: INDEX PATH 

  (1) Index Keys: customer_num (Serial, fragments: ALL) 
  Upper Index Filter: informix.customer_t.customer_num < 2000000 


 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 
				             customer_t 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 218739 218740 218739 00:31.66 24532 

  type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
  group 1 1 218739 00:32.82 


 [[email protected] sqltuning]$ time echo "set explain on; 
select {+avoid_index(customer_t ' 201_107' )} count(city)
 from customer_t where customer_num < 2000000"| dbaccess demodb 

 Database selected. 

 Explain set. 
  (count) 
  218739 
 1 row(s) retrieved. 

 Database closed. 
 real 0m13.442s 
 user 0m0.008s 
 sys 0m0.009s 

执行计划为:

 QUERY: (OPTIMIZATION TIMESTAMP: 01-05-2010 11:30:42) 
 ------ 
 select {+avoid_index(customer_t ' 201_107' )} count(city) 
from customer_t where customer_num < 2000000 


 DIRECTIVES FOLLOWED: 
 AVOID_INDEX ( customer_t 201_107 ) 
 DIRECTIVES NOT FOLLOWED: 

 Estimated Cost: 101433 
 Estimated # of Rows Returned: 1 

  1) informix.customer_t: SEQUENTIAL SCAN (Serial, fragments: ALL) 

  Filters: informix.customer_t.customer_num < 2000000 


 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 customer_t 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 218739 333333 1000000 00:31.92 101433 
 
 type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
  group 1 1 218739 00:32.65 

可以看出,使用 indexscan 的方式访问速度反而会慢很多,原因是我们访问的数据占数据表的比例很大,使用 index 得不偿失,而优化器没有能够正确的考虑这种情况,因而经过修正的访问计划为优。同样我们也可以通过实验验证,如果我们查询的范围缩小,例如:查询条件为 customer_num<1000, 则情况正好相反。

实例 2:连接方式

我们有 orders_t 数据表,5000000 行数据,在 customer_num 上建立有非唯一索引:’ idx_0 ’, 并且刚刚做过高级别的统计信息更新。以下实例表明连接循序的在效率上有很大的差别。

 [[email protected] sqltuning]$ time echo "set explain on; 
 select count(*) from orders_t o, customer_t c 
 where o.customer_num = c.customer_num " | dbaccess demodb 
 Database selected. 
 Explain set. 
  (count(*)) 
  277090 
 1 row(s) retrieved. 
 Database closed. 
 real 0m22.104s 
 user 0m0.008s 
 sys 0m0.012s 


执行计划为:

 QUERY: (OPTIMIZATION TIMESTAMP: 01-02-2010 12:17:19) 
 ------ 
 select count(*) from orders_t o, customer_t c 
 where o.customer_num = c.customer_num 


 Estimated Cost: 725139 
 Estimated # of Rows Returned: 1 

  1) informix.c: INDEX PATH 

  (1) Index Keys: customer_num (Key-Only) (Serial, fragments: ALL) 

  2) informix.o: INDEX PATH 

  (1) Index Keys: customer_num (Key-Only) (Serial, fragments: ALL) 
  Lower Index Filter: informix.o.customer_num = informix.c.customer_num 
 NESTED LOOP JOIN 


 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 c 
  t2 o 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 1000000 1000000 1000000 00:13.66 40687 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t2 277090 5000000 277090 01:42.06 1 

 type rows_prod est_rows time est_cost 
  ------------------------------------------------- 
  nljoin 277090 1211539 01:57.39 725140 

  type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
 group 1 1 277090 01:58.33 
 
 [[email protected] sqltuning]$ time echo "set explain on; 
 select {+ordered} count(*) from orders_t o, customer_t c 
 where o.customer_num = c.customer_num " | dbaccess demodb 
 Database selected. 
 Explain set. 
  (count(*)) 
  277090 
 1 row(s) retrieved. 
 Database closed. 
 real 0m38.978s 
 user 0m0.007s 
 sys 0m0.008s 

执行计划为:

 QUERY: (OPTIMIZATION TIMESTAMP: 01-02-2010 12:15:17) 
 ------ 
 select {+ordered} count(*) from orders_t o, customer_t c 
 where o.customer_num = c.customer_num 


 DIRECTIVES FOLLOWED: 
 ORDERED 
 DIRECTIVES NOT FOLLOWED: 

 Estimated Cost: 3394422 
 Estimated # of Rows Returned: 1 

  1) informix.o: INDEX PATH 

  (1) Index Keys: customer_num (Key-Only) (Serial, fragments: ALL) 

  2) informix.c: INDEX PATH 

  (1) Index Keys: customer_num (Key-Only) (Serial, fragments: ALL) 
  Lower Index Filter: informix.o.customer_num = informix.c.customer_num 
 NESTED LOOP JOIN 


 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1                        o 
  t2                        c 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 5000000 5000000 5000000 00:57.07 185667 
 
 type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t2 277090 1000000 277090 02:17.78 1 
 
 type rows_prod est_rows time est_cost 
  ------------------------------------------------- 
  nljoin 277090 1211539 03:21.09 3394422 

  type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
  group 1 1 277090 03:22.00 

可以看出,连接的循序不同时,查询的执行时间有很大的差别,优化器选择的连接顺序为最优。

当然执行计划涉及到的内容非常丰富,这里只是举了最简单的例子,在实际应用中需要综合考虑各种情况。

统计信息基本知识

统计信息是指数据库系统表中记录的关于业务数据的基本情况,包括一个表的行数、占用的数据页数、数据值分布情况,以及索引相关信息等,这些都是查询优化器选择执行计划的重要依据。没有统计信息、不准确的统计信息、都容易导致优化器选择效率低的执行计划。

统计信息存储与以下系统表中:

  • SYSTABLES: 表的索引情况,表中的行数
  • SYSCOLUMNS:列的次小值,列的次大值
  • SYSINDEXES:索引的级数,叶子节点数目,不同值的数量,cluster 程度
  • SYSFRAGMENTS:关于表分区或者索引分区的统计信息
  • SYSDISTRIB:数据分布情况

怎样更新统计信息

我们可以通过 SQL: “update statistics” 更新某个表某个列或者全部列的统计信息,统计信息有 low、medium、high 三种模式,其中 low 不生成数据分布信息,medium 以统计估算的方式生成数据分布信息,high 以精确的方式生成数据分布信息。针对 medium 方式我们可以指定 percent 和 confidence 参数,对于 high 我们可以指定需要的 percent 参数。Percent 参数是一个百分比,它的最大值为 10(10%),它的倒数就是统计区间的个数,统计区间越多则统计信息越精确。confidence 参数为采样的可信度,越高采样越精确。

另外在 IDS11 中,在表上创建索引时,自动更新基本统计信息,如新创建的索引信息,数据表的行数。

实例

例如需要更新表 customer_t 的 customer_num 列的统计信息,可以使用以下语句:

 update statistics for table customer_t(customer_num); 
或者
 update statistics low for table customer_t(customer_num); 

 update statistics medium for table customer_t(customer_num); 
或者
 update statistics medium for table customer_t(customer_num) resolution 2.5 0.95; 

 update statistics high for table customer_t(customer_num); 
或者 
 update statistics high for table customer_t(customer_num) resolution 0.5; 

如果要删除分布信息,可以使用以下语句 :

 update statistics for table customer_t(customer_num) drop distributions; 

如果要查看数据表的数据分布信息,可以使用 dbschema:

例如有测试数据表 test1, 有 100 行记录。具体查看方式和输出为 :

 [[email protected] sqltuning]$ dbschema -d demodb -hd test1 

 DBSCHEMA Schema Utility INFORMIX-SQL Version 11.50.FC3 
 { 
 Distribution for informix.statis_1.age 
 Constructed on 2010-01-03 10:56:04.00000 
 High Mode, 10.000000 Resolution 
 --- DISTRIBUTION --- 
  ( 1) 
  1: ( 3, 3, 4) 
  2: ( 3, 3, 7) 
  3: ( 3, 3, 10) 
  4: ( 3, 3, 13) 
  5: ( 3, 3, 16) 
  6: ( 3, 3, 19) 
  7: ( 3, 3, 22) 
  8: ( 3, 3, 25) 
  9: ( 3, 3, 28) 
 10: ( 1, 1, 30) 

 --- OVERFLOW --- 

  1: ( 4, 2) 
  2: ( 2, 30) 
 } 

可以看到一共有 7 个正常分布桶 (DISTRIBUTION),共有三列,其中第一列为该桶中数据值个数,第二列为该桶中不同的值的个数,第三列为该桶中数据的上限。DISTRIBUTION 用于表示数据在各个区间的分布情况,在以 medium、high 方式生成统计信息时,可以通过 resolution 关键词指定该分布百分比,例如比率为 10% 时,如果数据值个数足够的话,一般最多会有 100/10=10 个分布桶。

OVERFLOW 中只有两列,其中第一列为数据值重复的个数,第二列为该数据值。列中的某个数据值出现的次数多到一定程度时才在这里出现。比如这里,2 这个数值在该列中出现的次数为 4 次,30 这个数值出现的次数为 2 次。

当某个值的重复次数满足以下条件时,才放置到 OVERFLOW( 溢出桶 ) 中:

 verflow = 25% * resolution * number_rows 

统计信息对 SQL 执行效率的影响

实例 1

我们来看统计信息对访问方式的影响。

针对数据表 customer_t (1000000 行记录 ,customer_numz 值在 101 到 10000124 之间,在 customer_num 列上建有索引 ) 执行以下语句:

 select * from customer_t where customer_num > 1000 

在统计信息更新之前,执行计划选择索引扫描,事实上此时,无论查询条件的选择性如何,都会选择索引扫描。

 [[email protected] sqltuning]$ time echo "set explain on; 
 select {+index(customer_t ' 183_96')} count(city) from customer_t 
 where customer_num > 1000 " | dbaccess demodb ; 
 tail sqexplain.out -n 20 
 
 Database selected. 
 Explain set. 
  (count) 
  999901 
 1 row(s) retrieved. 
 Database closed. 
 real 1m18.870s 
 user 0m0.008s 
 sys 0m0.013s 
  Lower Index Filter: informix.customer_t.customer_num > 1000 


在完成 low 模式的统计信息更新之后,我们发现执行计划转为使用全表扫描,并且执行速度大大加快:

 [[email protected] sqltuning]$ time echo "set explain on; 
 select count(city) from customer_t 
 where customer_num > 1000 " | dbaccess demodb ; 
 tail sqexplain.out -n 20 
 
 Database selected. 
 Explain set. 
  (count) 
  999901 
 1 row(s) retrieved. 


 Database closed. 
 real 0m13.964s 
 user 0m0.005s 
 sys 0m0.014s 

此例说明,在没有基本信息的情况下,优化器很可能选择索引扫描,但实际上如果查询条件的选择性不好,则会导致性能问题。此例中的查询条件只能过滤掉很少的一部分数据,这种情况下优化器需要对表的基本情况有一个了解才能明智的放弃索引扫描而使用更有的全表扫描。

实例 2

我们来看统计信息对 join 顺序和方式的影响 :

我们有表 :

customer_t: 1000000 rows,在 customer_num 上建有索引,基本 (LOW) 统计信息为最新

orderts_t; 5000000 rows,在 customer_num 上建有索引,没有基本统计信息。

items_t: 67 rows,

考察如下 SQL 语句:

 select * from customer_t c, orders_t o, items_t i 
 where c.customer_num = o.customer_num 
 and i.order_num = o.order_num 
 and c.customer_num < 10000000 

如果 items_t 上没有基本统计信息,则

 [[email protected] sqltuning]$ time echo "set explain on; 
 select * from customer_t c, orders_t o, items_t i 
 where c.customer_num = o.customer_num 
 and i.order_num = o.order_num 
 and c.customer_num < 10000000; " | dbaccess demodb 
 
 Database selected. 
 Explain set. 
 customer_num 122 
 fname Cathy 
 lname O'Brian 
……
……
 1 row(s) retrieved. 
 Database closed. 
 real 1m1.143s 
 user 0m0.006s 
 sys 0m0.012s 

执行计划为 :

 QUERY: (OPTIMIZATION TIMESTAMP: 01-03-2010 17:44:32) 
 ------ 
 select * from customer_t c, orders_t o, items_t i 
 where c.customer_num = o.customer_num 
 and i.order_num = o.order_num and c.customer_num < 10000000 

 Estimated Cost: 1936048 
 Estimated # of Rows Returned: 111665344 

  1) informix.o: SEQUENTIAL SCAN (Serial, fragments: ALL) 

  Filters: informix.o.customer_num < 10000000 

  2) informix.c: INDEX PATH 

  (1) Index Keys: customer_num (Serial, fragments: ALL) 
  Lower Index Filter: informix.c.customer_num = informix.o.customer_num 
 NESTED LOOP JOIN 

  3) informix.i: SEQUENTIAL SCAN 


 DYNAMIC HASH JOIN 
  Dynamic Hash Filters: informix.i.order_num = informix.o.order_num 


 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 o 
  t2 c 
  t3 i 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 4999964 1666667 5000000 00:35.52 358337 
 
 type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t2 277088 999989 277088 02:37.55 1 
 
 type rows_prod est_rows time est_cost 
  ------------------------------------------------- 
  nljoin 277088 1666648 03:18.58 1608258 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t3 67 67 67 00:00.00 4 
 
 type rows_prod est_rows rows_bld rows_prb novrflo time est_cost 
  ------------------------------------------------------------------------------ 
  hjoin 1 111665376 67 277088 0 03:19.58 1936048 


图 3. 统计信息更新前执行计划(
图 3. 统计信息更新前执行计划

如果在 items_t 上更新基本统计信息以后,我们得到同样的查询结果,但是现执行速度大大加快 :

 [[email protected] sqltuning]$ time echo "set explain on; 
 select * from customer_t c, orders_t o, items_t i 
 where c.customer_num = o.customer_num 
 and i.order_num = o.order_num 
 and c.customer_num < 10000000; " | dbaccess demodb 
 
 Database selected. 
 Explain set. 
 customer_num 122 
 fname Cathy 
 lname O'Brian 
……
……
 1 row(s) retrieved. 
 Database closed. 
 real 0m1.716s 
 user 0m0.011s 
 sys 0m0.008s 


执行计划为:

 QUERY: (OPTIMIZATION TIMESTAMP: 01-03-2010 18:05:50) 
 ------ 
 select * from customer_t c, orders_t o, items_t i 
 where c.customer_num = o.customer_num 
 and i.order_num = o.order_num and c.customer_num < 10000000 

 Estimated Cost: 938099 
 Estimated # of Rows Returned: 1116654 

  1) informix.i: SEQUENTIAL SCAN 

  2) informix.o: INDEX PATH 

  Filters: informix.o.customer_num < 10000000 

  (1) Index Keys: order_num (Serial, fragments: ALL) 
  Lower Index Filter: informix.i.order_num = informix.o.order_num 
 NESTED LOOP JOIN 

  3) informix.c: INDEX PATH 

  (1) Index Keys: customer_num (Serial, fragments: ALL) 
  Lower Index Filter: informix.c.customer_num = informix.o.customer_num 
 NESTED LOOP JOIN 


 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 i 
  t2 o 
  t3 c 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 67 67 67 00:00.05 4 
 
 type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t2 1 1666667 1 00:00.05 1500 
 
 type rows_prod est_rows time est_cost 
  ------------------------------------------------- 
  nljoin 1 1116667 00:00.11 100652 
 
 type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t3 1 999989 1 00:00.06 1 

 type rows_prod est_rows time est_cost 
  ------------------------------------------------- 
 nljoin 1 1116654 00:00.17 938099 


图 4. 统计信息更新后执行计划(
图 4. 统计信息更新后执行计划

比较二者,我们发现连接的顺序由 oct,转变为:oic,对表 orders_t 的访问方式由全表扫描转变为索引扫描,HASH-Join 转变为 NESTEDLOOP-Join。

由上面的例子可见统计信息对于执行计划和 SQL 执行效率的重要性,即使是最基本统计信息的缺失也会导致连锁反应,从而使得优化器选择效率低的执行计划。

关于使用统计更新的建议

  • high 模式的统计信息要占用大量资源,一般不需要对数据库的所有表,或者表的所有列进行 high 模式的统计。只需要对关键的列进行统计即可。对于符合索引,一般对第一个列做高级别的统计更新,后面的列做相应低级别的即可。数据库升级以后需要重新进行统计信息的更新。
  • IDS11 版本中 create index 执行以后,会自动对表进行 Low 级别的统计信息更新。不需要再额外的做 low 级别的更新。在老版本中,需要更新统计信息以后优化器才会利用 index。
  • IDS11.5 版本中还加入了自动统计信息更新功能,对于数据分布变化频繁的数据表,可以大大减少 DBA 的工作量; 具体操作方式可以搜索 AUTO UPDATE STATISTICS (AUS)。

什么是 SQL Directives

简而言之,SQL Directives 就是一种通过在 SQL 语句中加入提示,达到强制修改执行计划的技术。

Informix 有哪些 SQL Directives

 Join-Order Directives:
  INDEX,AVOID_INDEX,INDEX_SJ,AVOID_INDEX_SJ,FULL,AVOID_FULL 
 Join-Plan Directives:
  ORDERED 
 Optimization-Goal Directives:
  USE_NL,USE_HASH,AVOID_NL,AVOID_HASH 

怎样使用 SQL Directives

使用 Directives: ORDERED

按照 FROM 子句中出现的次序进行表连接:

示例:

 SELECT --+ORDERED 
 c.x,c.y,b.z FROM a,b,c 
 WHERE c.x=b.n AND b.z=a.z; 

将先访问表 a,然后访问表 b 和 c,当表 a 和 b 都很小的时候这种执行计划速度会比其它顺序效率高。

使用 Directives: INDEX / AVOID_INDEX

指定使用或者避免使用某个索引。

示例:

 SELECT {+ORDERED, INDEX(a y), AVOID_INDEX(b n)} 
 c.x,c.y,b.z FROM a,b,c 
 WHERE c.x=b.n AND b.z=a.z; 

这里我们指定必须使用表 a 的索引 y,而避免使用表 b 的索引 n。

使用 Directives: FULL / AVOID_FULL

指定使用全表扫描或者避免权表扫描。

示例:

 SELECT {+ORDERED, INDEX(a y), AVOID_INDEX(b n), 
 FULL(c)} 
 c.x,c.y,b.z FROM a,b,c 
 WHERE c.x=b.n AND b.z=a.z; 

这里我们指定了表的访问顺序,使用 a 的索引 y,不使用 b 的索引 n,对 c 表使用全表扫描。

使用 Directives: JOIN METHOD DIRECTIVES

强迫优化器使用或者避免使用某种连接方式,Nested Loop 或者 Hash Joins,包括:USE_NL/ AVOID_NL,USE_HASH/ AVOID_HASH。

示例:

 SELECT {+ORDERED, INDEX(a y), AVOID_INDEX(b n), 
 FULL(c), USE_HASH(c/BUILD)} 
 c.x,c.y,b.z FROM a,b,c WHERE c.x=b.n AND b.z=a.z; 

这里我们指定了针对表 c 创建 hash 表。以 hashjoin 方式做连接。

使用 Directives: OPTIMIZATION GOAL

指定优化目标,包括:FIRST_ROWS,ALL_ROWS。

示例:

 SELECT {+ORDERED,FIRST_ROWS} 
 c.x,c.y,b.z FROM a,b,c WHERE c.x=b.n AND b.z=a.z; 

这里指定优化器选用有利于返回前几条记录的查询计划,比如,放弃使用 HASH-JOIN 方式,而使用 NESTEDLOOP-JOIN 方式。

外部 SQL Directives

在某种情况下,我们希望临时修改 SQL 的执行计划,或者希望在不修改应用的情况下调整 SQL 的执行计划,这时就用到了外部 Directives,我们可以针对一条 SQL 在系统表 sysdirectives 中添加相应的 Directives,当数据库执行 SQL 时,如果发现在 sysdirectives 表里有相应的 Directives,则应用之,即使 SQL 本身并没有带任何 Directives 信息。

打开和设置外部 SQL Directives

一般来说可以通过下面的方式打开:

ONCONFIG 文件中设置 EXT_DIRECTIVE 为 2

或者

ONCONFIG 文件中设置 EXT_DIRECTIVE 为 2,并且在需要考虑外部 Directives 的 Session 中执行“set environment IFX_EXTDIRECTIVES ‘ on ’”

设置外部 Directives

执行 SQL: SAVE EXTERNAL DIRECTIVES …… ACTIVE for ……

举例:

我们以 $Informix/demo/esqlc 下面的应用 demo2.ec 为例。demo2 中执行以下 SQL:

“select fname, lname from customer where lname < ?;”,我们来看如何不修改 demo2.ec 的情况下修改 SQL 的执行计划。

1. 首先,我们给 customer 的 lname 列建立 index,名称为“iddd1“

2. 打开全局 SQL Tracing,只监控我们关心的 database: demodb:

 echo " execute function 
sysadmin:task('set sql tracing on,1000,1, ’ high ’ ,'global')"| dbaccess demodb 

 echo " execute function 
sysadmin:task('set sql tracing database add','demodb')"| dbaccess demodb 

3. 编译并执行,demo2.ec:

 [[email protected] esqlc]$ make demo2 
 /opt/IBM/Informix/bin/esql -o demo2 -I/opt/IBM/Informix/incl
 -I/opt/IBM/Informix/incl/esql demo2.ec 

 [[email protected] esqlc]$ demo2 
 DEMO2 Sample ESQL Program running. 
 Column: fname Value: Ludwig 
 Column: lname Value: Pauli 
 Column: fname Value: Cathy 
 Column: lname Value: O'Brian 
 Column: fname Value: Alfred 
 Column: lname Value: Grant 
 DEMO2 Sample Program over. 

4. 查看执行计划:

这里我们只是验证对数据表的扫描方式:

 [[email protected] esqlc]$ echo "select sql_itr_info 
 from syssqltrace st, syssqltrace_iter iter 
 where sql_statement='select fname, lname 
 from customer_t where lname < ?' 
 and st.sql_id=iter.sql_id " | dbaccess sysmaster 

 sql_itr_info Index Scan 
 1 row(s) retrieved. 
 Database closed. 

可以看到,默认情况下使用了 Index Scan。

5. 针对这条 SQL 语句添加外部 Directives:

 [[email protected] esqlc]$echo "SAVE EXTERNAL DIRECTIVES 
 {+AVOID_INDEX(customer_t iddd1)} ACTIVE FOR 
 select fname, lname from customer_t  
 where lname < ?;" | dbaccess demodb 

执行完毕后,我们可以看到 demodb:sysdirectives 中多了一条记录。因为这条记录的存在,demo2 中 SQL 的执行方式将发生变化。

6. 再次执行 demo2, 然后查看执行计划 :

我们得到的结果是:

 sql_itr_info Table Scan 
 1 row(s) retrieved. 
 Database closed. 

如果我们同时生成 sqexplain.out,会发现其中多了以下内容,表明外部 Directives 起了作用:

 External Directives in effect. 
 DIRECTIVES FOLLOWED: 
 AVOID_INDEX ( customer_t 201_107 ) 
 DIRECTIVES NOT FOLLOWED: 

需要注意的是,不要过分依使用这个特性,因为一旦打开外部 Directives 特性,一条语句执行前都需要查找相匹配的 Directives,尤其当 sysdirectives 中激活的记录不止一条时, 会导致数据库性能下降。

SQLTracing 是 IDS 版本 11 的新功能,用于追踪 SQl 执行的情况,以帮助诊断 SQL,包括:SQL 执行的时间、使用的资源情况、等待每一个资源的时间等。默认情况下 IDS 的 SQLTracing 功能是关闭的。

包括以下信息:

  The user ID of the user who ran the command 
  The database session ID 
  The name of the database 
  The type of SQL statement 
  The duration of the SQL statement execution 
  The time this statement completed 
  The text of the SQL statement or a function call list 
 (also called stack trace) with the statement type, 
 for example: procedure1() calls procedure2() calls procedure3() 
 
  Statistics including the: 
–  Number of buffer reads and writes 
–  Number of page reads and writes 
–  Number of sorts and disk sorts 
–  Number of lock requests and waits 
–  Number of logical log records 
–  Number of index buffer reads 
–  Estimated number of rows 
–  Optimizer estimated cost 
–  Number of rows returned  isolation level. 


管理 SQL Tracing

通过设置 ONCONFIG 文件参数

 SQLTRACE level=LOW,ntraces=2000,size=2,mode=global 

其中:

  • level 可以是 LOW,MED,HIGH,
  • LOW 包括:statement statistics, statement text, and statement iterators.
  • MED 包括:all of the information included in low-level tracing, plus table names, the database name, and stored procedure stacks
  • HIGH 包括:all of the information included in low-level tracing, plus table names。
  • ntraces 表示追踪的最多 SQL 条数。
  • size 是指追踪每条 SQL 使用的内存大小,以 K 为单位。
  • Mode 可以是 global 或者 user,global 表示对所有用户打开 SQL 追踪功能,此模式下所有 SQL 都被追踪;user 表示以 user 模式打开 SQL 追踪,此模式下只有打开 SQL 追踪的 session 下的 SQL 才被追踪。

通过 Admin Command

以 global 模式打开 Tracing:

 EXECUTE FUNCTION task("set sql tracing on", 1000, 1,"high","global"); 
 EXECUTE FUNCTION task("set sql tracing on", "global"); 

此模式下,所有用户的所有 SQL 都被追踪。

以 user 模式打开 Tracing:

 EXECUTE FUNCTION task("set sql tracing on", 1000, 1,"high","user"); 
 EXECUTE FUNCTION task("set sql tracing on", "user"); 

此模式下,只有添加到追踪列表中用户的 SQL 才被追踪。

针对某些数据库打开 Tracing:

 EXECUTE FUNCTION task("set sql tracing database", ‘ demodb ’ ); 

针对某个 Session 打开 Tracing 功能:

 EXECUTE FUNCTION task("set sql user tracing on", session_id); 

无论用户模式还是全局模式,此方式都能打开对当前 sessionSQL 的追踪。

针对某些用户打开 Tracing 功能:

第一步,先打开选择用户模式

 EXECUTE FUNCTION task("set sql tracing on", 1000, 1,"high", ’ user ’ ); 

第二步,把要追踪的用户添加到追踪列表

 EXECUTE FUNCTION task("set sql tracing user add", “informix"); 
……


关闭 Tracing。

关闭所有 ( 包括 global/user 模式 ) 的追踪。

 EXECUTE FUNCTION task(“set sql tracing off'”); 

清除 user 模式下的追踪用户列表:

 EXECUTE FUNCTION task(“set sql tracing user clear”); 
 EXECUTE FUNCTION task(“set sql tracing user remove”,”informix”); 

清除被追踪的数据库:

 EXECUTE FUNCTION task("set sql tracing database clear"); 

注意: 在设置新的追踪方式前,最好先清除所有已设置的追踪标志,也就是先执行以下语句 z 组合以后,再设置新的追踪方式和目标。

 execute function sysadmin:task('set sql tracing off'); 
 execute function sysadmin:task('set sql tracing user clear'); 
 execute function sysadmin:task('set sql tracing database clear'); 
……


一个 SQLTracing 的例子
				
 [[email protected] sqltuning]$echo “execute function 
 sysadmin:task('set sql tracing on', 100,2,'high','global')” | dbaccess demodb
  
 [[email protected] sqltuning]$ time echo "select * from customer c, 
 orders o, cust_calls cc where c.customer_num = o.customer_num 
 and cc.customer_num=c.customer_num 
 and c.lname not like '%a';"| dbaccess demodb 
 
 [[email protected] sqltuning]$ time echo "select * 
 from sysmaster:syssqltrace where sql_id=3"| dbaccess demodb 
 
 Database selected. 
 sql_id 11 
 sql_address 1275031644 
 sql_sid 41 
 sql_uid 503 
 sql_stmttype 2 
 sql_stmtname SELECT 
 sql_finishtime 1268648703 
 sql_begintxtime 3870692 
 sql_runtime 0.007486995934 
 sql_pgreads 5 
 sql_bfreads 109 
 sql_rdcache 95.41284403670 
 sql_bfidxreads 0 
 sql_pgwrites 0 
 sql_bfwrites 0 
 sql_wrcache 0.00 
 sql_lockreq 81 
 sql_lockwaits 0 
 sql_lockwttime 0.00 
 sql_logspace 0 
 sql_sorttotal 0 
 sql_sortdisk 0 
 sql_sortmem 0 
 sql_executions 1 
 sql_totaltime 0.019677553892 
 sql_avgtime 0.019677553892 
 sql_maxtime 0.007486995934 
 sql_numiowaits 3 
 sql_avgiowaits 0.001834622424 
 sql_totaliowaits 0.005503867271 
 sql_rowspersec 1202.084264377 
 sql_estcost 11 
 sql_estrows 11 
 sql_actualrows 9 
 sql_sqlerror 0 
 sql_isamerror 0 
 sql_isollevel 2 
 sql_sqlmemory 23104 
 sql_numiterators 5 
 sql_database  
 sql_numtables 0 
 sql_tablelist None 
 sql_statement select * from customer c, orders o, cust_calls cc 
 where c.cus 
  tomer_num = o.customer_num 
  and cc.customer_num=c.customer_num 
  and c.lname not like '%a'
 sql_stmtlen 148 
 sql_stmthash 755685204 
 sql_pdq 0 
 sql_num_hvars 0 
 sql_dbspartnum 1048969 

 1 row(s) retrieved. 


通过命令行查看追踪结果:

 onstat – g his 
 Statement history: 

 Trace Level Low 
 Trace Mode Global 
 Number of traces 1000 
 Current Stmt ID 11 
 Trace Buffer size 1000 
 Duration of buffer 2455 Seconds 
 Trace Flags 0x00001611 
 Control Block  0x4bff7018 



 Statement # 11: @ 0x4bff705c 

 Database: 0x100189 
 Statement text: 
  select * from customer c, orders o, cust_calls cc 
  where c.customer_num = o.customer_num 
  and cc.customer_num=c.customer_num 
  and c.lname not like 
  '%a'

 Iterator/Explain 
 ================ 
  ID Left Right Est Cost Est Rows Num Rows Partnum Type 
  3 0 0 4 7 7 1049056 Seq Scan 
  4 0 0 1 22 1 1049038 Index Scan 
  2 3 4 8 5 7 0 Nested Join 
  5 0 0 1 23 1 1049042 Index Scan 
  1 2 5 11 11 9 0 Nested Join 


从上面的表格很容易可以得到如下图的执行计划:


图 5. 分析得出的执行计划示意
图 5. 分析得出的执行计划示意

 

Informix SQL 调整检查列表

在 select 语句中应指明需要访问的列名,而不要用 *

坏的方式:

 select * from customer; 

好的方式:

 select customer_num, fname, lname from customer; 

任何时候都应该只返回需要的列,避免不必要的资源消耗。

避免使用前置的通配符

坏的方式:

 select * from customer_t where fname like ‘ %f ’ ; 

好的方式:

 select * from customer_t where fname like ‘ name% ’ ; 

前置通配符将导致数据库不得不放弃使用索引,而使用全表扫描,如果有可能,应该尽量避免这种表达式。

使用 first、skip 关键词

如果只是想访问一个查询中符合条件的部分数据,则可以考虑使用 first,skip 关键词组合,在数据库服务器端实现分页,而不用访问全部数据。

例如:

如果每页需要 100 行数据,则:

取第一页的数据:

 select first 100 * from customer ; 

取第二页数据:

 select skip 100 first 100 * from customer ; 

取第三页数据:

 select skip 200 first 100 * from customer ; 
……
……

避免不必要的算术运算符

坏的方式:

 time echo "select count(*) from customer 
where customer_num *10/2 < 20000" | dbaccess demodb 

 real 0m11.059s 
 user 0m0.007s 
 sys 0m0.008s 

好的方式:

 time echo "select count(*) from customer where customer_num < 4000" | dbaccess demodb 
 real 0m0.049s 
 user 0m0.007s 
 sys 0m0.009s 

能提前完成的算术运算不要留给数据库,对于每一行都要进行的算术运算会导致不必要的性能问题。

避免不必要的函数运算

坏的方式:

 time echo "select count(*) from customer 
where substr(fname,1,1)='f' and customer_num < 300000"|dbaccess demodb 

 real 0m1.511s 
 user 0m0.006s 
 sys 0m0.011s 

好的方式:

 time echo "select count(*) from customer 
where fname like 'f%' and customer_num < 300000"|dbaccess demodb 

 real 0m1.225s 
 user 0m0.010s 
 sys 0m0.007s 

like,match 运算符要比内置的函数效率更高,函数调用需要占用更多的系统资源。必要时再使用。

使用 exists 替代 distinct

坏的方式:

 time echo "select distinct cu.customer_num, cu.fname 
 from cust_calls cc, customer cu 
 where cc.customer_num=cu.customer_num 
 and cu.customer_num < 90000" | dbaccess demodb 
  real 0m5.150s 
  user 0m0.299s 
  sys 0m0.401s 

好的方式:

 time echo "select cu.customer_num, cu.fname 
 from customer cu 
 where cu.customer_num < 90000 
 and exists( select cc.customer_num from cust_calls cc 
 where cc.customer_num=cu.customer_num) " | dbaccess demodb 
  real 0m2.658s 
  user 0m0.401s 
  sys 0m0.294s 

联合查询返回的内容有重复内容时,查询,返回和剔除重复记录都会占用不必要的系统资源,通过 exist 在 SQL 执行的初期就避免查询结果中包含重复记录,可以大幅提高效率。

使用 OR 替代 UNION

坏的方式:

 time echo "select customer_num, fname, lname 
 from customer where customer_num<200000 
 and fname like 'f%' union select customer_num, 
 fname, lname from customer 
 where customer_num<200000 
 and fname like '%c'" | dbaccess demodb 
  real 0m7.513s 
 user 0m1.850s 
 sys 0m0.814s 

好的方式:

time echo "select customer_num, fname, lname 
from customer where customer_num<200000 
and (fname like 'f%' or fname like '%c')" | dbaccess demodb 
 real 0m6.985s 
 user 0m1.754s 
 sys 0m0.927s 

执行 Union 会涉及到两个数据集的合并操作,会占用较多的系统资源,可能情况下,尽量避免使用。一般情况下,一个 SQL 能完成的就用一个 SQL 完成。

使用 BETWEEN 替代 IN

坏的方式:

time echo "select customer_num,lname,fname from customer 
where customer_num 
in(10000,10001,10002,10003, …… 10222)"| dbaccess demodb
 real 0m3.077s 
 user 0m0.012s 
 sys 0m0.017s 

好的方式:

 time echo "select customer_num,lname,fname 
 from customer 
 where customer_num between 10000 and 10222"| dbaccess demodb 
 real 0m1.711s 
 user 0m0.017s 
 sys 0m0.013s 

正确使用 index scan 和 sequential scan

返回表的大部分数据时,使用顺序扫描更优。

返回表的少部分数据时,使用 index 扫描更优。

数据表之间做连接时,一般情况下要在连接列上建立索引。

避免某些不明智的 SQL

一个例子:

坏的方式: SQL-1

 select count(city) from customer 
 where customer_num > all (select (customer_num) from customer_t) 

好的方式:SQL-2

 select count(city) from customer 
 where customer_num > (select max(customer_num) from customer_t) 


对于 SQL-1:

 [[email protected] sqltuning]$ time echo "set explain on; 
 select count(city) from customer 
 where customer_num > all (select (customer_num) 
 from customer_t)" | dbaccess demodb 
 Database selected. 
 Explain set. 
 (count) 
  4 
 1 row(s) retrieved. 
 Database closed. 
 real 2m7.820s 
 user 0m0.007s 
 sys 0m0.017s 

 QUERY: (OPTIMIZATION TIMESTAMP: 01-07-2010 15:08:32) 
 ------ 
 select count(city) from customer where customer_num > 
 all (select (customer_num) from customer_t) 


 Estimated Cost: 1014292 
 Estimated # of Rows Returned: 1 

  1) informix.customer: SEQUENTIAL SCAN (Serial, fragments: ALL) 

  Filters: informix.customer.customer_num > ALL  

  Subquery: 
  --------- 
  Estimated Cost: 3 
  Estimated # of Rows Returned: 1 

  1) informix.customer_t: INDEX PATH 

  (1) Index Keys: customer_num (Key-Only) (Aggregate) (Serial, fragments: ALL) 



 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 customer 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 4 2666674 10000028 05:08.62 1014293 

  type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
  group 1 1 4 05:08.62 


 Subquery statistics: 
 -------------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 customer_t 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 1000000 1000000 1000000 00:15.77 3 
 
  type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
  group 1 1 1000000 00:18.78 


对于 SQL-2:

 time echo "set explain on; select count(city) 
 from customer where customer_num > 
 (select max(customer_num) from customer_t)" | dbaccess demodb 
 Database selected. 
 Explain set. 
  (count) 
  4 
 1 row(s) retrieved. 
 Database closed. 
 real 0m0.764s 
 user 0m0.008s 
 sys 0m0.010s 

 QUERY: (OPTIMIZATION TIMESTAMP: 01-07-2010 15:11:33) 
 ------ 
 select count(city) from customer 
 where customer_num > (select max(customer_num) from customer_t) 


 Estimated Cost: 295661 
 Estimated # of Rows Returned: 1 

  1) informix.customer: INDEX PATH 

  (1) Index Keys: customer_num (Serial, fragments: ALL) 
  Lower Index Filter: informix.customer.customer_num >  

  Subquery: 
  --------- 
  Estimated Cost: 3 
  Estimated # of Rows Returned: 1 

  1) informix.customer_t: INDEX PATH 

  (1) Index Keys: customer_num (Key-Only) (Aggregate) (Serial, fragments: ALL) 



 Query statistics: 
 ----------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 
  t1 customer 

  type table rows_prod est_rows rows_scan time est_cost 
  ------------------------------------------------------------------- 
  scan t1 4 2666674 4 00:00.49 295661 
 
  type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
  group 1 1 4 00:00.49 


 Subquery statistics: 
 -------------------- 

  Table map : 
  ---------------------------- 
  Internal name Table name 
  ---------------------------- 

  type rows_prod est_rows rows_cons time 
  ------------------------------------------------- 
  group 1 1 0 00:00.23 


通过以上介绍,相信大家可以充分利用 Informix 提供的工具和特性,更加快速地定位 SQL 的性能问题,并且进行有效的调整。

原文链接:http://www.ibm.com/developerworks/cn/data/library/techarticles/dm-1009yuanht/index.html

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15082138/viewspace-672895/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/15082138/viewspace-672895/