1. Oracle sql及性能优化调整
1.1. 选用适合的ORACLE优化器
ORACLE的优化器共有3种:
a. RULE (基于规则) b. COST (基于成本) c. CHOOSE (选择性)
设置缺省的优化器,可以通过对init.ora文件中OPTIMIZER_MODE参数的各种声明。
1.2. 访问Table的方式
ORACLE 采用两种访问表中记录的方式:
a. 全表扫描:
全表扫描就是顺序地访问表中每条记录. ORACLE采用一次读入多个数据块
(databaseblock)的方式优化全表扫描. b. 通过ROWID访问表:
你可以采用基于ROWID的访问方式情况,提高访问表的效率, ROWID包含了表中记录的物理位置信息。ORACLE采用索引(INDEX)实现了数据和存放数据的物理位置(ROWID)之间的联系. 通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高.
1.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 Work_city
如何访问 private synonym public synonym
Plant_detail private synonym sal_limit Work_city
private synonym public synonym
Jill
Plant_detail table owner
考虑一下下列SQL语句能否在这两个用户之间共享. SQL 能否共享 原因 每个用户都有一个private select max(sal_cap) from sal_limit; 不能 synonym - sal_limit , 它们是不同的对象 select count(*) from work_city where sdesc like 能 两个用户访问相同的对象public 'NEW%'; synonym - work_city 用户jack 通过private synonym访select a.sdesc,b.location from work_city a , plant_detail b where a.city_id = b.city_id 不能 问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;
1.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
1.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?;
1.6. SELECT子句中避免使用‘*’
当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用 ?*? 是一个方便的方法.不幸的是,这是一个非常低效的方法. 实际上,ORACLE在解析的过程中, 会将?*? 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间。
1.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.
1.8. 使用DECODE函数来减少处理时间
语法:DECODE(value, if1, then1, if2,then2, if3,then3, . . . else ) 使用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,?*?,NULL)) D0020_COUNT, COUNT(DECODE(DEPT_NO,0030,?*?,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子句中.
1.9. 整合简单无关联的数据库访问
如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系),结果集为多个相关表的记录的笛卡儿集。 例如1:
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?; 例如2:
select m.knowledgemodule_id, m.knowledgemodule_name
from tb_dic_knowledge_module m where m.knowledgemodule_id < 300 select cp.checkpoint_id, cp.checkpoint_name from tb_dic_quiz_check_point cp where cp.checkpoint_id < 2010 and cp.checkpoint_studyid = 6 上面2个查询合并为一个
select m.knowledgemodule_id, m.knowledgemodule_name,cp.checkpoint_id, cp.checkpoint_name
from tb_dic_knowledge_module m, tb_dic_quiz_check_point cp, dual x where NVL('X',X.DUMMY)=NVL('X', m.ROWID(+)) and NVL('X',X.DUMMY)=NVL('X', cp.ROWID(+))
and m.knowledgemodule_id < 300 and cp.checkpoint_id < 2010 and cp.checkpoint_studyid = 6
(译者按: 虽然采取这种方法,效率得到提高,但是程序的可读性大大降低,所以读者 还是要权衡之间的利弊)
1.10. 删除重复记录
最高效的删除重复记录方法 ( 因为使用了ROWID) DELETE FROM EMP E
WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X
WHERE X.EMP_NO = E.EMP_NO);
1.11. Top-N查询语句
例如:查询工资最高的三个员工:
SELECT ROWNUM as RANK, last_name, salary
FROM (SELECT last_name,salary FROM employees ORDER BY salary DESC) WHERE ROWNUM <= 3;
1.12. 用TRUNCATE替代DELETE
当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息。如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况)。
而当运用TRUNCATE时,回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因此很少的资源被调用,执行时间也会很短。
(译者按: TRUNCATE只在删除全表适用,TRUNCATE是DDL不是DML)
1.13. 尽量多使用COMMIT
只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少: COMMIT所释放的资源: a. 回滚段上用于恢复数据的信息. b. 被程序语句获得的锁 c. redo log buffer 中的空间
d. ORACLE为管理上述3种资源中的内部花费
(译者按: 在使用COMMIT时必须要注意到事务的完整性,现实中效率和事务完整性往往是鱼和熊掌不可得兼)
1.14. 计算记录条数
和一般的观点相反, count(*) 比count(1)稍快 , 当然如果可以通过索引检索,对索引列的计数仍旧是最快的. 例如 COUNT(EMPNO)
1.15. 用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子句中)
1.16. 减少对表的查询
在含有子查询的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;
1.17. 通过内部函数提高SQL效率
复杂的SQL往往牺牲了执行效率. 能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的。
1.18. 使用表的别名(Alias)
当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误.
(译者注: Column歧义指的是由于SQL中不同的表具有相同的Column名,当SQL语句中出现这个Column时,SQL解析器无法判断这个Column的归属)
1.19. 用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 将更显著地提高效率,下一节中将指出)
1.20. 用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_NO (+) 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?);
1.21. 用表连接替换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)
1.22. 用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核心模块将在子查询的条件一旦满足后,立刻返回结果.
1.23. 用索引提高效率
索引是表的一个概念部分,用来提高检索数据的效率,实际上ORACLE使用了一个复杂的自平衡B-tree结构。通常通过索引查询数据比全表扫描要快。当ORACLE找出执行查询和Update语句的最佳路径时, ORACLE优化器将使用索引。同样在联结多个表时使用索引也可以提高效率。另一个使用索引的好处是它提供了主键(primary key)的唯一性验证。
除了那些LONG或LONG RAW数据类型你可以索引几乎所有的列。通常在大型表中使用索引特别有效. 当然你也会发现在扫描小表时使用索引同样能提高效率。
虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价. 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改. 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O . 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢. 译者按:
定期的重构索引是有必要的.
ALTER INDEX
1.24. 索引的操作
ORACLE对索引有两种访问模式。
索引唯一扫描(INDEX UNIQUE SCAN),大多数情况下, 优化器通过WHERE子句访问INDEX。 例如:
表LODGING有两个索引:建立在LODGING列上的唯一性索引LODGING_PK和建立在MANAGER列上的非唯一性索引LODGING$MANAGER.
SELECT * FROM LODGING WHERE LODGING = ‘ROSE HILL’;
在内部上述SQL将被分成两步执行, 首先 , LODGING_PK 索引将通过索引唯一扫描的方式被访问 , 获得相对应的ROWID, 通过ROWID访问表的方式执行下一步检索。
如果被检索返回的列包括在INDEX列中,ORACLE将不执行第二步的处理(通过ROWID访问表),因为检索数据保存在索引中, 单单访问索引就可以完全满足查询结果。
下面SQL只需要INDEX UNIQUE SCAN 操作.
SELECT LODGING FROM LODGING WHERE LODGING = ‘ROSE HILL’; 索引范围查询(INDEX RANGE SCAN) 适用于两种情况:
1. 基于一个范围的检索 2. 基于非唯一性索引的检索 例1:
SELECT LODGING FROM LODGING WHERE LODGING LIKE ‘M%’; WHERE子句条件包括一系列值, ORACLE将通过索引范围查询的方式查询LODGING_PK,由于索引范围查询将返回一组值, 它的效率就要比索引唯一扫描 低一些。 例2:
SELECT LODGING FROM LODGING WHERE MANAGER = ‘BILL GATES’; 这个SQL的执行分两步, LODGING$MANAGER的索引范围查询(得到所有符合条件记录的ROWID) 和下一步通过ROWID访问表得到LODGING列的值. 由于LODGING$MANAGER是一个非唯一性的索引,数据库不能对它执行索引唯一扫描。
由于SQL返回LODGING列,而它并不存在于LODGING$MANAGER索引中, 所以在索引范围查询后会执行一个通过ROWID访问表的操作.
WHERE子句中, 如果索引列所对应的值的第一个字符由通配符(WILDCARD)开始,索引将不被采用。
SELECT LODGING FROM LODGING WHERE MANAGER LIKE ‘%HANMAN’; 在这种情况下,ORACLE将使用全表扫描。
1.25. 基础表的选择
基础表(Driving Table)是指被最先访问的表(通常以全表扫描的方式被访问)。根据优化器的不同SQL语句中基础表的选择是不一样的。
如果你使用的是CBO (COST BASED OPTIMIZER),优化器会检查SQL语句中的每个表的物理大小、索引的状态、然后选用花费最低的执行路径。
如果你用RBO (RULE BASED OPTIMIZER) , 并且所有的连接条件都有索引对应, 在这种情况下, 基础表就是FROM子句中列在最后的那个表.
举例:
SELECT A.NAME,B.MANAGER FROM WORKER A, LODGING B WHERE A.LODGING = B.LODING;
由于LODGING表的LODING列上有一个索引, 而且WORKER表中没有相比较的索引, WORKER表将被作为查询中的基础表.
1.26. 不明确的索引等级
当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
1.27. 避免在索引列上使用计算.
WHERE子句中如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描。
低效:
SELECT ? FROM DEPT WHERE SAL * 12 > 25000; 高效:
SELECT ? FROM DEPT WHERE SAL > 25000/12; 译者按:
这是一个非常实用的规则,请务必牢记。
1.28. 自动选择索引
如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性,在这种情况下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
1.29. 避免在索引列上使用NOT
通常,我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数相同的影响,当ORACLE”遇到”NOT,他就会停止使用索引转而执行全表扫描。 低效: (这里,不使用索引)
SELECT ? FROM DEPT WHERE NOT DEPT_CODE = 0;
高效: (这里,使用了索引)
SELECT ? FROM DEPT WHERE DEPT_CODE > 0;
需要注意的是,在某些时候, ORACLE优化器会自动将NOT转化成相对应的关系操作符: NOT > to <= NOT >= to < NOT < to >= NOT <= to >
1.30. 用>=替代>
如果DEPTNO上有一个索引: 高效:
SELECT * FROM EMP WHERE DEPTNO >=4 低效:
SELECT * FROM EMP WHERE DEPTNO >3
两者的区别在于,前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。
1.31. 用UNION替换OR (适用于索引列)
通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果。对索引列使用OR将造成全表扫描。注意,以上规则只针对多个索引列有效。如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低. 在下面的例子中, LOC_ID 和REGION上都建有索引: 高效:
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”
如果你坚持要用OR, 那就需要返回记录最少的索引列写在最前面. 注意:
WHERE KEY1 = 10 (返回最少记录) OR KEY2 = 20 (返回最多记录) ORACLE 内部将以上转换为
WHERE KEY1 = 10 AND ((NOT KEY1 = 10) AND KEY2 = 20)
1.32. 用IN来替换OR
下面的查询可以被更有效率的语句替换: 低效:
SELECT ? FROM LOCATION
WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30 高效
SELECT ? FROM LOCATION WHERE LOC_IN IN (10,20,30); 译者按:
这是一条简单易记的规则,但是实际的执行效果还须检验,在ORACLE8i下,两者的执行路径似乎是相同的.
1.33. 避免在索引列上使用IS NULL和IS NOT NULL
避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引,对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录,如果至少有一个列不为空,则记录存在于索引中。
如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null) , ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入)。然而如果所有的索引列都为空,ORACLE将认为整个键值为空,而空不等于
空,因此你可以插入1000条具有相同键值的记录,当然它们都是空。因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引。
低效: (索引失效)
SELECT ? FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL; 高效: (索引有效)
SELECT ? FROM DEPARTMENT WHERE DEPT_CODE >=0;
1.34. 总是使用索引的第一个列
如果索引是建立在多个列上, 只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引。
1.35. ORACLE内部操作
当执行查询时,ORACLE采用了内部的操作。下表显示了几种重要的内部操作: ORACLE Clause ORDER BY UNION MINUS INTERSECT DISTINCT,MINUS,INTERSECT,UNION MIN,MAX,COUNT GROUP BY ROWNUM Queries involving Joins CONNECT BY 内部操作 SORT ORDER BY UNION-ALL MINUS INTERSECT SORT UNIQUE SORT AGGREGATE SORT GROUP BY COUNT or COUNT STOPKEY SORT JOIN,MERGE JOIN,NESTED LOOPS CONNECT BY
3.6. Session、Connection、process的概念及关系:
1、在Oracle中session和process的区别是什么?
一个process可以有0个、1个或者多个session,一个session也可以存在若干个process中,并行同样是一个session对应一个process,主session是coordinator session,每个parallel process同样会对应数据库里一个单独的session。可以从v$px_session和v$session中验证这点。;
连接connects,会话sessions和进程pocesses的关系: 每个sql login称为一个连接(connection),而每个连接,可以产生一个或多个会话,如果数据库运行在专用服务器方式,一个会话对应一个服务器进程(process),如果数据库运行在共享服务器方式,一个服务器进程可以为多个会话服务。
3.7. 游标
游标的概念:
游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处理结果显示出来或最终写回数据库。这样数据处理的速度才会提高,否则频繁的磁盘数据交换会降低效率。 游标有两种类型:显式游标和隐式游标。在前述程序中用到的SELECT...INTO...查询语句,一次只能从数据库中提取一行数据,对于这种形式的查询和DML操作,系统都会使用一个隐式游标。但是如果要提取多行数据,就要由程序员定义一个显式游标,并通过与游标有关的语句进行处理。显式游标对应一个返回结果为多行多列的SELECT语句。
游标一旦打开,数据就从数据库中传送到游标变量中,然后应用程序再从游标变量中分解出需要的数据,并进行处理。 隐式游标
如前所述,DML操作和单行SELECT语句会使用隐式游标,它们是: * 插入操作:INSERT。 * 更新操作:UPDATE。 * 删除操作:DELETE。
* 单行查询操作:SELECT ... INTO ...。
当系统使用一个隐式游标时,可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。隐式游标可以使用名字SQL来访问,但要注意,通过SQL游标名总是只能访问前一个DML操作或单行SELECT操作的游标属性。所以通常在刚刚执行完操作之后,立即使用SQL游标名来访问属性。
oracle在open时将数据读到缓存中,以后fetch时就从缓存中读取数据,但如果数据量大的时候,不可能把所有数据都取到。
游标的使用分成以下4个步骤: 1、声明游标
在DECLEAR部分按以下格式声明游标:
CURSOR 游标名[(参数1 数据类型[,参数2 数据类型...])] IS SELECT语句; 参数是可选部分,所定义的参数可以出现在SELECT语句的WHERE子句中。如果定义了参数,则必须在打开游标时传递相应的实际参数。
SELECT语句是对表或视图的查询语句,甚至也可以是联合查询。可以带WHERE条件、ORDER BY或GROUP BY等子句,但不能使用INTO子句。在SELECT语句中可以使用在定义游标之前定义的变量。 2.打开游标
在可执行部分,按以下格式打开游标:
OPEN 游标名[(实际参数1[,实际参数2...])];
打开游标时,SELECT语句的查询结果就被传送到了游标工作区。(将数据读到服务器缓存中) 3.提取数据
在可执行部分,按以下格式将游标工作区中的数据取到变量中(从缓存中读取数据,但如果数据量大的时候,不可能把所有数据都取到)。提取操作必须在打开游标之后进行。
FETCH 游标名 INTO 变量名1[,变量名2...]; 或
FETCH 游标名 INTO 记录变量;
游标打开后有一个指针指向数据区,FETCH语句一次返回指针所指的一行数据,要返回多行需重复执行,可以使用循环语句来实现。控制循环可以通过判断游标的属性来进行。
下面对这两种格式进行说明:
第一种格式中的变量名是用来从游标中接收数据的变量,需要事先定义。变量的个数和类型应与SELECT语句中的字段变量的个数和类型一致。
第二种格式一次将一行数据取到记录变量中,需要使用%ROWTYPE事先定义记录变量,这种形式使用起来比较方便,不必分别定义和使用多个变量。 定义记录变量的方法如下: 变量名 表名|游标名%ROWTYPE;
其中的表必须存在,游标名也必须先定义。 4.关闭游标 CLOSE 游标名;
显式游标打开后,必须显式地关闭。游标一旦关闭,游标占用的资源就被释放,游标变成无效,必须重新打开才能使用。
SET SERVEROUTPUT ON DECLARE
v_ename VARCHAR2(10); v_job VARCHAR2(10); CURSOR emp_cursor IS
SELECT ename,job FROM emp WHERE empno=7788; BEGIN
OPEN emp_cursor;
FETCH emp_cursor INTO v_ename,v_job;
DBMS_OUTPUT.PUT_LINE(v_ename||','||v_job);
CLOSE emp_cursor; END;
4. 数据仓库
4.1. ETL:
定义:
ETL分别是Extract(数据抽取)、 Transform(转换)、 Loading(装载)三个英文单词的首字母缩写。是数据抽取(Extract)、转换(Transform)、清洗(Cleansing)、装载(Load)的过程。构建数据仓库的重要一环,用户从数据源抽取出所需的数据,经过数据清洗,最终按照预先定义好的数据仓库模型,将数据加载到数据仓库中去。
抽取:将数据从各种原始的业务系统中读取出来,这是所有工作的前提。 转换:按照预先设计好的规则将抽取得数据进行转换、清洗,以及处理一些冗余、歧义的数据,使本来异构的数据格式能统一起来。 装载:将转换完的数据按计划增量或全部的导入到数据仓库中。在技术上主要涉及增量、转换、调度和监控等几个方面的处理。
ETL的质量问题具体表现为正确性、完整性、一致性、完备性、有效性、时效性和可获取性等几个特性。而影响质量问题的原因有很多,由系统集成和历史数据造成的原因主要包括:业务系统不同时期系统之间数据模型不一致;业务系统不同时期业务过程有变化;旧系统模块在运营、人事、财务、办公系统等相关信息的不一致;遗留系统和新业务、管理系统数据集成不完备带来的不一致性。
实现ETL,首先要实现ETL转换的过程。它可以集中地体现为以下几个方面:
1、空值处理 可捕获字段空值,进行加载或替换为其他含义数据,并可根据字段空值实现分流加载到不同目标库。
2、规范化数据格式 可实现字段格式约束定义,对于数据源中时间、数值、字符等数据,可自定义加载格式。
3、拆分数据 依据业务需求对字段可进行分解。例,主叫号 861084613409,可进行区域码和电话号码分解。 4、验证数据正确性 可利用Lookup及拆分功能进行数据验证。例如,主叫号861084613409,进行区域码和电话号码分解后,可利用Lookup返回主叫网关或交换机记载的主叫地区,进行数据验证。
5、数据替换 对于因业务因素,可实现无效数据、缺失数据的替换。
6、Lookup 查获丢失数据 Lookup实现子查询,并返回用其他手段获取的缺失字段,保证字段完整性。
7、建立ETL过程的主外键约束 对无依赖性的非法数据,可替换或导出到错误数据文件中,保证主键惟一记录的加载。
元数据:
拓展新型应用
对业务数据本身及其运行环境的描述与定义的数据,称之为元数据(metadata)。元数据是描述数据的数据。从某种意义上说,业务数据主要用于支持业务系统应用的数据,而元数据则是企业信息门户、客户关系管理、数据仓库、决策支持和B2B等新型应用所不可或缺的内容。
元数据的典型表现为对象的描述,即对数据库、表、列、列属性(类型、格式、约束等)以及主键/外部键关联等等的描述。特别是现行应用的异构性与分布性越来越普遍的情况下,统一的元数据就愈发重要了。“信息孤岛”曾经是很多企业对其应用现状的一种抱怨和概括,而合理的元数据则会有效地描绘出信息的关联性。
维度表, 事实表, 数据分析:
维度表示你要对数据进行分析时所用的一个量, 比如你要分析产品销售情况, 你可以选择按类别来进行分析,或按区域来分析. 这样的按..分析就构成一个维度。前面的示例就可以有两个维度:类型和区域。另外每个维度还可以有子维度(称为属性),例如类别可以有子类型,产品名等属性。下面是两个常见的维度表结构:
产品维度表:Prod_id, Product_Name, Category, Color, Size, Price 时间维度表:TimeKey, Season, Year, Month, Date
事实表:是数据聚合后依据某个维度生成的结果表。它的结构示例如下:
销售事实表:Prod_id(引用产品维度表), TimeKey(引用时间维度表), SalesAmount(销售总量,以货币计), Unit(销售量)
上面的这些表就是存在于数据仓库中的。从这里可以看出它有几个特点:
1. 维度表的冗余很大,主要是因为维度一般不大(相对于事实表来说的),而维度表的冗余可以使事实表节省很多空间。
2. 事实表一般都很大,如果以普通方式查询的话,得到结果一般发的时间都不是我们可以接受的。所以它一般要进行一些特殊处理。如SQL Server 2005就会对事实表进行如预生成处理等。
3. 维度表的主键一般都取整型值的标志列类型,这样也是为了节省事实表的存储空间。
聚合表:是包含事实数据表的汇总信息的表。当 SQL 作为查询机制使用时,这些表可用于提高查询性能。通俗一点就是:几个表连接起来插入倒一个新表,这个新表就是聚合表。