Oracle+SQL优化第二弹

SQL 语句性能优化(未完待续)


要使 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 buffer 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 sal_limit;  每个用户都有一个 private
 不能 synonym - sal_limit , 它们是不
  同的对象
  
select count(*) from work_city where sdesc 能 两个用户访问相同的对象
like 'NEW%';  public synonym - work_city
select a.sdesc,b.location from work_city a ,  用 户 jack  通 过 private
 不能 synonym 访问 plant_detail 而
plant_detail b where a.city_id = b.city_id  jill 是表的所有者,对象不同.
  
  
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 秒

选择 TAB2 作为基础表 (不佳的方法) 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 …,..,.. ; …..

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

OR  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);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值