1. PLSQL程序优化原则
1.1 导致性能问题的内在原因
导致系统性能出现问题从系统底层分析也就是如下几个原因:
l CPU占用率过高,资源争用导致等待
l 内存使用率过高,内存不足需要磁盘虚拟内存
l IO占用率过高,磁盘访问需要等待
1.2 PLSQL优化的核心思想
PLSQL优化实际上就是避免出现“导致性能问题的内在原因”,实际上编写程序,以及性能问题跟踪应该本着这个核心思想去考虑和解决问题。
l PLSQL程序占用CPU的情况
n 系统解析SQL语句执行,会消耗CPU的使用
n 运算(计算)会消耗CPU的使用
l PLSQL程序占用内存的情况
n 读写数据都需要访问内存
n 内存不足时,也会使用磁盘
l PLSQL程序增大IO的情况
n 读写数据都需要访问磁盘IO
n 读取的数据越多,IO就越大
大家都知道CPU现在都很高,计算速度非常快;访问内存的速度也很快;但磁盘的访问相对前两个相比速度就差的非常大了,因此PLSQL性能优化的重点也就是减少IO的瓶颈,换句话说就是尽量减少IO的访问。
性能的优先级CPU->内存->IO,影响性能的因素依次递增。根据上面的分析,PLSQL优化的核心思想为:
1. 避免过多复杂的SQL脚本,减少系统的解析过程
2. 避免过多的无用的计算,例如:死循环
3. 避免浪费内存空间没有必要的SQL脚本,导致内存不足
4. 内存中计算和访问速度很快
5. 尽可能的减少磁盘的访问的数据量,该原则是PLSQL优化中重要思想。
6. 尽可能的减少磁盘的访问的次数,该原则是PLSQL优化中重要思想。
下面的章节具体介绍常见影响性能的SQL语句情况。
1.3 ORACLE优化器
ORACLE的优化器:
a. RULE (基于规则) b. COST (基于成本) c.CHOOSE (选择性)
设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明,如RULE,COST,CHOOSE,ALL_ROWS,FIRST_ROWS . 你当然也在SQL句级或是会话(session)级对其进行覆盖.
为了使用基于成本的优化器(CBO, Cost-Based Optimizer) , 你必须经常运行analyze 命令,以增加数据库中的对象统计信息(object statistics)的准确性.
如果数据库的优化器模式设置为选择性(CHOOSE),那么实际的优化器模式将和是否运行过analyze命令有关. 如果table已经被analyze过, 优化器模式将自动成为CBO , 反之,数据库将采用RULE形式的优化器.
在缺省情况下,ORACLE采用CHOOSE优化器, 为了避免那些不必要的全表扫描(full table scan) , 你必须尽量避免使用CHOOSE优化器,而直接采用基于规则或者基于成本的优化器.
在oracle10g前默认的优化模式是CHOOSE,10g默认是ALL_ROWS,我不建议大家去改动ORACLE的默认优化模式。
主要说明了在SQL编写上和PLSQL程序编写上可以优化的地方。
1.4.1 选择最有效率的表名顺序
只在基于规则的优化器rule中有效,目前我们oracle选择的优化器基本都不选择rule,因此该问题基本不会出现,但为了安全和规范起见,建议编程习惯采用该规则。
ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 drivingtable)将被最先处理. 在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表.当ORACLE处理多个表时, 会运用排序及合并的方式连接它们.首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行派序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并.
例如:
表 ac01有 16,384 条记录
表 ab01 有1 条记录
选择ab01作为基础表 (好的方法)
select count(*) from ac01,ab01 执行时间0.96秒
选择ac01作为基础表 (不好的方法)
select count(*) from ab01,ac01 执行时间26.09秒
1.4.2 WHERE子句中的连接顺序
ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前
例如:
(低效)
SELECT ab01.aab001,ab02.aab051
FROM ab01,ab02
WHERE ab02.aae140=’31’
AND ab01.aab001=ab02.aab001;
(高效)
SELECT ab01.aab001,ab02.aab051
FROM ab01,ab02
WHERE ab01.aab001=ab02.aab001
AND ab02.aae140=’31’;
1.4.3 SELECT子句中避免使用 ‘ * ‘
当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 ‘*' 是一个方便的方法.不幸的是,这是一个非常低效的方法. 实际上,ORACLE在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。
1.4.4 用EXISTS替代IN
实际情况看,使用exists替换in效果不是很明显,基本一样。
在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率.
低效:
SELECT *
FROM ac01
Where aac001 in (select aac001 from ac02 whereaab001=str_aab001 and aae140=’31’);
或
SELECT *
FROM ac01
Where aac001 in (select distinct aac001 from ac02where aab001=str_aab001 and aae140=’31’);
注意使用distinct也会影响速度
高效:
SELECT *
FROM ac01
Where exists (select 1from ac02 where aac001=ac01.aac001 and aab001=str_aab001 and aae140=’31’);
in的常量列表是优化的(例如:aab019 in (‘20’,’30’)),不用exists替换;in列表相当于or
1.4.5 用NOT EXISTS替代NOT IN
Oracle在10g之前版本not in都是最低效的语句,虽然在10g上not in做到了一些改进,但仍然还是存在一些问题,因此我们一定要使用not exists来替代not in的写法。
在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我们可以把它改写成NOT EXISTS.
例如:
SELECT *
FROM ac01
WHERE aab001 NOT IN (SELECT aab001 from ab01 where aab020=’100’);
为了提高效率.改写为:
SELECT *
FROM ac01
WHERE not exists (SELECT 1 from ab01 where aab001=ac01.aab001 and aab020=’100’);
1.4.6 用表连接替换EXISTS
在子查询的表和主表查询是多对一的情况,一般采用表连接的方式比EXISTS更有效率。
例如:
低效:
SELECT ac01.*
FROM ac01
Where exists (select 1 from ac02
whereaac001=ac01.aac001
andaab001=ac01.aab001
and aae140='31'
andaae041='200801');
高效:
SELECT ac01.*
FROM ac02,ac01
Where ac02.aac001=ac01.aac001
and ac02.aab001=ac01.aab001
and ac02.aae140='31'
and aae041='200801';
到底exists和表关联哪种效率高,其实是根据两个表之间的数据量差别大小是有关的,如果差别不大实际上速度基本差不多。
1.4.7 用EXISTS替换DISTINCT