1、PLSQL程序优化和性能分析方法要点1. 前言1.1 目的性能测试是测试中比较重要的工作,性能测试应分为压力的测试和性能的测试,其中性 能问题中绝大部分都是由于程序编写的不合理、不规范造成的。本文档说明了程序中常见的 不优化的脚本编写,导致的性能问题,并且在也描述了怎样去跟踪和解决程序上的性能问题 的方法。在最后一章里面描述了做一个白盒测试工具测试性能问题的设计思想。1.2 文档说明本文档只说明 PLSQL 编写的优化问题,不包括 ORACLE 本身的性能优化(内存 SGA、系统参数、表空间等)、操作系统的性能问题和硬件的性能问题。对于 PLSQL 程序优 化方面的内容有很多,本文档列出在我们
2、实际工作中一些常见的情况。本文档难免有不正确 的地方,也需要大家给予指正。本文档举例说明的问题语句不是实际程序中真正存在的,只是让大家能看起来更容易理 解,但这些语句也不代表在我们程序中其他部分语句不存在这些问题。举例说明中的语句采用的是社保核心平台的数据字典,在举例描述中没有标明表名和字 段名的含义,还需单独参考。1.3 词汇表词汇名称词汇含义备注1.4 参考资料PLSQL 程序优化和性能测试方法1ORACLE SQL 性能优化系列232. PLSQL 程序优化原则2.1 导致性能问题的内在原因导致系统性能出现问题从系统底层分析也就是如下几个原因: CPU 占用率过高,资源争用导致等待 内存
3、使用率过高,内存不足需要磁盘虚拟内存 IO 占用率过高,磁盘访问需要等待2.2 PLSQL 优化的核心思想PLSQL 优化实际上就是避免出现“导致性能问题的内在原因”,实际上编写程序,以及 性能问题跟踪应该本着这个核心思想去考虑和解决问题。 PLSQL 程序占用 CPU 的情况 系统解析 SQL 语句执行,会消耗 CPU 的使用 运算(计算)会消耗 CPU 的使用 PLSQL 程序占用内存的情况 读写数据都需要访问内存 内存不足时,也会使用磁盘 PLSQL 程序增大 IO 的情况 读写数据都需要访问磁盘 IO 读取的数据越多,IO 就越大大家都知道 CPU 现在都很高,计算速度非常快;访问内存
4、的速度也很快;但磁盘的访 问相对前两个相比速度就差的非常大了,因此 PLSQL 性能优化的重点也就是减少 IO 的瓶颈, 换句话说就是尽量减少 IO 的访问。性能的优先级 CPU-内存-IO,影响性能的因素依次递增。根据上面的分析,PLSQL 优化的核心思想为:1. 避免过多复杂的 SQL 脚本,减少系统的解析过程2. 避免过多的无用的计算,例如:死循环3. 避免浪费内存空间没有必要的 SQL 脚本,导致内存不足4. 内存中计算和访问速度很快5. 尽可能的减少磁盘的访问的数据量,该原则是 PLSQL 优化中重要思想。6. 尽可能的减少磁盘的访问的次数,该原则是 PLSQL 优化中重要思想。下面
5、的章节具体介绍常见影响性能的 SQL 语句情况。2.3 ORACLE 优化器ORACLE 的优化器:第 2 页 / 共 27 页PLSQL 程序优化和性能测试方法a. RULE (基于规则) b. COST (基于成本) c. CHOOSE (选择性)设置缺省的优化器,可以通过对 init.ora 文件中 OPTIMIZER_MODE 参数的各种声明,如 RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你当然也在 SQL 句级或是会话 (session)级对其进行覆盖.为了使用基于成本的优化器(CBO, Cost-Based Optimizer) , 你必须经常运
6、行 analyze 命令,以 增加数据库中的对象统计信息(object statistics)的准确性.如果数据库的优化器模式设置为选择性(CHOOSE),那么实际的优化器模式将和是否运行 过 analyze 命令有关. 如果 table 已经被 analyze 过, 优化器模式将自动成为 CBO , 反之, 数据库将采用 RULE 形式的优化器.在缺省情况下,ORACLE 采用 CHOOSE 优化器,为了避免那些不必要的全表扫描(fulltable scan) , 你必须尽量避免使用 CHOOSE 优化器,而直接采用基于规则或者基于成本的 优化器.在 oracle10g 前默认的优化模式是
7、CHOOSE,10g 默认是 ALL_ROWS,我不建议大家去 改动 ORACLE 的默认优化模式。2.4 PLSQL 优化主要说明了在 SQL 编写上和 PLSQL 程序编写上可以优化的地方。2.4.1 选择最有效率的表名顺序只在基于规则的优化器 rule 中有效,目前我们 oracle 选择的优化器基本都不选择 rule,因此该问题基 本不会出现,但为了安全和规范起见,建议编程习惯采用该规则。ORACLE 的解析器按照从右到左的顺序处理 FROM 子句中的表名,因此 FROM 子句中写在最后的表 (基础表 driving table)将被最先处理. 在 FROM 子句中包含多个表的情况下,
8、你必须选择记录条数最少的表 作为基础表.当 ORACLE 处理多个表时, 会运用排序及合并的方式连接它们.首先,扫描第一个表(FROM 子 句中最后的那个表)并对记录进行派序,然后扫描第二个表(FROM 子句中最后第二个表),最后将所有从第二 个表中检索出的记录与第一个表中合适记录进行合并.例如:表 ac01 有 16,384 条记录表 ab01 有 1 条记录选择 ab01 作为基础表 (好的方法)select count(*) from ac01,ab01 执行时间 0.96 秒选择 ac01 作为基础表 (不好的方法)第 3 页 / 共 27 页PLSQL 程序优化和性能测试方法sele
9、ct count(*) from ab01,ac01 执行时间 26.09 秒2.4.2 WHERE 子句中的连接顺序ORACLE 采用自下而上的顺序解析 WHERE 子句,根据这个原理,表之间的连接必须写在其他 WHERE 条件之前例如:(低效)SELECT ab01.aab001,ab02.aab051FROM ab01,ab02WHERE ab02.aae140=31AND ab01.aab001=ab02.aab001;(高效)SELECT ab01.aab001,ab02.aab051FROM ab01,ab02WHERE ab01.aab001=ab02.aab001AND ab0
10、2.aae140=31;2.4.3 SELECT 子句中避免使用 * 当你想在 SELECT 子句中列出所有的 COLUMN 时,使用动态 SQL 列引用 * 是一个方便的方法.不 幸的是,这是一个非常低效的方法. 实际上,ORACLE 在解析的过程中, 会将* 依次转换成所有的列名, 这 个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。2.4.4 用 EXISTS 替代 IN实际情况看,使用 exists 替换 in 效果不是很明显,基本一样。在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, EXISTS(或 NOT EXISTS)通常将提高
11、查询的效率.低效:SELECT *FROM ac01Where aac001 in (select aac001 from ac02 where aab001=str_aab001 and aae140=31);或SELECT *FROM ac01Where aac001 in (select distinct aac001 from ac02 where aab001=str_aab001 and aae140=31); 注意使用 distinct 也会影响速度第 4 页 / 共 27 页使用PLSQL 程序优化和性能测试方法高效:SELECT *FROM ac01Where exists
12、(select 1 from ac02 where aac001=ac01.aac001 and aab001=str_aab001 and aae140=31);in 的常量列表是优化的(例如:aab019 in (20,30),不用 exists 替换;in 列表相当于 or2.4.5 用 NOT EXISTS 替代 NOT INOracle 在 10g 之前版本 not in 都是最低效的语句,虽然在 10g 上 not in 做到了一些改进,但仍然还是存在 一些问题,因此我们一定要使用 not exists 来替代 not in 的写法。在子查询中,NOT IN 子句将执行一个内部的排
13、序和合并. 无论在哪种情况下,NOT IN 都是最低效的 (因为 它对子查询中的表执行了一个全表遍历). 为了避免使用 NOT IN ,我们可以把它改写成 NOT EXISTS.例如:SELECT *FROM ac01WHERE aab001 NOT IN (SELECT aab001 from ab01 where aab020=100);为了提高效率.改写为:SELECT *FROM ac01WHERE not exists (SELECT 1 from ab01 where aab001=ac01.aab001 and aab020=100);2.4.6 用表连接替换 EXISTS在子查
14、询的表和主表查询是多对一的情况,一般采用表连接的方式比 EXISTS 更有效率。例如:低效:SELECT ac01.*FROM ac01Where exists (select 1 from ac02where aac001=ac01.aac001and aab001=ac01.aab001and aae140=31and aae041=200801);高效:SELECT ac01.*FROM ac02,ac01第 5 页 / 共 27 页PLSQL 程序优化和性能测试方法Where ac02.aac001=ac01.aac001and ac02.aab001=ac01.aab001and a
15、c02.aae140=31and aae041=200801;到底 exists 和表关联哪种效率高,其实是根据两个表之间的数据量差别大小是有关的,如果差别不大实际 上速度基本差不多。2.4.7 用 EXISTS 替换 DISTINCT当提交一个包含一对多表信息(比如个人基本信息表和个人参保信息表)的查询时,避免在 SELECT 子句 中使用 DISTINCT. 一般可以考虑用 EXISTS 替换例如:低效:select distinct ac01.aac001from ac02,ac01where ac02.aac001 = ac01.aac001and ac02.aae140=31and
16、ac01.aab001=100100;高效:select ac01.aac001from ac01where exists(select 1 from ac02 where aac001 = ac01.aac001and aae140=31)and ac01.aab001=100100;EXISTS 使查询更为迅速,因为 RDBMS 核心模块将在子查询的条件一旦满足后,立刻返回结果。因此如果 不是特别研究和追求速度的话(例如:数据转换),查询一个表的数据需要关联其他表的这种情况查询, 建议采用 EXISTS 的方式。2.4.8 减少对表的查询该问题是我们编程中出现过的问题,请大家一定注意,并且
17、该类问题优化可以带来较大性能的提升。例如:低效cursor cur_kc24_mz isSelect akc260第 6 页 / 共 27 页PLSQL 程序优化和性能测试方法from kc24where akb020 =str_akb020and aka130=11;cursor cur_kc24_zy isSelect akc260from kc24where akb020 =str_akb020and aka130=21;for rec_mz in cur_kc24_mz loop门诊处理.end loop;for rec_mz in cur_kc24_zy loop住院处理.end l
18、oop;高效cursor cur_kc24 isSelect akc260,aka130from kc24where akb020 =str_akb020and aka130 in (11,21);for rec_kc24 in cur_kc24 loopif rec_kc24.aka130=11 then门诊处理.end if;if rec_kc24.aka130=21 then住院处理.end if;end loop;高效的做法使用同样的条件(或者说是索引)只访问一次磁盘,低效的做法访问了 2 次磁盘,这样速度 差别将近 2 倍。2.4.9 避免循环(游标)里面嵌查询游标里面不能嵌入查询(
19、或者再嵌游标),其实也不能有 update delete 等语句,只能有 insert 语句。但在实 际的编程情况下是不可能完全避免的,但我们一定要尽量避免。该类问题也是我们程序中出现过的问题, 该类问题也可以大大提升程序效率,请大家一定注意。例如:第 7 页 / 共 27 页PLSQL 程序优化和性能测试方法低效:Cursor cur_ac04 isSelect aac001,akc010From ac04Where aab001= prm_aab001;For rec_ac04 in cur_ac04 loopSelect aac008Into str_aac008from ac01whe
20、re aac001=rec_ac04.aac001;if str_aac008=1 thenn_jfje := rec_ac04.akc010*0.08;end if;if str_aac008=2 thenn_jfje := rec_ac04.akc010*0.1;end if;End loop;高效:Cursor cur_ac04 isSelect ac01.aac001,ac04.akc010,ac01.aac008From ac04,ac01Where ac04.aac001=ac01.aac001and aab001= prm_aab001;For rec_ac04 in cur_a
21、c04 loopif rec.aac008=1 thenn_jfje := rec_ac04.akc010*0.08;end if;if rec.aac008=2 thenn_jfje := rec_ac04.akc010*0.1;end if;end loop;优化的方法是尽量把游标循环中的查询语句放到游标查询中一起查询出来,这样相当于只访问了 1 次磁 盘读到内存;如果放到游标中的话,假如游标有 100 万数据量,那么程序需要 100 万次磁盘,可以想象 浪费了多少 IO 的访问。如果在程序编写上没有办法避免游标中有查询语句的话(一般情况是可以避免的),那么也要保证游标中 的查询使用的索引
22、(即查询速度非常快),例如:游标 100 万数据量,游标中的查询语句执行需要 0.02 秒, 从这个速度上来说是很快的,但总体上看 100 万*0.02 秒=2 万秒=5 小时 33 分钟,如果写一个不够优化的 语句需要 1 秒,那么需要几天能执行完呢?第 8 页 / 共 27 页PLSQL 程序优化和性能测试方法2.4.10 尽量用 union all 替换 unionUnion 会去掉重复的记录,会有排序的动作,会浪费时间。因此在没有重复记录的情况下或可以允许有重 复记录的话,要尽量采用 union all 来关联。2.4.11 使用 DECODE 函数来减少处理时间使用 DECODE 函
23、数可以避免重复扫描相同记录或重复连接相同的表.例如:(低效)select count(1) from ac01 where aab001=100001 and aac008=1;select count(1) from ac01 where aab001=100001 and aac008=2;(低效)Select count(1),aac008From ac01Where aab001=100001and aac008 in (1,2)group by aac008;(高效)select count(decode(aac008,1,1,null) zz,count(decode(aac008
24、,2,1,null) txfrom ac01where aab001=100001;特别说明:group by 和 order by 都会影响性能,编程时尽量避免没有必要的分组和排序,或者通过其他的有效的编 程办法去替换,比如上面的处理办法。2.4.12 group by 优化Group by 需要查询后排序,速度慢影响性能,如果查询数据量大,并且分组复杂,这样的查询语句在性 能上是有问题的。尽量避免使用分组或者采用上面的一节的办法去代替。采用 group by 的也一定要进行优化。例如:低效select ac04.aac001,ac01.aac002,ac01.aac003,sum(aac0
25、40),ac01.aab001from ac04,ac01where ac04.aac001=ac01.aac001 and ac01.aab001=1000000370group by ac04.aac001,ac01.aac002,ac01.aac003,ac01.aab001;第 9 页 / 共 27 页PLSQL 程序优化和性能测试方法高效:select ac04.aac001,ac01.aac002,ac01.aac003,gzze,ac01.aab001from (select aac001,sum(aac040) gzze from ac04 group by aac001) a
26、c04,ac01where ac04.aac001=ac01.aac001and aab001=1000000370;2.4.13 尽量避免用 order byOrder by 需要查询后排序,速度慢影响性能,如果查询数据量大,排序的时间就很长。但我们也不能避免 不使用,这样大家一定注意一点的是如果使用 order by 那么排序的列表必须符合索引,这样在速度上会得 到很大的提升。2.4.14 用 Where 子句替换 HAVING 子句避免使用 HAVING 子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序, 总计等操作. 如果能通过 WHERE 子句限制
27、记录的数目,那就能减少这方面的开销.例如:低效:SELECT aac008,count(1)FROM ac01GROUP BY aac008HAVING aac008 in (1,2);高效SELECT aac008,count(1)FROM ac01Where aac008 in (1,2)GROUP BY aac008 ;HAVING 中的条件一般用于对一些集合函数的比较,如 COUNT() 等等. 除此而外,一般的条件应该写在 WHERE 子句中2.4.15 使用表的别名(Alias)当在 SQL 语句中连接多个表时, 请使用表的别名并把别名前缀于每个 Column 上.这样一来,就可以
28、减少解 析的时间并减少那些由 Column 歧义引起的语法错误.2.4.16 删除重复记录一般数据转换的程序经常会使用到该方法。最高效的删除重复记录方法 ( 因为使用了 ROWID)第 10 页 / 共 27 页PLSQL 程序优化和性能测试方法DELETE FROM ac01 aWHERE a.rowid (SELECT MIN(b.rowid)FROM ac01 bWHERE a.aac002=b.aac002and a.aac003=b.aac003 );2.4.17 COMMIT 使用数据转换的程序需要关注这一点。1. Commit 执行也是有时间的,不过时间特别短,但提交频率特别大,
29、必然也会浪费时间。2. commit 可以释放资源,在大量数据更新时,必须及时提交。a. 回滚段上用于恢复数据的信息.b. 被程序语句获得的锁c. redo log buffer 中的空间d. ORACLE 为管理上述 3 种资源中的内部花费例如:Cur_ac20 有 5000 万数据n_count :=0;For arec in cur_ac20 loopInsert into ac20 n_count := n_count + 1;If n_count = = 100000 then -10 万一提交commit;n_count := 0;End if;End loop;Commit;如果
30、 1 条一提交,需要提交 5000 万必然浪费时间;如果整体提交,资源不能释放,性能必须下降。 在实际编程时,应注意提交的次数和提交的数据量的平衡关系。2.4.18 减少多表关联表关联的越多,查询速度就越慢,尽量减少多个表的关联,建议表关联不要超过 3 个(子查询也属于表 关联)。数据转换上会存在大数据量表的关联,关联多了会影响索引的效率,可以采用建立临时表的办法,有时 更能提高速度。2.4.19 批量数据插入数据转换时或者大业务数据插入时,有以下几种办法进行数据插入(不包括 imp、impdp 和 sqlloader) Insert into select 方式将查询的结果一次插入到目标表中。第 11 页 / 共 27 页PLSQL 程序优化和性能测试方法例如:Insert into ac01_bak select * from ac01;由于
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1