Oracle 性能优化40条军规之我所遇见

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

2.Select子句中避免使用“*”
(1)Oracle在解析SQL语句的时候,对于“*”将通过查询数据库字典来将其转换成对应的列名。
(2)如果在Select子句中需要列出所有的Column时,建议列出所有的Column名称,而不是简单的用“*”来替代,这样可以减少多于的数据库查询开销。

select子句总结:列出列名

3.From子句选择高效率的表名顺序
         要想得到高效的表名顺序,则首先需要了解Oracle中SQL语句的执行顺序。
(1)from子句执行顺序
         ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表 driving table)将被最先处理。
(2)多表执行顺序
         当ORACLE处理多个表时,会运用排序及合并的方式连接它们。首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行派序,然后扫描第二个表(FROM子句中最后第二个表),最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。
例一:

  /*表 TAB1 16,384 条记录*/
  /*表 TAB2 1 条记录 */
 /*选择TAB2作为基础表 (最好的方法)*/
  select count(*) from tab1,tab2   执行时间0.96秒
  
   /*选择TAB2作为基础表 (不佳的方法)*/
  select count(*) from tab2,tab1   执行时间26.09秒

如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表。
例二:

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

(上面代码中,EMP表则为交叉表,同时被LOCATION表和CATEGORY表引用)

上面代码效率会比以下的代码高:

ELECT * 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

高效率的表名顺序总结为一句:数据量小的表,交叉表(被其他表引用的表)放from子句中的最后

4.Where子句中的连接顺序
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’

where子句的连接顺序总结为:select 子句写最前,条件越苛刻的写最后(即可以过滤掉最大数量记录的条件)

5.Where子句替换Having子句
         避免使用HAVING子句,HAVING 只会在检索出所有记录之后才对结果集进行过滤。这个处理需要排序、总计等操作。 如果能通过WHERE子句限制记录的数目,就能减少这方面的开销。

6.全表记录删除用Truncate
(1)Truncate和delete的区别
         Truncate删除表中记录的时候不保存删除信息,不能恢复。因此Truncate删除记录比Delete快,而且占用资源少。
         Delete表中记录的时候,Oracle会在Rollback段中保存删除信息以备恢复。
(2)删除建议
         删除表中记录的时候,如果不需要恢复的情况之下应该尽量使用Truncate而不是Delete。
Truncate仅适用于删除全表的记录。

7.计算数据

Select count(*) from tablename; 
Select count(1) from tablename; 
Select max(rownum) from tablename;

一般认为,在没有索引的情况之下,第一种方式最快。 如果有索引列,使用索引列最快。

8.减少对表的查询操作
         含有子查询的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

总结:在子查询中尽量减少使用select子句的频率

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

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

9.关于EXISTS和NOT EXISTS
(1)用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’)

(2)用NOT EXISTS替代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 E
WHERE NOT EXISTS (SELECT ‘X’ 
                       FROM DEPT D
                    WHERE D.DEPT_NO = E.DEPT_NO
                                  AND DEPT_CAT = ‘A’) 

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

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

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

(4)用表连接替换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’ 

从效率方面上总结:
         表连接方式>EXISTS、NOT EXISTS>IN、NOT IN、DISTINCT


10.用索引提高效率
(1)Oracle对索引有两种访问模式
         ①索引唯一扫描 (Index Unique Scan)
         ②索引范围扫描 (Index Range Scan)

(2)多个平等的索引
         ①当SQL语句的执行路径可以使用分布在多个表上的多个索引时,ORACLE会同时使用多个索引并在运行时对它们的记录进行合并,检索出仅对全部索引有效的记录。

         ②在ORACLE选择执行路径时,唯一性索引的等级高于非唯一性索引。然而这个规则只有当WHERE子句中索引列和常量比较才有效。如果索引列和其他表的索引类相比较。这种子句在优化器中的等级是非常低的。

         ③如果不同表中两个相同等级的索引将被引用,FROM子句中表的顺序将决定哪个会被率先使用。 FROM子句中最后的表的索引将有最高的优先级。

         ④如果相同表中两个相同等级的索引将被引用,WHERE子句中最先被引用的索引将有最高的优先级。

(3)等式比较优先于范围比较
         DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引。

SELECT ENAME
     FROM EMP
     WHERE DEPTNO > 20
     AND EMP_CAT = ‘A’;

这里只有EMP_CAT索引被用到,然后所有的记录将逐条与DEPTNO条件进行比较。
执行路径如下:

TABLE ACCESS BY ROWID ON EMP

INDEX RANGE SCAN ON CAT_IDX

         即使是唯一性索引,如果做范围比较,其优先级也低于非唯一性索引的等式比较。

(4)不明确的索引等级
         当ORACLE无法判断索引的等级高低差别,优化器将只使用一个索引,它就是在WHERE子句中被列在最前面的。

DEPTNO上有一个非唯一性索引,EMP_CAT也有一个非唯一性索引。

SELECT ENAME
     FROM EMP
     WHERE DEPTNO > 20
     AND EMP_CAT > ‘A’;

这里, ORACLE只用到了DEPT_NO索引. 执行路径如下:

TABLE ACCESS BY ROWID ON EMP

INDEX RANGE SCAN ON DEPT_IDX

(5)避免在索引列上使用计算
         WHERE子句中,如果索引列是函数的一部分。优化器将不使用索引而使用全表扫描
低效:(不使用索引)

SELECT …
  FROM DEPT
WHERE SAL * 12 > 25000;

高效:(使用索引)

SELECT …
  FROM DEPT
WHERE SAL  > 25000/12;

(6)强制索引失效
         如果两个或以上索引具有相同的等级,你可以强制命令ORACLE优化器使用其中的一个(通过它,检索出的记录数量少) 。

SELECT ENAME
FROM EMP
WHERE EMPNO = 7935  
AND DEPTNO + 0 = 10    /*DEPTNO上的索引将失效*/
AND EMP_TYPE || ‘’ = ‘A’  /*EMP_TYPE上的索引将失效*/

(7)自动选择索引
         如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性索引。在这种情况下,ORACLE将使用唯一性索引而完全忽略非唯一性索引。

SELECT ENAME
  FROM EMP
WHERE EMPNO = 2326  
     AND DEPTNO  = 20 ;

这里,只有EMPNO上的索引是唯一性的,所以EMPNO索引将用来检索记录。

TABLE ACCESS BY ROWID ON EMP

INDEX UNIQUE SCAN ON EMP_NO_IDX
(8)避免在索引列上使用NOT
         我们要避免在索引列上使用NOT,NOT会产生在和在索引列上使用函数相同的影响。当ORACLE遇到NOT,它就会停止使用索引转而执行全表扫描
低效:(不使用索引)

   SELECT …
     FROM DEPT
   WHERE NOT DEPT_CODE = 0

高效:(使用索引)

 SELECT …
   FROM DEPT
 WHERE DEPT_CODE > 0
 

11.用 >= 替代 >
如果DEPTNO上有一个索引
高效:

SELECT *
     FROM EMP
   WHERE DEPTNO >=4

低效:

SELECT *
     FROM EMP
   WHERE DEPTNO >3

         两者的区别在于,前者DBMS将直接跳到第一个DEPT等于4的记录,而后者将首先定位到DEPTNO等于3的记录并且向前扫描到第一个DEPT大于3的记录.

这一点,与前面10.(3)等式比较优先于范围比较相同的意思,即有确定的取值(=)的效率总比没有确定值的效率高(>),虽然它们的工作原理不同

12.用Union替换OR(适用于索引列)
         通常情况下,用UNION替换WHERE子句中的OR将会起到较好的效果。对索引列使用OR将造成全表扫描。 注意,以上规则只针对多个索引列有效。
高效:

SELECT LOC_ID , LOC_DESC , REGION
     FROM LOCATION
   WHERE LOC_ID = 10
   UNION
   SELECT LOC_ID , LOC_DESC , REGION
     FROM LOCATION
   WHERE REGION = “MELBOURNE”

低效:

SELECT LOC_ID , LOC_DESC , REGION
     FROM LOCATION
   WHERE LOC_ID = 10 OR REGION = “MELBOURNE”

13.用IN替换OR
低效:

SELECT….
  FROM LOCATION
WHERE LOC_ID = 10
       OR  LOC_ID = 20
       OR  LOC_ID = 30

高效:

SELECT…
  FROM LOCATION
WHERE LOC_IN IN (102030

接着第九点继续总结:
在效率方面:
         表连接方式>EXISTS、NOT EXISTS>IN、NOT IN、DISTINCT>OR

14.避免在索引列上使用is null和is not null
         避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引
低效:(索引失效)

SELECT …
  FROM DEPARTMENT
WHERE DEPT_CODE IS NOT NULL;

高效:(索引有效)

SELECT …
  FROM DEPARTMENT
WHERE DEPT_CODE >=0;

15.总是使用索引的第一列
         如果索引是建立在多个列上, 只有在它的第一个列被where子句引用时, 优化器才会选择使用该索引

SQL> create index multindex on multiindexusage(inda,indb);
Index created.

SQL> select * from  multiindexusage where indb = 1;
Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   TABLE ACCESS (FULL) OF 'MULTIINDEXUSAGE‘

         很明显, 当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引。

16.使用UNION ALL替代UNION
         当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序。如果用UNION ALL替代UNION,这样排序就不是必要了,效率就会因此得到提高。

         由于UNION ALL的结果没有经过排序,而且不过滤重复的记录,因此是否进行替换需要根据业务需求而定。

17.避免改变索引列的类型
         当比较不同数据类型的数据时, ORACLE自动对列进行简单的类型转换

/*假设EMP_TYPE是一个字符类型的索引列.*/
SELECT …
  FROM EMP
 WHERE EMP_TYPE = 123

/*这个语句被ORACLE转换为:*/
SELECT …
  FROM EMP
 WHERE TO_NUMBER(EMP_TYPE)=123

         因为内部发生的类型转换,这个索引将不会被用到。

几点注意:
      ①当比较不同数据类型的数据时,ORACLE自动对列进行简单的类型转换。
      ②如果在索引列上面进行了隐式类型转换,在查询的时候将不会用到索引。
      ③注意当字符和数值比较时,ORACLE会优先转换数值类型到字符类型。
      ④为了避免ORACLE对SQL进行隐式的类型转换,最好把类型转换用显式表现出来。

18.Where子句不能使用索引的情况
(1)下面的例子中,‘!=’ 将不使用索引 ,索引只能告诉你什么存在于表中,而不能告诉你什么不存在于表中。

不使用索引:

 SELECT ACCOUNT_NAME
      FROM TRANSACTION
   WHERE AMOUNT !=0

使用索引:

SELECT ACCOUNT_NAME
      FROM TRANSACTION
    WHERE AMOUNT > 0

(2)下面的例子中,‘||’是字符连接函数。就象其他函数那样,停用了索引

不使用索引:

SELECT ACCOUNT_NAME,AMOUNT
  FROM TRANSACTION
WHERE ACCOUNT_NAME||ACCOUNT_TYPE=’AMEXA’

使用索引:

SELECT ACCOUNT_NAME,AMOUNT
  FROM TRANSACTION
WHERE ACCOUNT_NAME = ‘AMEX’
     AND ACCOUNT_TYPE=’ A’;

(3)下面的例子中,‘+’是数学函数。就象其他数学函数那样,停用了索引

不使用索引:

SELECT ACCOUNT_NAME,AMOUNT
  FROM TRANSACTION
WHERE AMOUNT + 3000 >5000

使用索引:

SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE AMOUNT > 2000

(4)下面的例子中,相同的索引列不能互相比较,这将会启用全表扫描。

不使用索引:

SELECT ACCOUNT_NAME, AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME = NVL(:ACC_NAME, ACCOUNT_NAME)

使用索引:

SELECT ACCOUNT_NAME,AMOUNT
FROM TRANSACTION
WHERE ACCOUNT_NAME LIKE NVL(:ACC_NAME,%)

19.连接多个扫描
         如果对一个列和一组有限的值进行比较,优化器可能执行多次扫描并对结果进行合并连接
例:

SELECT * 
      FROM LODGING
    WHERE MANAGER IN (‘BILL GATES’, ’KEN MULLER’)

优化器可能将它转换成以下形式:

 SELECT * 
      FROM LODGING
    WHERE MANAGER = ‘BILL GATES’
           OR MANAGER = ’KEN MULLER’

20.避免使用耗费资源的操作
(1)带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎执行耗费资源的排序(SORT)功能。DISTINCT需要一次排序操作,而其他的至少需要执行两次排序。
(2)通常,带有UNION,MINUS,INTERSECT的SQL语句都可以用其他方式重写

21.优化GROUP BY

         提高GROUP BY语句的效率,可以通过将不需要的记录在GROUP BY之前过滤掉。

低效:

 SELECT JOB ,AVG(SAL)
    FROM EMP
  GROUP BY JOB
HAVING JOB = ‘PRESIDENT’
         OR JOB = ‘MANAGER’

高效:

SELECT JOB,AVG(SAL)
   FROM EMP
WHERE JOB = ‘PRESIDENT’
        OR JOB = ‘MANAGER’
GROUP BY JOB

22.日期+小数(小数位数>5会进位)

         当使用日期时,需要注意如果有超过5位小数加到日期上,这个日期会进到下一天

小数位为5位:

SELECT TO_DATE(‘01-JAN-93+.99999)
  FROM DUAL
Returns:
’01-JAN-93 23:59:59

小数位为6位:

SELECT TO_DATE(‘01-JAN-93+.999999)
  FROM DUAL
Returns:
’02-JAN-93 00:00:00

以上所有内容都是参照以下链接中的内容进行编写,截取了目前学习到上遇到过的,再加上自己的理解总结了一些,如有不正确之处,恳请各位读者们纠正。

原文链接:https://www.cnblogs.com/zjfjava/p/7092503.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值