Oracle SQL性能优化

1. 选用适合的ORACLE优化器
    ORACLE的优化器共有3种: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优化器,而直接采用基于规则或者基于成本的优化器.

2. 访问Table的方式
    ORACLE 采用两种访问表中记录的方式:
    (a).全表扫描:全表扫描就是顺序地访问表中每条记录.ORACLE采用一次读入多个数据块
                (database block)的方式优化全表扫描.
    (b).通过ROWID访问表:你可以采用基于ROWID的访问方式情况,提高访问表的效率,ROWID包
                       含了表中记录的物理位置信息.ORACLE采用索引(INDEX)实现了数据
                       和存放数据的物理位置(ROWID)之间的联系.通常索引提供了快速访
                       问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高.

3. 共享SQL语句
    为了不重复解析相同的SQL语句,在第一次解析之后, ORACLE将SQL语句存放在内存中.这块
位于系统全局区域SGA(system global area)的共享池(shared pool)中的内存可以被所有的
数据库用户共享.因此,当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前的执行过
的语句完全相同, ORACLE就能很快获得已经被解析的语句以及最好的执行路径.ORACLE的这个功
能大大地提高了SQL的执行性能并节省了内存的使用.可惜的是ORACLE只对简单的表提供高速缓冲
(cache buffering),这个功能并不适用于多表连接查询.数据库管理员必须在init.ora中为这个
区域设置合适的参数,当这个内存区域越大,就可以保留更多的语句,当然被共享的可能性也就越大
了.当你向ORACLE提交一个SQL语句,ORACLE会首先在这块内存中查找相同的语句.这里需要注明的
是,ORACLE对两者采取的是一种严格匹配,要达成共享,SQL语句必须完全相同(包括空格,换行等).
    共享的语句必须满足三个条件:
    A.字符级的比较:当前被执行的语句和共享池中的语句必须完全相同.
    例如:SELECT * FROM EMP;和下列每一个都不同
         SELECT * from EMP;
         Select * From Emp;
         SELECT * FROM EMP;
    B.两个语句所指的对象必须完全相同:
    例如: 用户      对象名         如何访问
          Jack   Sal_limit     private synonym
                 Work_city     public synonym
                 Plant_detail  public synonym
          Jill   Sal_limit     private synonym
                 Work_city     public synonym
                 Plant_detail  table owner
    考虑一下下列SQL语句能否在两个用户之间共享:
    SQL                                    能否共享  原因
    select max(sal_cap) from sql_limit;    不能      每个用户都有一个private synonym
                                                     -Sal_limit,他们是不同的对象
    select count(*) from work_city         能        两个用户访问相同的对象public
     where sdesc like 'NEW%';                        synonym-Work_city
    select a.sdesc,b.location              不能      用户Jack通过public synonym访
      from work_city a,plant_detail b                问Plant_detail,而Jill是表的所
     where a.city_id=b.city_id                       有者,对象不同
    C.两个SQL语句中必须使用相同的名字的绑定变量(bind variables)
    例如:第一组的两个SQL语句是相同的(可以共享),而第二组中的两个语句是不同的(即使在运行
          时,赋于不同的绑定变量相同的值)
    a.select pin , name from people where pin = :blk1.pin;
      select pin , name from people where pin = :blk1.pin;
    b.select pin , name from people where pin = :blk1.ot_ind;
      select pin , name from people where pin = :blk1.ov_ind;

4. 选择最有效率的表名顺序(只在基于规则的优化器中有效)
    ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基
础表 driving table)将被最先处理.在FROM子句中包含多个表的情况下,你必须选择记录条数最少
的表作为基础表.当ORACLE处理多个表时,会运用排序及合并的方式连接它们.首先,扫描第一个表
(FROM子句中最后的那个表)并对记录进行排序,然后扫描第二个表(FROM子句中最后第二个表),最后
将所有从第二个表中检索出的记录与第一个表中合适记录进行合并.
    例如:表 TAB1 16,384 条记录,表 TAB2 1 条记录
    选择TAB2作为基础表 (最好的方法):select count(*) from tab1,tab2 执行时间0.96秒
    选择TAB1作为基础表 (不佳的方法):select count(*) from tab2,tab1 执行时间26.09秒
    如果有3个以上的表连接查询,那就需要选择交叉表(intersection table)作为基础表,交叉表
是指那个被其他表所引用的表.
    例如:EMP表描述了LOCATION表和CATEGORY表的交集.
    SELECT * FROM LOCATION L,CATEGORY C,EMP E
     WHERE E.EMP_NO BETWEEN 1000 AND 2000
       AND E.CAT_NO = C.CAT_NO
       AND E.LOCN = L.LOCN
    将比下列SQL更有效率
    SELECT * FROM EMP E,LOCATION L,CATEGORY C
     WHERE E.CAT_NO = C.CAT_NO
       AND E.LOCN = L.LOCN
       AND E.EMP_NO BETWEEN 1000 AND 2000

5. WHERE子句中的连接顺序.
    ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条
件之前,那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾.
    例如:(低效,执行时间156.3秒)
    SELECT * FROM EMP E
     WHERE SAL > 50000
       AND JOB = 'MANAGER'
       AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO);
    (高效,执行时间10.6秒)
    SELECT * FROM EMP E
     WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO)
       AND SAL > 50000
       AND JOB = 'MANAGER';

6. SELECT子句中避免使用'*'
    当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用'*'是一个方便的方法.
不幸的是,这是一个非常低效的方法.实际上,ORACLE在解析的过程中,会将'*'依次转换成所有
的列名,这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间.

7. 减少访问数据库的次数
    当执行每条SQL语句时,ORACLE在内部执行了许多工作:解析SQL语句,估算索引的利用率,绑
定变量,读数据块等等.由此可见,减少访问数据库的次数,就能实际上减少ORACLE的工作量.
    例如,以下有三种方法可以检索出雇员号等于0342或0291的职员.
    方法1:(最低效)
    SELECT EMP_NAME,SALARY,GRADE
      FROM EMP
     WHERE EMP_NO = 342;
    SELECT EMP_NAME,SALARY,GRADE
      FROM EMP
     WHERE EMP_NO = 291;
    方法2:(次低效)
    DECLARE
      CURSOR C1 (E_NO NUMBER) IS
        SELECT EMP_NAME,SALARY,GRADE
          FROM EMP
         WHERE EMP_NO = E_NO;
    BEGIN
      OPEN C1(342);
      FETCH C1 INTO ,..,.. ;
        ..
      CLOSE C1;
      OPEN C1(291);
      FETCH C1 INTO ,..,.. ;
        ..
      CLOSE C1;
    END;
    方法3:(高效)
    SELECT A.EMP_NAME,A.SALARY,A.GRADE,
           B.EMP_NAME,B.SALARY,B.GRADE
      FROM EMP A,EMP B
     WHERE A.EMP_NO = 342
       AND B.EMP_NO = 291;

    注意:在SQL*Plus,SQL*Forms和Pro*C中重新设置ARRAYSIZE参数,可以增加每次数据库访问
的检索数据量,建议值为200

8. 使用DECODE函数来减少处理时间
    使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.
    例如:SELECT COUNT(*),SUM(SAL) FROM EMP WHERE DEPT_NO = 0020 AND ENAME LIKE 'SMITH%';
         SELECT COUNT(*),SUM(SAL) FROM EMP WHERE DEPT_NO = 0030 AND ENAME LIKE 'SMITH%';
    你可以用DECODE函数高效地得到相同结果
    SELECT COUNT(DECODE(DEPT_NO,0020,'X',NULL)) D0020_COUNT,
           COUNT(DECODE(DEPT_NO,0030,'X',NULL)) D0030_COUNT,
           SUM(DECODE(DEPT_NO,0020,SAL,NULL)) D0020_SAL,
           SUM(DECODE(DEPT_NO,0030,SAL,NULL)) D0030_SAL
      FROM EMP
     WHERE ENAME LIKE 'SMITH%';

    类似的,DECODE函数也可以运用于GROUP BY 和ORDER BY子句中.

9. 整合简单,无关联的数据库访问
    如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系)
    例如:SELECT NAME FROM EMP WHERE EMP_NO = 1234;
         SELECT NAME FROM DPT WHERE DPT_NO = 10;
         SELECT NAME FROM CAT WHERE CAT_TYPE = 'RD';
    上面的3个查询可以被合并成一个:
    SELECT E.NAME,D.NAME,C.NAME
      FROM CAT C,DPT D,EMP E,DUAL X
     WHERE NVL('X',X.DUMMY) = NVL('X',E.ROWID(+))
       AND NVL('X',X.DUMMY) = NVL('X',D.ROWID(+))
       AND NVL('X',X.DUMMY) = NVL('X',C.ROWID(+))
       AND E.EMP_NO(+) = 1234
       AND D.DEPT_NO(+) = 10
       AND C.CAT_TYPE(+) = 'RD';
    (译者按:虽然采取这种方法,效率得到提高,但是程序的可读性大大降低,所以读者还是要权衡之间的利弊)

10. 删除重复记录
    最高效的删除重复记录方法(因为使用了ROWID)
    DELETE FROM EMP E
     WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO);

11. 用TRUNCATE替代DELETE
    当删除表中的记录时,在通常情况下,回滚段(rollback segments)用来存放可以被恢复的信息.
如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令
之前的状况)
    而当运用TRUNCATE时,回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因
此很少的资源被调用,执行时间也会很短.
    (译者按:TRUNCATE只在删除全表适用,TRUNCATE是DDL不是DML)

12. 尽量多使用COMMIT
    只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释
放的资源而减少,COMMIT所释放的资源:
    a.回滚段上用于恢复数据的信息.
    b.被程序语句获得的锁
    c.redo log buffer中的空间
    d.ORACLE为管理上述3种资源中的内部花费
    (译者按:在使用COMMIT时必须要注意到事务的完整性,现实中效率和事务完整性往往是鱼和熊掌不可得兼)

13. 计算记录条数
    和一般的观点相反,count(*)比count(1)稍快,当然如果可以通过索引检索,对索引列的计数仍旧是最快的.
    例如:COUNT(EMPNO)
    (译者按: 在CSDN论坛中,曾经对此有过相当热烈的讨论,作者的观点并不十分准确,通过实际的测试,上述
三种方法并没有显著的性能差别)

14. 用Where子句替换HAVING子句
    避免使用HAVING子句,HAVING只会在检索出所有记录之后才对结果集进行过滤.这个处理需要排序,总计等
操作.如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销.
    例如:
    低效:SELECT REGION,AVG(LOG_SIZE)
           FROM LOCATION
          GROUP BY REGION
         HAVING REGION!=‘SYDNEY’
            AND REGION!=‘PERTH’
    高效:SELECT REGION,AVG(LOG_SIZE)
           FROM LOCATION
          WHERE REGION!=‘SYDNEY’
            AND REGION!=‘PERTH’
          GROUP BY REGION
    (译者按:HAVING中的条件一般用于对一些集合函数的比较,如COUNT()等等.除此而外,一般的条件应该写
在WHERE子句中)

15. 减少对表的查询
    在含有子查询的SQL语句中,要特别注意减少对表的查询.
    例如:
    低效:SELECT TAB_NAME
           FROM TABLES
          WHERE TAB_NAME=(SELECT TAB_NAME
                            FROM TAB_COLUMNS
                           WHERE VERSION=604)
            AND DB_VER=(SELECT DB_VER
                          FROM TAB_COLUMNS
                         WHERE VERSION=604)
    高效:SELECT TAB_NAME
           FROM TABLES
          WHERE (TAB_NAME,DB_VER)=(SELECT TAB_NAME,DB_VER
                                     FROM TAB_COLUMNS
                                    WHERE VERSION=604)

 

    Update 多个Column 例子:
    低效:UPDATE EMP
            SET EMP_CAT=(SELECT MAX(CATEGORY) FROM EMP_CATEGORIES),
                SAL_RANGE=(SELECT MAX(SAL_RANGE) FROM EMP_CATEGORIES)
          WHERE EMP_DEPT=0020;
    高效:UPDATE EMP
            SET (EMP_CAT,SAL_RANGE)=(SELECT MAX(CATEGORY),MAX(SAL_RANGE)
                                       FROM EMP_CATEGORIES)
          WHERE EMP_DEPT=0020;

 

 

16. 通过内部函数提高SQL效率.

 

SELECT H.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC,COUNT(*)

FROM HISTORY_TYPE T,EMP E,EMP_HISTORY H

WHERE H.EMPNO = E.EMPNO

AND H.HIST_TYPE = T.HIST_TYPE

GROUP BY H.EMPNO,E.ENAME,H.HIST_TYPE,T.TYPE_DESC;

 

通过调用下面的函数可以提高效率.

FUNCTION LOOKUP_HIST_TYPE(TYP IN NUMBER) RETURN VARCHAR2

AS

TDESC VARCHAR2(30);

CURSOR C1 IS

SELECT TYPE_DESC

FROM HISTORY_TYPE

WHERE HIST_TYPE = TYP;

BEGIN

OPEN C1;

FETCH C1 INTO TDESC;

CLOSE C1;

RETURN (NVL(TDESC,’?’));

END;

 

FUNCTION LOOKUP_EMP(EMP IN NUMBER) RETURN VARCHAR2

AS

ENAME VARCHAR2(30);

CURSOR C1 IS

SELECT ENAME

FROM EMP

WHERE EMPNO=EMP;

BEGIN

OPEN C1;

FETCH C1 INTO ENAME;

CLOSE C1;

RETURN (NVL(ENAME,’?’));

END;

 

SELECT H.EMPNO,LOOKUP_EMP(H.EMPNO),

H.HIST_TYPE,LOOKUP_HIST_TYPE(H.HIST_TYPE),COUNT(*)

FROM EMP_HISTORY H

GROUP BY H.EMPNO , H.HIST_TYPE;

 

(译者按: 经常在论坛中看到如 ’能不能用一个SQL写出….’ 的贴子, 殊不知复杂的SQL往往牺牲了执行效率.
            能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的)

 


17. 使用表的别名(Alias)

当在SQL语句中连接多个表时,
            请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误.

 

(译者注:
            Column歧义指的是由于SQL中不同的表具有相同的Column名,当SQL语句中出现这个Column时,SQL解析器无法判断这个Column的归属)

 

18. 用EXISTS替代IN

在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, 使用EXISTS(或NOT
            EXISTS)通常将提高查询的效率.

 

低效:

SELECT *

FROM EMP (基础表)

WHERE EMPNO > 0

AND DEPTNO IN (SELECT DEPTNO

FROM DEPT

WHERE LOC = ‘MELB’)

 

高效:

SELECT *

FROM EMP (基础表)

WHERE EMPNO > 0

AND EXISTS (SELECT ‘X’

FROM DEPT

WHERE DEPT.DEPTNO = EMP.DEPTNO

AND LOC = ‘MELB’)

 

 

(译者按: 相对来说,用NOT EXISTS替换NOT IN 将更显著地提高效率,下一节中将指出)

 

 

19. 用NOT EXISTS替代NOT IN

在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是最低效的
            (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT
            EXISTS.

 

例如:

SELECT …

FROM EMP

WHERE DEPT_NO NOT IN (SELECT DEPT_NO

FROM DEPT

WHERE DEPT_CAT=’A’);

 

为了提高效率.改写为:

 

(方法一: 高效)

SELECT ….

FROM EMP A,DEPT B

WHERE A.DEPT_NO = B.DEPT(+)

AND B.DEPT_NO IS NULL

AND B.DEPT_CAT(+) = ‘A’

 

 

(方法二: 最高效)

SELECT ….

FROM EMP E

WHERE NOT EXISTS (SELECT ‘X’

FROM DEPT D

WHERE D.DEPT_NO = E.DEPT_NO

AND DEPT_CAT = ‘A’);

 

20. 用表连接替换EXISTS

 

通常来说 , 采用表连接的方式比EXISTS更有效率

SELECT ENAME

FROM EMP E

WHERE EXISTS (SELECT ‘X’

FROM DEPT

WHERE DEPT_NO = E.DEPT_NO

AND DEPT_CAT = ‘A’);

 

(更高效)

SELECT ENAME

FROM DEPT D,EMP E

WHERE E.DEPT_NO = D.DEPT_NO

AND DEPT_CAT = ‘A’ ;

 

(译者按: 在RBO的情况下,前者的执行路径包括FILTER,后者使用NESTED LOOP)

 

21. 用EXISTS替换DISTINCT

当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT.
            一般可以考虑用EXIST替换

 

例如:

低效:

SELECT DISTINCT DEPT_NO,DEPT_NAME

FROM DEPT D,EMP E

WHERE D.DEPT_NO = E.DEPT_NO

高效:

SELECT DEPT_NO,DEPT_NAME

FROM DEPT D

WHERE EXISTS ( SELECT ‘X’

FROM EMP E

WHERE E.DEPT_NO = D.DEPT_NO);

 

EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果.

 

22. 识别’低效执行’的SQL语句

 

用下列SQL工具找出低效SQL:

 

SELECT EXECUTIONS , DISK_READS, BUFFER_GETS,

ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2) Hit_radio,

ROUND(DISK_READS/EXECUTIONS,2) Reads_per_run,

SQL_TEXT

FROM V$SQLAREA

WHERE EXECUTIONS>0

AND BUFFER_GETS > 0

AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8

ORDER BY 4 DESC;

 

(译者按: 虽然目前各种关于SQL优化的图形化工具层出不穷,但是写出自己的SQL工具来解决问题始终是一个最好的方法)

 

23. 使用TKPROF 工具来查询SQL性能状态

 

SQL trace 工具收集正在执行的SQL的性能状态数据并记录到一个跟踪文件中.
            这个跟踪文件提供了许多有用的信息,例如解析次数.执行次数,CPU使用时间等.这些数据将可以用来优化你的系统.

 

设置SQL TRACE在会话级别: 有效

 

ALTER SESSION SET SQL_TRACE TRUE

 

设置SQL TRACE 在整个数据库有效仿, 你必须将SQL_TRACE参数在init.ora中设为TRUE,
            USER_DUMP_DEST参数说明了生成跟踪文件的目录

 

(译者按: 这一节中,作者并没有提到TKPROF的用法, 对SQL TRACE的用法也不够准确, 设置SQL
            TRACE首先要在init.ora中设定TIMED_STATISTICS, 这样才能得到那些重要的时间状态.
            生成的trace文件是不可读的,所以要用TKPROF工具对其进行转换,TKPROF有许多执行参数.
            大家可以参考ORACLE手册来了解具体的配置. )

 

24. 用EXPLAIN PLAN 分析SQL语句

 

EXPLAIN PLAN 是一个很好的分析SQL语句的工具,它甚至可以在不执行SQL的情况下分析语句.
            通过分析,我们就可以知道ORACLE是怎么样连接表,使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称.

你需要按照从里到外,从上到下的次序解读分析的结果. EXPLAIN PLAN分析的结果是用缩进的格式排列的,
            最内部的操作将被最先解读, 如果两个操作处于同一层中,带有最小操作号的将被首先执行.

NESTED LOOP是少数不按照上述规则处理的操作, 正确的执行路径是检查对NESTED
            LOOP提供数据的操作,其中操作号最小的将被最先处理.

 

译者按:

 

通过实践, 感到还是用SQLPLUS中的SET TRACE 功能比较方便.

举例:

 

SQL> list

1 SELECT *

2 FROM dept, emp

3* WHERE emp.deptno = dept.deptno

SQL> set autotrace traceonly /*traceonly 可以不显示执行结果*/

SQL> /

14 rows selected.

Execution Plan

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

0 SELECT STATEMENT ptimizer=CHOOSE

1 0 NESTED LOOPS

2 1 TABLE ACCESS (FULL) OF 'EMP'

3 1 TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'

4 3 INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)

 

Statistics

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

0 recursive calls

2 db block gets

30 consistent gets

0 physical reads

0 redo size

2598 bytes sent via SQL*Net to client

503 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

14 rows processed

 

通过以上分析,可以得出实际的执行步骤是:

1. TABLE ACCESS (FULL) OF 'EMP'

2. INDEX (UNIQUE SCAN) OF 'PK_DEPT' (UNIQUE)

3. TABLE ACCESS (BY INDEX ROWID) OF 'DEPT'

4. NESTED LOOPS (JOINING 1 AND 3)

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

转载于:http://blog.itpub.net/21229923/viewspace-620349/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值