1.oracle优化器
优化目标分为4种: choose (选择性) rule (基于规则) first rows(第一行) all rows(所有行) Description:描述sql的执行计划 Object owner:对象模式 Object name:对象名 Cost:花费(的时间) Cardinality:基数,约等于行数 Bytes:空间(访问的存储空间)create table emp1 as select * from emp;
create table dept1 as select * from dept;insert into emp1 select * from emp1;select count(1) from emp1;2.table的访问方式
2.1全表扫描(table access full) 全表扫描就是顺序地访问表中每条记录。 oracle采用一次读入多个数据块(database block)的方式优化全表扫描。举例:select * from emp1;2.2通过rowid访问表(table access by user/index rowid) 可以采用基于rowid的访问方式情况,提高访问表的效率。 rowid包含了表中记录的物理位置信息。 oracle采用索引(index)实现了数据和存放数据的物理位置(rowid)之间的联系。 通常索引提供了快速访问rowid的方法,因此那些基于索引列的查询就可以得到性能上的提高。举例:select rowid,emp.* from emp where rowid='AAAR3sAAEAAAACXAAA';select * from emp1 where empno=7788;create index idx_emp1_empno on emp1(empno);3.共享sql语句
为了不重复解析相同的sql语句,在第一次解析之后, oracle将sql语句存放在内存中。 位于系统全局区域sga(system global area)的共享池(shared buffer pool)中,可以被所有的数据库用户共享。 当执行一个sql语句(有时被称为一个游标)时,如果它和之前的执行过的语句完全相同, oracle就能很快获得已经被解析的语句以及最好的执行路径。限制: 对单表查询有效,不适用于多表连接查询。 sql语句必须完全相同(包括空格,换行等),严格匹配(字符级的比较)。 举例:select * from emp; 和下列每一个都不同 select * from EMP; SELECT * from emp; select * from emp; TEST_ALL_OBJECTS3. 基础表的选择
基础表(driving table)是指被最先访问的表(通常以全表扫描的方式被访问). 根据优化器的不同, sql语句中基础表的选择是不一样的. 如果使用的是cbo (cost based optimizer),优化器会检查sql语句中的每个表的物理大小,索引的状态,然后选用花费最低的执行路径. 如果使用rbo (rule based optimizer),并且所有的连接条件都有索引对应, 基础表就是from 子句中列在最后的那个表.4.选择最有效率的表名顺序(只在基于规则的优化器中有效)
oracle的解析器按照从右到左的顺序处理from子句中的表名, 因此from子句中写在最后的表(基础表 driving table)将被最先处理。 在from子句中包含多个表的情况下,必须选择记录条数最少的表作为基础表。 当oracle处理多个表时, 会运用排序及合并的方式连接它们。 首先,扫描第一个表(from子句中最后的那个表)并对记录进行派序, 然后扫描第二个表(from子句中最后第二个表), 最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。select * from emp,dept where emp.deptno=dept.deptno;select * from emp1,dept1 where emp1.deptno=dept1.deptno;HASH JOIN 比较适合大表的连接
先将一个表(通常是小表)做Hash运算,再将列数据存储到hash列表中, 从另一个表中抽取记录,做hash运算,到hash列表中找到相应的值做匹配。SORT MERGE JOIN 排序合并 先将关联表的关联列各自做排序,然后从各自排序表中抽取数据,到另一个排序表中做匹配, 因为Meger join需要做更多的排序,所以消耗的资源更多。 SORT MERGE JOIN使用的场合是没有索引,并且数据已经排序的情况。NESTED LOOPS 嵌套循环 比较适合小表(小于1万)的连接 先从一个表中读取数据,访问另一张表(通过索引访问)来做匹配, NESTED LOOPS使用的场合是当一个关联表比较小的时候,效率会更高。如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表,
交叉表是指那个被其他表所引用的表. select * from student,course,sc where student.sno=sc.sno and course.cno=sc.cno;5. where子句中的连接顺序(只在基于规则的优化器中有效)
oracle采用自下而上的顺序解析where子句。根据这个原理,表之间的连接必须写在其他where条件之前, 那些可以过滤掉最大数量记录的条件必须写在where子句的末尾. select * from emp,dept where emp.deptno=dept.deptno and empno=7788;select * from emp1,dept1 where emp1.deptno=dept1.deptno and empno=7788;select * from student,course,sc where student.sno=sc.sno and course.cno=sc.cno and student.sno=1;6. select子句中避免使用'*'
在select子句中列出所有的column时,使用动态sql列引用'*' 是一个方便的方法.不幸的是,这是一个非常低效的方法. 实际上,oracle在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间. select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp;-->select * from emp;7. 减少访问数据库的次数
当执行每条sql语句时, oracle在内部执行了许多工作: 解析sql语句, 估算索引的利用率, 绑定变量 , 读数据块等等. 由此可见, 减少访问数据库的次数 , 就能实际上减少oracle的工作量. 例如, 检索出雇员号等于7369或7788的职员. select ename,sal,job from emp where empno =7369; select ename,sal,job from emp where empno =7788; -->select ename,sal,job from emp where empno in (7369,7788); select ename,sal,job from emp where empno =7369; select ename,sal,job from emp where deptno =20; -->select ename,sal,job from emp where empno=7369 or deptno =20;8. 使用decode函数来减少处理时间
使用decode函数可以避免重复扫描相同记录或重复连接相同的表. 例如: select count(*),sum(sal) from emp where deptno=20 and ename like '%S%'; select count(*),sum(sal) from emp where deptno=30 and ename like '%S%'; 可以用decode函数高效地得到相同结果 select count(decode(deptno,20,1,null)) d20_count, count(decode(deptno,30,1,null)) d30_count, sum(decode(deptno,20,sal,null)) d20_sal, sum(decode(deptno,30,sal,null)) d30_sal from emp where ename like '%S%';9. 减少对表的查询
在含有子查询的sql语句中,要特别注意减少对表的查询. select emp.*,(select dname from dept where deptno=emp.deptno) dname,(select loc from dept where deptno=emp.deptno) loc from emp;-->select emp.*,dname,loc from emp,dept where emp.deptno=dept.deptno;10.用truncate替代delete
当删除表中的记录时,回滚段(rollback segments)用来存放被删除的信息。如果没有commit事务,oracle会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况) 而当运用truncate时, 回滚段不再存放任何可被恢复的信息。当命令运行后,数据不能被恢复。限制:truncate只能删除全表数据,不能删除部分数据。11.尽量多使用commit
尽量多使用commit,避免回滚段和redo log空间的被过度占用。批量操作时每1000条数据提交一次。12. 用exists替代in
select * from emp1 where deptno in (select deptno from dept1 where dname='RESEARCH'); --7.302s-->select * from emp1 where exists (select 1 from dept1 where dname='RESEARCH' and deptno=emp1.deptno); --7.284screate index idx_emp1_deptno on emp1(deptno);
select * from dept1 where deptno in (select deptno from emp1); --0.016s-->select * from dept1 where exists (select 1 from emp1 where deptno=dept1.deptno); --0.016sselect * from all_tables
select * from all_tab_columnscreate table temp_tab as select * from all_tables;create table temp_col as select owner,table_name,column_name,data_type,data_length,column_id from all_tab_columns;select * from temp_tab where table_name in (select table_name from temp_col); --0.343s
-->select * from temp_tab where exists (select 1 from temp_col c where table_name=c.table_name); --0.282screate index idx_table_name on temp_tab(table_name);
13. 用not exists替代not in
update emp1 set deptno=10 where empno=7839;select * from dept1 where deptno not in (select deptno from emp1); --0.156s
-->select * from dept1 where not exists (select 1 from emp1 where deptno=dept1.deptno); --0.015sselect * from temp_tab where table_name not in (select table_name from temp_col); --0.031s
-->select * from temp_tab where not exists (select 1 from temp_col c where table_name=c.table_name); --0.015s14. 用表连接替换exists
通常来说,采用表连接的方式比exists更有效率 create index idx_dept1_deptno on dept1(deptno);select emp1.* from emp1 where exists (select 1 from dept1 where deptno=emp1.deptno);--18.9s
-->select emp1.* from emp1,dept1 where emp1.deptno=dept1.deptno; --18.002sselect * from temp_tab where exists (select 1 from temp_col c where table_name=c.table_name); --0.063s
-->select t.* from temp_tab t,temp_col c where t.table_name=c.table_name; --0.062s15. 用exists替换distinct
当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在select子句中使用distinct. 一般可以考虑用exist替换 select distinct dept1.deptno,dname from dept1,emp1 where dept1.deptno=emp1.deptno; --0.764s-->select deptno,dname from dept1 where exists(select 1 from emp1 where deptno=dept1.deptno);--0.312s15. cbo下使用更具选择性的索引
基于成本的优化器(cbo, cost-based optimizer)对索引的选择性进行判断来决定索引的使用是否能提高效率. 如果索引有很高的选择性, 那就是说对于每个不重复的索引键值,只对应数量很少的记录。比如, 表中共有100条记录而其中有80个不重复的索引键值. 这个索引的选择性就是80/100 = 0.8 . 选择性越高, 通过索引键值检索出的记录就越少. 如果索引的选择性很低,检索数据就需要大量的索引范围查询操作和rowid访问表的操作. 也许会比全表扫描的效率更低. 如果检索数据量超过30%的表中记录数.使用索引将没有显著的效率提高. 在特定情况下, 使用索引也许会比全表扫描慢, 但这是同一个数量级上的区别. 一般情况下,使用索引比全表扫描要块几倍乃至几千倍! drop index IDX_OBJECT_ID;select * from TAB_ALL_OBJECTS where OBJECT_ID=75237; --2.964screate index IDX_OBJECT_ID on TAB_ALL_OBJECTS (OBJECT_ID)select * from TAB_ALL_OBJECTS where OBJECT_ID=75237; --0.422sselect column_name,count(1) from temp_col group by column_name having count(1)=1
select * from temp_col where column_name='CLUSTER_NAME'; --0.109screate index IDX_column_name on temp_col (column_name); --0.046s--清除数据库内存缓冲区
alter system flush buffer_cache;16. 用索引提高效率 索引是表的一个概念部分,用来提高检索数据的效率. 实际上,oracle使用了一个复杂的自平衡b-tree结构. 通常,通过索引查询数据比全表扫描要快. 通常, 在大型表中使用索引特别有效. 虽然使用索引能得到查询效率的提高,但是索引需要空间来存储。每当表中有记录做insert\update\delete时,索引会被修改. 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢. 定期的重构索引是有必要的. alter index <indexname> rebuild <tablespacename>17. oracle对索引的访问模式.
17.1.完全索引扫描 INDEX FULL SCANselect emp.* from emp,dept where emp.deptno=dept.deptno;--first row17.2快速完全索引扫描 INDEX FAST FULL SCANselect view_name from tab_all_views;select OBJECT_ID from TEST_ALL_OBJECTS;--all rows17.3 索引范围扫描 INDEX RANGE SCANcreate index idx_emp_deptno on emp (deptno);select * from emp where deptno=10;17.4 唯一索引扫描 INDEX UNIQUE SCAN
select * from emp where empno=7788;17.5 索引跳跃扫描18.唯一性索引与非唯一性索引
优先使用唯一性索引select ename from emp where empno=7788 and deptno=20;19. 多个平等的索引
当sql语句的执行路径可以使用分布在多个表上的多个索引时, oracle会同时使用多个索引并在运行时对它们的记录进行合并, 检索出仅对全部索引有效的记录。select * from emp,dept where emp.deptno=dept.deptno and ename='SCOTT';在oracle选择执行路径时,唯一性索引的等级高于非唯一性索引。
select * from emp where job='ANALYST';select * from emp where empno=7788 and job='ANALYST';然而这个规则只有当where子句中索引列和常量比较才有效。
如果索引列和其他表的索引列相比较,在优化器中的等级是非常低的。select * from emp,dept where emp.deptno=dept.deptno and job='ANALYST';如果不同表中两个相同等级的索引将被引用,from子句中表的顺序将决定哪个会被率先使用;
from子句中最后的表的索引将有最高的优先级。--select * from emp,dept where emp.deptno=dept.deptno;如果相同表中两个相同等级的索引将被引用,不同的优化器可能索引的优先顺序不同。select * from emp e1,emp e2 where e1.deptno=20 and e2.job='CLERK';20.等式比较和范围比较
等式比较和范围比较将优先选择等式比较。select * from emp where deptno>10 and job='CLERK';select * from emp where job='CLERK' and deptno>10;21.数值列索引优于字符列索引
优先使用选择性高的索引create index IDX_EMP_JOB on EMP (JOB);select * from emp where deptno=10 and job='CLERK';select * from emp where deptno>10 and job>'A';create index IDX_EMP_JOB_SAL on EMP (JOB, SAL)
21.同等级的字符索引,选择性高的优于选择性低的create index IDX_EMP_ENAME on EMP (ENAME);select * from emp where job='CLERK' and ename='SMITH';select * from emp where ename='SMITH' and job='CLERK';在rule优化器下,两个索引都能被用到,并且按照where后的顺序来区分优先级。select * from emp_1 where job='CLERK' and ename='SMITH';select * from emp_1 where ename='SMITH' and job='CLERK';22. 避免在索引列上使用计算.
对索引列进行运算将导致索引失效,可以将运算等效的移给变量端。drop index IDX_EMP_SAL;create index IDX_EMP_SAL on EMP (SAL);select * from emp where sal*12>25000;-->select * from emp where sal>25000/12;23. 强制索引失效
多个平等索引情况下,rule优化器使用了多个个索引,可以故意让区分度低的索引失效。select * from emp where job='CLERK' and ename='SMITH';-->select * from emp where job||''='CLERK' and ename='SMITH';25. 避免在索引列上使用not
not会停止使用索引,执行全表扫描. select * from emp where not ename='SMITH';对于数值比较运算,oracle优化器会自动将not转化成对应的比较运算符,将不会影响索引的使用。not > to <= not >= to < not < to >= not <= to > select ename from emp where not deptno>20; -->select ename from emp where deptno<=20;26. 用>=替代>
如果deptno上有一个索引, select ename from emp where not deptno>20; -->select ename from emp where not deptno>=30; 两者的区别在于, 前者dbms将直接跳到第一个deptno等于30的记录,而后者将首先定位到deptno=20的记录并且向前扫描到第一个dept大于20的记录.27. 用union替换or (适用于索引列)
对索引列使用or将造成全表扫描. --chooseselect * from emp where deptno=20 or ename='SMITH';-->select * from emp where deptno=20 union select * from emp where ename='SMITH';注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询效率可能会因为你没有选择or而降低.28. 用in来替换or
--数据库已经智能了,可以替换了select * from emp where deptno=10 or deptno=20 or deptno=30;-->select * from emp where deptno in (10,20,30);29. 避免在索引列上使用is null和is not null
避免在索引中使用任何可以为空的列,oracle将无法使用该索引。select * from emp where ename is null;select * from emp where ename is not null;对于单列索引,如果列包含空值,索引中将不存在此记录。对于复合索引,如果每个列都为空,索引中同样不存在此记录;如果至少有一个列不为空,则记录存在于索引中。如果唯一性索引建立在表的a列和b列上, 并且表中存在一条记录的a,b值为(123,null) , oracle将不接受下一条具有相同a,b值(123,null)的记录(插入)。create table test (a number,b number);create unique index idx_test on test(a,b);insert into test values (123,null);--insert into test values (123,null);然而如果所有的索引列都为空,oracle将认为整个键值为空而空不等于空。
因此可以插入1000 条具有相同键值的记录,当然它们都是空! delete from test;insert into test values (null,null);因为空值不存在于索引列中,所以where子句中对索引列进行空值比较将使oracle停用该索引。select * from test where a is null;30.复合索引总是使用索引的第一个列 如果索引是建立在多个列上, 只有在它的第一个列被where子句引用时,优化器才会选择使用该索引。create index IDX_EMP_JOB on EMP (JOB);create index IDX_EMP_JOB_SAL on EMP (JOB, SAL);create index IDX_EMP_SAL on EMP (SAL);drop index IDX_EMP_JOB;drop index IDX_EMP_SAL;select * from emp where job='CLERK';select * from emp where sal=5000;当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引31.用union-all替换union
当sql语句需要union两个查询结果集合时,这两个结果集合会以union-all的方式被合并,然后在输出最终结果前进行排序。如果用union all替代union, 这样排序就不是必要了,效率就会因此得到提高。select * from stu_oracle union all select * from stu_java;32.order by使用索引的条件:
order by中所有的列必须包含在相同的索引中并保持在索引中的排列顺序; order by中所有的列必须定义为非空。where子句使用的索引和order by子句中所使用的索引不能并列。 select * from emp order by empno; --select * from emp order by empno,ename;33. 避免改变索引列的类型
当比较不同数据类型的数据时,oracle自动对列进行简单的类型转换。select * from emp where empno='7788';能使用索引是因为oracle自动对列进行简单的类型转换,转换后的语句为:select * from emp where empno=to_number('7788');类型转换没有发生在索引列上,索引没有失效。create table emp_test (empno varchar2(10) primary key,ename VARCHAR2(10));insert into emp_test select empno,ename from emp;select * from emp_test where empno=7788;
select * from emp_test where to_number(empno)=7788;-->select * from emp_test where empno='7788';因为内部发生的类型转换, 这个索引将不会被用到! 为了避免oracle对sql进行隐式的类型转换,最好把类型转换用显式表现出来。34. where子句对列加下列符号将不使用索引
'!=' 、'||' 、'+'索引只能记录有哪些键值, 不能记录没有哪些键值 select * from emp where empno!=0; select * from emp where empno+0=7788; -->select * from emp where empno>0;36. 避免使用耗费资源的操作
带有distinct,union,minus,intersect,order by的sql语句会启动sql引擎执行耗费资源的排序(sort)功能。distinct需要一次排序操作, 而其他的至少需要执行两次排序。例如,一个union查询,其中每个查询都带有group by子句, group by会触发嵌入排序(nested sort);这样,每个查询需要执行一次排序, 然后在执行union时,又一个唯一排序(sort unique)操作被执行而且它只能在前面的嵌入排序结束后才能开始执行。带有union,minus,intersect的sql语句都可以用其他方式重写。37.分离表和索引 将表和索引建立在不同的表空间内(tablespaces)。决不要将不属于oracle内部系统的对象存放到system表空间里。同时,确保数据表空间和索引表空间置于不同的硬盘上。