技术开发 频道

SQL语句性能调整之注意事项

  现只对body中的每部分作出详细说明

  1) sql语句的统计信息

  我们把select语句的执行过程分成3个阶段(分析?? 执行?? 取结果),把DML语句的执行分成2个阶段(分析?? 执行)。格式化的输出文件中列出了sql语句执行的每一阶段所耗费资源统计信息,对于这些信息,是以行列的模式给出的。每一行代表每个sql执行中的具体某一阶段所耗费的相应资源。这样通过分析每个阶段耗费的资源,可以知道哪个sql有问题,并进一步知道该sql执行过程中哪一阶段出现了问题,从而快速定位问题,进而迅速解决问题。

  下面对每个列进行说明:

  call : 表示sql语句执行的每个阶段,每个sql语句的活动被分成以下3部分:

  Parse: 语句被解析的次数,主要是生成执行计划。包含hard parse与soft parse。

  需要做的工作:权限检查,表、列、引用的表的存在性检查;

  比较执行计划,选出最好的一个等等。

  Execute: 真正执行语句的时间,对于DML语句,在此阶段中修改数据;

  对于select语句,这步只是标识出查询出的行。

  Fetch : 只对select语句有效,DML语句的执行并没有该阶段

  其它列的值都是与sql执行三个阶段中所耗费的资源的统计值

  COUNT

  一个语句被parsed、executed、fetched的次数

  CPU

  执行这个语句的每个阶段耗费的cpu时间

  ELAPSED

  执行这个语句的每个阶段耗费的总时间(包括上面的cpu时间与其它时间,如读数据)

  DISK

  每个阶段读硬盘的次数(有可能从file system buffer中取得数据)

  对于该参数,我们希望该值越小越好,如果该值很大,该sql需要调整,

  建立相关索引或看是否正确的使用了索引

  QUERY

  每个阶段以consistent mode 方式从数据库buffer中查询的buffers数。

  对于查询,其buffer一般都是以consistent mode模式被读取

  CURRENT

  每个阶段以current mode方式从数据库buffer中查询的buffers数。Buffers are often

  对于DML语句,需要的buffer是以current mode模式被读取的。

  QUERY + CURRENT 的和是该sql语句总的存取的buffer数目

  ROWS

  这个sql语句最后处理的行数,不包括子查询中查询出来的行数。

  对于select语句,该值产生于fetch阶段;对于dml该值产生于execute阶段。

  因为统计耗费的时间时,最小的计量单位为0.01秒,所以如果得到一个阶段中耗费的时间为0,并不表示这个阶段没有耗费时间,而是极可能说明这个阶段耗费的时间小于0.01秒,小于计量单位,数据库无法计时,只要以0.00表示

  2) 与执行计划有关的内容

  Misses in library cache during parse: 1 -- 说明hard parse的次数

  Optimizer goal: CHOOSE -- 采用的优化器

  Parsing user id: 19 (SCOTT) -- 那个用户执行的该sql

  Rows Row Source Operation

  ------- ---------------------------------------------------

  12 TABLE ACCESS FULL EMP

  Rows Execution Plan -- 下面是真正的执行计划

  ------- ---------------------------------------------------

  0 SELECT STATEMENT GOAL: CHOOSE

  12 TABLE ACCESS (FULL) OF 'EMP'

  Misses in library cache during parse: 这个统计值是一个比较重要的指标,如果该值与该语句的parse统计值基本相等,并且该值比较大,而且该sql的parse阶段耗费的资源比较多,则说明你的语句应该采用bind variable模式。

  执行计划部分也比较重要,它能看出查询是否用了索引,和各种关联操作所采用的方法。

  建议用autotrace进行跟踪而不是用上面方法进行跟踪的原因:

  虽然上面的方法给出了sql语句的执行计划、统计数据等信息,但是因为tkprof的格式化输出不给出详尽的costs与statistics信息,这使我们在利用格式化输出判断系统到底是使用基于代价的优化方法还是基于规则的优化方法时,感到很迷茫,我们不能明确的知道到底系统在使用那种优化器。

  但是仅仅因为这个原因就使我们放弃上面跟踪方法而该用autotrace功能是不够的,因为:

  1) 到底系统是使用基于规则的还是基于代价的优化器我们可以借助与其它信息来识别,而且从10G以后,oracle就宣布不再使用基于规则的优化器了。

  2) 上面的跟踪办法能实现autotrace不能完成的功能,因为autotrace只能跟踪本会话,而不能跟踪其它会话,这样假如有一个正在运行的程序出现了性能问题,我们就不能使用autrace去跟踪sql的执行情况。

  下面以一个具体的例子的数据说明如何利用格式化的输出文件进行sql的调整:

  第一步: - 现看格式化输出文件最后部分,即汇总部分

  ===========================================================

  OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS

  | call | count | cpu | elapsed | disk | query | current | rows |

  |--------- |------- |------ |--------- |--------- |-------- |--------- |-------- |

  | Parse | [A] 7 | 1.87 | 4.53 | 385 | [G] 553 | 22 | 0 |

  | Execute | [E] 7 | 0.03 | 0.11 | [P] 0 | [C] 0 | [D] 0 | [F] 0 |

  | Fetch | [E] 6 | 1.39 | 4.21 | [P] 182 | [C] 820 | [D] 3 | [F] 20 |

  --------------------------------------------------------------------------

  Misses in library cache during parse: 5

  Misses in library cache during execute: 1

  8 user SQL statements in session.

  12 internal SQL statements in session.

  [B] 54 SQL statements in session.

  3 statements EXPLAINed in this session.

  (1). 通过比较 [A] 与 [B],我们可以发现是否有过量的parsing现象。在上面的例子中,我们可以看到在session中执行了54个语句,但是只有7次parses,所以这是比较正常的,没有过量的parse现象。

  (2). 利用 [P], [C] & [D] 来决定数据库高速缓存的命中率问题

  Hit Ratio is logical reads/physical reads:

  Logical Reads = Consistent Gets + DB Block Gets

  Logical Reads = query + current

  Logical Reads = Sum[C] + Sum[D]

  Logical Reads = 0+820 + 0+3

  Logical Reads = 820 + 3

  Logical Reads = 823

  Hit Ratio = 1 - (Physical Reads / Logical Reads)

  Hit Ratio = 1 - (Sum[P] / Logical Reads)

  Hit Ratio = 1 - (128 / 823)

  Hit Ratio = 1 - (0.16)

  Hit Ratio = 0.84 or 84%

  (3). 我们希望fetch的次数要比rows小,即一次fetch可以取多行数据(array fetching),

  可以我们可以更高效的取得查询数据。

  这可以通过比较[E]与[F].

  [E] = 6 = Number of Fetches

  [F] = 20 = Number of Rows

  从上面的信息中我们可以看到,6次fetch总共取了20行数据,结果不是很坏。如果使用了经过良好配置arrayfetching,则可以用更少的fetch次数取到同样数量的数据,性能会更好。

  (4). [G] 表示为了对语句进行分析,读数据字典告诉缓存的次数

  - 这个参数对性能的影响不大,一般不用关心。而且这个统计值一般不是我们可以控制的。

  第二步 – 检查耗费大量资源的语句

  ===============================================

  update ...

  where ...

  | call | count | cpu | elapsed | disk | query | current | rows |

  |--------- |------- |----- |--------- |------ |-------- |--------- |-------- |

  | Parse | 1 | 7 | 122 | 0 | 0 | 0 | 0 |

  | Execute | 1 | 75 | 461 | 5 | [H] 297 | [I] 3 | [J] 1 |

  | Fetch | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

  -----------------------------------------------------------------------

  [H] 表明需要访问297个数据块才能找到我们需要修改的数据。

  [I] 表明我们的修改操作才修改3个数据块中的数据

  [J] 表明我们只修改了一行数据(其它数据块的修改应为undo、redo信息)

  为了修改一行数据而要搜寻297个数据块。

  考虑是否需要在查许的列上建一个索引!

  第三步 – 查看是否有过量的parse现象

  ==============================

  select ...

  | call | count | cpu | elapsed | disk | query | current | rows |

  |--------- |------ -|--------- |--------- |----- -|------- -|-------- -|------ -|

  | Parse | [M] 2 | [N] 221 | 329 | 0 | 45 | 0 | 0 |

  | Execute | [O] 3 | [P] 9 | 17 | 0 | 0 | 0 | 0 |

  | Fetch | 3 | 6 | 8 | 0 | [L] 4 | 0 | [K] 1 |

  -------------------------------------------------------------------------

  Misses in library cache during parse: 2 [Q]

  [K] 表明这个查询只返回一行数据

  [L] 表明我们需要fetch 4次才能得到数据,这是正常的,因为需要额外的fetch操作以便检查是否fetch到cursor的最后,当然还可能有其它fetch开销。

  [M] 表明我们进行了两次parse(包含hard parse 与soft parse) – 这是我们不想看到的,

  特别是当parse阶段操作耗费cpu资源比execute阶段耗费的cpu资源([O] & [P])

  多得多得时候。 [Q] 表明这两个parse操作都是hard parse。如果[Q]的值为1,

  这这个语句有一个hard parse,然后跟着一个soft parse(仅仅从库缓存中得到

  上次分析的信息,比hard parse要高效的多)。

  对上面的例子来说,结果并不是特别的坏,因为该语句只执行2次,然而如果对于频繁执行的sql来说,如果几乎每次执行都需要hard parse,则结果就会变的很坏,此时我们说该语句有过量的parse现象(excessive parsing)。

  o解决该问题的方法:

  - 使用bind variables

  - 使shared pool足够大,从而在内存中容纳你执行过的每一条语句,

  以便下一次可以重用该语句。但这种方法治标不治本,在繁忙的系统中有时会引起

  ora-04031: unable to allocate %s bytes of shared memory (%s,%s,%s)

  错误。

  - 使用8i新引入的参数cursor_sharing,建议在经过测试后再使用该参数,因为有时

  使用该参数后会引起系统性能下降

  如何降低parse阶段使用的cpu时间

  1.Rewrite the application so statements do not continually reparse.

  2.Reduce parsing by using the initialization parameter SESSION_CACHED_CURSORS

  3.Use bind variables to reduce parsing.

  注意:

  记住如果cursor没有被关闭,将在tkprof的输出文件中看不到任何该sql的输出。设置 SQL_TRACE = false并不能关闭PL/SQL 的child cursors,所以要在自己的存储过程中养成及时关闭显式cursor的习惯。令我们高兴的是,在SQL*Plus中,语句一旦执行完毕,该语句对应的cursor也自动关闭了。

0
相关文章