ORACEL 优化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(*0 from work_city where sdesc like 'NEW%'
两个用户访问相同的对象 public synonym - work_city
select a.sdesc,b.location from work_city a , plant_detail b where a.city_id = b.city_id
不能
用户 jack 通过 private synonym 访问 plant_detail 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
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 子句中 .
如果 DECODE 取值为 NULL SUM NULL )的值是 NULL ,不会正常求和的。可以改成如下所示就好了:
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,0)) D0020_SAL, SUM(DECODE(DEPT_NO,0030,SAL,0)) D0030_SAL FROM EMP WHERE ENAME LIKE ‘SMITH%';
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)
( 译者按 : 优快云 论坛中 , 曾经对此有过相当热烈的讨论 , 作者的观点并不十分准确 , 通过实际的测试 , 上述三种方法并没有显著的性能差别 )
14. Where 子句替换 HAVING 子句
避免使用 HAVING 子句 , HAVING 只会在检索出所有记录之后才对结果集进行过滤 . 这个处理需要排序 , 总计等操作 . 如果能通过 WHERE 子句限制记录的数目 , 那就能减少这方面的开销 .
例如 :
低效 :
SELECT REGION AVG(LOG_SIZE)
FROM LOCATION
GROUP BY REGION
HAVING REGION REGION != ‘SYDNEY'
AND REGION != ‘PERTH'
高效
SELECT REGION AVG(LOG_SIZE)
FROM LOCATION
WHERE REGION 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;
通过调用下面的函数可以提高效率 .
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;
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;
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')
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值