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')