Toggle navigation
程序员小站
首页
分类
标签
归档
关于我
友链
登录
Oracle Sql 性能优化调整
admin
2020-05-09
444
1
原创
数据库
## Oracle Sql 性能优化调整 ### 一、选用适合的Oracle优化器,Oracle的优化器共有3种: Ø Rule(基于规则) Ø Cost(基于成本) Ø 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优化器,而直接采用基于规则或者基于成本的优化器。 ### 二、访问Table的方式,Oracle采用两种访问表中记录的方式 Ø 全表扫描 全表扫描就是顺序地访问表中每条记录。Oracle采用一次读入多个数据块(database block)的方式优化全表扫描。 Ø 通过Rowid访问表 你可以采用基于Rowid的访问方式情况,提高访问表的效率,Rowid包含了表中记录的物理位置信息……Oracle采用索引(index)实现了数据和存放数据的物理位置(Rowid)之间的联系。通常索引提供了快速访问Rowid的方法,因此那些基于索引列的查询就可以得到性能上的提高。 ### 三、共享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语句必须完全相同(包括空格,换行等) 共享的语句必须满足三个条件: Ø 字符级的比较 当前被执行的语句和共享池中的语句必须完全相同。例如: SELECT * FROM EMP; 和下列每一个都不同 SELECT * from EMP; Select * From Emp; SELECT * FROM EMP; Ø 两个语句所指的对象必须完全相同 例如: | 用户 | 对象名 | 如何访问 | | ---- | ------------ | --------------- | | 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, plant_detail b where a.city_id = b.city_id | 不能 | 用户jack通过private synonym访问plant_detail 而Jill是表的所有者,对象不同 | Ø 两个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; ``` ### 四、选择最有效的表名顺序(只在基于规则的优化器中有效) 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表的交集。 ```sql 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更有效率 ```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 ``` ### 五、Where子句中的连接顺序 Oracle采用自下而上的顺序解析Where子句,根据这个原理,表之间的连接必须写在其他Where条件之前,那些可以过滤掉最大数量记录的条件必须写在Where子句的末尾。 例如: ```sql select ... from EMP E where SAL>50000 and JOB ='MANAGER' and 25 < (select count(*) from EMP where MGR = E.EMPNO) ``` (低效,执行时间156.3秒) ```sql select ... from EMP E where 25 < (select count(*) from EMP where MGR = E.EMPNO) and SAL > 50000 and JOB = 'MANAGER' ``` (高效,执行时间10.6秒) ### 六、Select子句中避免使用’*’ 当你想在Select子句中列出所有的Column时,使用动态SQL列引用’*’是一个方便的方法。不幸的是,这是一个非常低效的方法。实际上,Oracle在解析的过程中,会将’*’依次转换成所有的列名,这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间。 ### 七、减少访问数据库的次数 当执行每条SQL语句时,Oracle在内部执行了许多工作:解析SQL语句,估算索引的利用率,绑定变量,读数据块等等。由此可见,减少访问数据库的次数,就能实际上减少Oracle的工作量。 例如:以下有三种方法可以检索出雇员号等于0342或0291的职员。 Ø 方法1(最低效) ```sql select EMP_NAME, SALARY, GRADE from EMP where EMP_NO = 0342 select EMP_NAME, SALARY, GRADE from EMP where EMP_NO = 0291 ``` Ø 方法2(次低效) ```sql declare cursor c1(E_NO NUMBER) is select EMP_NAME, SALARY, GRADE from EMP Where EMP_NO = E_NO; Begin Open c1(0342); fetch c1 into …, .., ..; Open c1(0291); fetch c1 into …, .., ..; close c1; end; ``` Ø 方法3(高效) ```sql select a.EMP_NAME, a.SALARY, a.GRADE, b.EMP_NAME, b.SALARY, b.GRADE from EMP a, EMP b where a.EMP_NO = 0342 and b.EMP_NO = 0291; ``` 注意: 在SQL*Plus,SQL*Forms和Pro*C中重新设置Arraysize参数,可以增加每次数据库访问的检索数据量,建议值为200。 ### 八、使用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子句中。 ### 九、整合简单,无关联的数据库访问 如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系) 例如: ``` select name from EMP where EMP_no = 1234; select name from DTP where DPT - no = 10; select name from CAT where CAT_type = 'RD'; ``` 上面的3个查询可以被合并成一个: ```sql 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' ``` (译者按:虽然采用这种方法,效率得到提高,但是程序的可读性大大降低,所以读者还是要权衡之间的利弊) ### 十、删除重复记录 最高效的删除重复记录方法(因为使用了Rowid) ```sql delete from EMP E where E.Rowid > (select min(x.rowid) from EMP X where X.EMP_NO = E.EMP_NO); ``` ### 十一、用Truncate替代Delete 当删除表中的记录时,在通常情况下,回滚段(rollback segments)用来存放可以被恢复的信息。如果你没有Commit事务,Oracle会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况) 而当运用Truncate时,回滚段不再存放任何可被恢复的信息。当命令运行后,数据不能被恢复。因此很少的资源被调用,执行时间也会很短。 (译者按:Truncate只在删除全表适用,Truncate是DDL不是DML) ### 十二、尽量多使用Commit 只要有可能,在程序中尽量多使用Commit,这样程序的性能得到提高,需求也会因为Commit所释放的资源而减少。Commit所释放的资源: Ø 回滚段上用于恢复数据的信息 Ø 被程序语句获得的锁 Ø Redo log buffer中的空间 Ø Oracle为管理上述3种资源中的内部花费 (译者按:在使用Commit时必须要注意到事务的完整性,现实中效率和事务完整性往往是鱼和熊掌不可兼得) ### 十三、计算记录条数 和一般的观点相反,count(*)比count(1)稍快,当然如果可以通过索引检索,对索引列的计数仍旧是最快的。如:count(empno) (译者按:在CSDN论坛中,曾经对此有过相当热烈的讨论,作者的观点并不十分准确,通过实际的测试,上述三种访求并没有显著的性能差别) ### 十四、用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子句中) ### 十五、减少对表的查询 在含有子查询的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_Detp = 0020; ``` (低效) ``` update EMP set (EMP_CAT, SAL_RANGE) = (Select max(category), max(sal_range) from EMP_Categories) where EMP_Dept = 0020; ``` (高效) ### 十六、通过内部函数提高SQL效率 ```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; ``` 通过调用下面的函数可以提高效率 ```sql function 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; ``` ```sql function lookup_emp(typ in number) return varchar2 as tdesc 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; ``` ```sql 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; ``` (译者按:经常在论坛中看到如“能不能用一个SQL写出…。”的帖子,殊不知复杂的SQL往往牺牲了执行效率。能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的) ### 十七、使用表的别名(Alias) 当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个Column上。这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。 (译者注:Column歧义指的是由于SQL中不同的表具有相同的Column名,当SQL语句中出现这个Column时,SQL解析器无法判断这个Column的归属) ### 十八、用Exists替代In 在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。在这种情况下,使用Exists(或Not Exists)通常将提高查询的效率。 ```sql select * from emp(基础表) where empno > 0 and deptno in (select deptno from dept where loc = 'MELB') ``` (低效) ```sql select * from emp(基础表) where empno > 0 and exists (select 'X' from dept where dept.deptno = emp.deptno and loc = 'MELB') ``` (高效) (译者按:相对来说,用Not Exists替换Not in将更显著地提高效率,下一节中将指出) ### 十九、用Not Exists替代Not In 在子查询中,Not In子句将执行一个内部的排序和合并。无论在哪种情况下,Not In都是最低效的(因为它对子查询中的表执行了一个全表遍历)。为了避免使用Not In,我们可以把它改写成外连接(Outer Joins)或Not Exists。 例如: ```sql select ... from emp where dept_no not in (select dept_no from dept where dept_cat = 'A'); ``` 为了提高效率。改写为: (方法一:高效) ```sql select ... from emp a, dept b where a.dept_no = b.dept(+) and b.dept_no is null and b.dept_cat(+) = 'A'; ``` (方法二:最高效) ```sql select ... from emp e where not exists (select 'X' from dept d where d.dept_no = e.dept_no and dept - cat = 'A'); ``` ### 二十、用表连接替换Exists 通常来说,采用表连接的方式比Exists更有效率 ```sql select ename from emp e where exists (select 'X' from dept where dept_no = e.dept_no and dept_cat = 'A'); ``` (更高效) ```sql select ename from dept d, emp e where e.dept_no = d.dept_no and dept_cat = 'A'; ``` (译者按:在RBO的情况下,前者的执行路径包括Filter,后者使用Nested Loop) ### 二十一、用Exists替换Distinct 当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在Select子句中使用Distinct。一般可以考虑用Exist替换。 例如: 低效: ```sql select distinct dept_no, dept_name from dept d, emp e where d.dept_no = e.dept_no; ``` 高效: ```sql select dept_no, dept_name from dept d where exists (select 'X' from emp e where e.dept_no = d.dept_no); ``` Exists使查询更为迅速,因为Rdbms核心模块将在子查询的条件一旦满足后,立刻返回结果。 ### 二十二、识别“低效执行”的SQL语句 用下列SQL工具找出低效SQL: ```sql select executions, disk_reads, buffer_gets, round((buffer_gets - disk_reads) / buffer_gets, 2) hit_radio, round(disk_reads / executions, 2) reads_per_run, SQL_text from V$sqlarea where executions > 0 and buffer_gets > 0 and (buffer_gets - disk_reads) / buffer_gets < 0.8 order by 4 desc; ``` (译者按:虽然目前各种关于SQL优化的图形化工具导出不穷,但是写出自己的SQL工具来解决问题始终是一个最好的方法) ### 二十三、使用Tkprof工具来查询SQL性能状态 SQL trace工具收集正在执行的SQL的性能状态数据并记录到一个跟踪文件中。这个跟踪文件提供了许多有用的信息,例如:解析次数。执行次数,CPU使用时间等。这些数据将可以用来优化你的系统。 设置SQL trace在会话级别: 有效 alter session set sql_trace true 设置SQL trace在整个数据库有效仿,你必须将SQL_trace参数在init.ora中设为True,User_dump_dest参数说明了生成跟踪文件的目录。 (译者按:这一节中,作者并没有提到Tkprof的用法,对SQL Trace的用法也不够准确,设置SQL Trace首先要在init.ora中设定Timed_statistics,这样才能得到那些重要的时间状态。生成的trace文件是不可读的,所以要用Tkprof工具对其进行转换,Tkprof有许多执行参数。大家可以参考Oracle手册来了解具体的配置。) ### 二十四、用Explain Plan分析SQL语句 Explain Plan是一个很好的分析SQL语句的工具,它甚至可以在不执行SQL的情况下分析语句。通过分析,我们就可以知道Oracle是怎么样连接表,使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称。 你需要按照从里到外,从上到下的次序解读分析的结果。Explain Plan分析的结果是用缩进的格式排列的,最内部的操作将被最先解读,如果两个操作处于同一层中,带有最小操作号的将被首先执行。 Nested Loop是少数不按照上述规则处理的操作,正确的执行路径是检查对Nested Loop提供数据的操作,其中操作号最小的将被最先处理。 (译者按:通过实践,感到还是用Sqlplus中的Set trace功能比较方便。) 例如: SQL> list ```sql select * from dept,emp where emp.deptno = dept.deptno ``` SQL> set autotrace traceonly traceonly可以不显示执行结果 SQL> 14 rows selected. Execution Plan \------------------------------------------------------- 0 select stratement Optimizer = CHOOSE 1 0 Nested Loops 2 1 Table Access (full) of ‘EMP’ 3 2 Table Access (BY Index Rowid) of ‘Dept’ 4 3 Index (Unique Scan) of ‘Pk_dept’ (Unique) Statistics 0 recursive calls 2 db block gets 30 consistent gets 0 physical reads 0 redo size 2598 bytes sent via SQL*Net to client 503 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 14 rows processed 通过以上分析,可以得出实际的执行步骤是: 1. Table access (full) of ‘emp’ 2. Index (unique Scan) of ‘Pk_dept’ (Unique) 3. Table access (by Index rowid) of ‘dept’ 4. Nested Loops (Joining 1 and 3) 注:目前许多第三方的工具如Toad和Oracle本身提供的工具如OMS的SQL Analyze都提供了极其方便的Explain Plan工具。也许喜欢图形化界面的朋友们可以用它们。 ### 二十五、用索引提高效率 索引是表的一个概念部分,用来提高检索数据的效率。实际上,Oracle使用了一个复杂的自平衡B-tree结构。通常,通过索引查询数据比全表扫描要快。当Oracle找出执行查询和Update语句的最佳路径时,Oracle优化器将使用索引。同样在联结多个表时使用索引也可以提高效率。另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证。 除了那些Long或Long Raw数据类型,你可以索引几乎所有的列。通常,在大型表中使用索引特别有效。当然,你也会发现,在扫描小表时,使用索引同样能提高效率。 虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价。索引需要空间来存储,也需要定期维护,每当有记录在表中增减或索引列被修改时,索引本身也会被修改。这意味着每条记录的Insert,Delete,Update将为此多付出4,5次的磁盘I/O。因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。 (译者按:定期的重构索引是有必要的。 Alter index <Indexname> rebuild <Tablespacename>) ### 二十六、索引的操作 Oracle对索引有两访问模式 Ø 索引唯一扫描(Index Unique Scan) 大多数情况下,优化器通过Where子句访问Index 例如: 表Lodging有两个索引:建立在Lodging列上的唯一性索引Lodging_PK和建立在Manager列上的非唯一性索引Lodging$Manager. ```sql Select * from Lodging Where Loging = ‘rose hill’; ``` 在内部,上述SQL将被分成两步执行,首先,Lodging_PK索引将通过索引唯一扫描的方式被访问,获得相对应的Rowid,通过Rowid访问表的方式执行下一步检索。 如果被检索返回的列包括在Index列中,Oracle将不执行第二步的处理(通过Rowid访问表)。因为检索数据保存在索引中,单单访问索引就可以完全满足查询结果。 下面SQL只需要Index Unique Scan操作。 ```sql Select lodging from lodging where lodging = ‘rose hill’; ``` Ø 索引范围查询(Index Range Scan) 适用于两种情况:基于一个范围的检索;基于非唯一性索引的检索。 例1: ```sql Select lodging from lodging where lodging like ‘m%’; ``` Where子句条件包括一系列值,Oracle将通过索引范围查询的方式查询Lodging_pk.由于索引范围查询将返回一组值,它的效率就要比索引唯一扫描低一些。 例2: ```sql Select lodging from lodging where manager = ‘bill gates’; ``` 这个SQL的执行分两步,Lodging$Manager的索引范围查询(得到所有符合条件记录的Rowid)和下一步通过Rowid访问表得到Lodging列的值。由于Lodging$Manager是一个非唯一性的索引,数据库不能对它执行索引唯一扫描。 由于SQL返回Lodging列,而它并不存在于Lodging$Manager索引中,所以在索引范围查询后会执行一个通过Rowid访问表的操作。 Where子句中,如果索引列所对应的值的第一个字符由通配符(Wildcard)开始,索引将不被采用。在这种情况下,Oracle将使用全表扫描。 ```sql Select lodging from lodging where manager like ‘%hanman’; ``` ### 二十七、基础表的选择 基础表(Driving Table)是指被最先访问的表(通常以全表扫描的方式被访问)。根据优化器的不同,SQL语句中基础表的选择是不一样的。 如果你使用的是CBO(Cost Based Optimizer),优化器会检查SQL语句中的每个表的物理大小,索引状态,然后选用花费最低的执行路径。 如果你用RBO(Rule Based Optimizer),并且所有的连接条件都有索引对应,在这种情况下,基础表就是From子句中列在最后的那个表。 例如: ```sql Select a.name,b.manager from worker a, lodging b where a.lodging = b.loding; ``` 由于Lodging表的Loding列上有一个索引,而且Worker表中没有相比较的索引,Worker表将被作为查询中的基础表。 ### 二十八、多个平等的索引 当SQL语句的执行路径可以使用分布在多个表上的多个索引时,Oracle会同时使用多个索引并在运行时对它们的记录进行合并,检索出仅对全部索引有效的记录。 在Oracle选择执行路径时,唯一性索引的等级高于非唯一性索引。然而这个规则只有当Where子句中索引列和常量比较才有效。如果索引列和其他表的索引类相比较。这种子句在优化器中的等级是非常低的。 如果不同表中两个相同等级的索引将被引用,From子句中表的顺序将决定哪个会被率先使用。From子句中最后的表的索引将有最高的优先级。 如果相同表中两个相同等级的索引将被引用,Where子句中最先被引用的索引将有最高的优先级。 例如: Deptno上有一个非唯一性索引,EMP_cat也有一个非唯一性索引。 ```sql Select ename from emp where deptno = 20 and emp_cat = ‘A’; ``` 这里,Deptno索引将被最先检索,然后同EMP_Cat索引检索出的记录进行合并。执行路径如下: Table access by rowid on emp And-equal Index range scan on dept_Idx Index range scan on cat_Idx ### 二十九、等式比较和范围比较 当Where子句中有索引列,Oracle不能合并它们,Oracle将用范围比较。 例如: Deptno上有一个非唯一性索引,Emp_Cat也有一个非唯一性索引。 ```sql Select ename from emp where deptno > 20 and emp_cat = ‘A’; ``` 这里只有Emp_cat索引被用到,然后所有的记录将逐条与Deptno条件进行比较。执行路径如下: Table access by rowid on emp Index range scan on cat_idx ### 三十、不明确的索引等级 当Oracle无法判断索引的等级高低差别,优化器将只使用一个索引,它就是在Where子句中被列在最前面的。 例如: Deptno上有一个非唯一性索引,Emp_cat也有一个非唯一性索引。 ```sql 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 (译者按:我们来试一下以下这种情况:) SQL> select index_name,uniqueness from user_indexes where table_name = ‘emp’; Index_name Uniquenes \------------------------------------------------- EMPNO UNIQUE EMPTYPE NONUNIQUE SQL> select * from emp where empno >= 2 and emp_type = ‘A’; No rows selected Execution Plan \------------------------------------------------- 0 Select statement Optimizer=CHOOSE 1 0 Table access (by index rowid) of ‘emp’ 2 1 Index (range scan) of ‘emptype’ (non-unique) 虽然Empno是唯一性索引,但是由于它所做的是范围比较,等级要比非唯一性索引的等式比较低! ### 三十一、强制索引失败 如果两个或以上索引具有相同的等级,你可以强制命令Oracle优化器使用其中一个(通过它,检索出的记录数量少)。 例如: Select ename from emp where empno = 7935 and deptno + 0 = 10 /*deptno上的索引将失败*/ and emp_type || ‘’= ‘A’/*emp_type上的索引将失效*/ 这是一种相当直接的提高查询效率的办法。但是你必须谨慎考虑这种策略,一般来说,只有在你希望单独优化几个SQL时才能采用它。 这里有一个例子关于何时采用这种策略, 假设在Emp表的Emp_type列上有一个非唯一性的索引而Emp_class上没有索引。 ```sql Select ename from emp where emp_type = ‘A’ and emp_class = ‘X’; ``` 优化器会注意到Emp_type上的索引并使用它。这是目前唯一的选择。如果,一段时间以后,另一个非唯一性建立在Emp_class上,优化器必须对两个索引进行选择,在通常情况下,优化器将使用两个索引并在他们的结果集合上执行排序及合并。然而,如果其中一个索引(Emp_type)接近于唯一性而另一个索引(Emp_class)上有几千个重复的值。排序及合并就会成为一种不必要的负担。在这种情况下,你希望使优化器屏蔽掉Emp_class索引。 用下面的方案就可以解决问题。 ```sql Select ename from emp where emp_type = ‘A’ and emp_class || ‘’= ‘X’; ``` ### 三十二、避免在索引列上使用计算 Where子句中,如果索引列是函数的一部分。优化器将不使用索引而使用全表扫描。 例如: (低效) ```sql Select … from dept where sal * 12 > 25000; ``` (高效) ```sql Select … from dept where sal > 25000/12; ``` 译者按:这是一个非常实用的规则,请务必牢记。 ### 三十三、自动选择索引 如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性。 在这种情况下,Oracle将使用唯一性索引而完全忽略非唯一性索引。 例如: ```sql 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 ### 三十四、避免在索引列上使用Not 通常我们要避免在索引列上使用Not,Not会产生和在索引列上使用函数相同的影响。当Oracle“遇到”Not,他就会停止使用索引转而执行全表扫描。 例如: 低效(这里不使用索引) ```sql Select … from dept where dept_code not = 0; ``` 高效(这里使用索引) ```sql Select … from dept where dept_code > 0; ``` 需要注意的是,在某些时候,Oracle优化器会自动将Not转化成相对应的关系操作符。 Not > to <= Not >= to < Not < to >= Not <= to > 译者按:在这个例子中,作者犯了一些错误。例子中的低效率SQL是不能被执行的。我做了一些测试。 SQL> select * from emp where Not empno >1; No rows selected Execution Plan \----------------------------------------------------- 0 Select Statement Optimizer=CHOOSE 1 0 Table Access (by index rowid) of ‘emp’ 2 1 Index (range scan) of ‘empno’ (unique) SQL> select * from emp where empno <=1; No rows selected Execution Plan \----------------------------------------------------- 0 select statement optimizer=shoose 1 0 table access (by index rowid) of ‘emp’ 2 1 index (range scan) of ‘empno’ (unique) 两者的效率完全一样,也许这符合作者关于“在某些时候,Oracle优化器会自动将Not转化成相对应的关系操作符”的观点。 ### 三十五、用>=替代> 如果Deptno上有一个索引。 高效: ```sql Select * from emp where deptno >=4; ``` 低效: ```sql Select * from emp where deptno >3; ``` 两者的区别在于,前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到Deptno=3的记录并且向前扫描到第一个DEPT大于3的记录。 ### 三十六、用Union替换Or(适用于索引列) 通常情况下,用Union替换Where子句中的Or将会起到较好的效果。对索引列使用Or将造成全表扫描。注意,以上规则只针对多个索引列有效。如果有Column没有被索引,查询效率可能会因为你没有选择Or而降低。 在下面的例子中,Loc_id和Region上都建有索引。 高效: ```sql Select loc_id,loc_desc,region from location where loc_id = 10 union Select loc_id,loc_desc,region from location where region = ‘Melbourne’; ``` 低效: ```sql 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) 译者按: 下面的测试数据仅供参考:(a=1003返回一条记录,b=1返回1003条记录) SQL> select * from unionvsor 1st test 2 where a = 1003 or b = 1; 1003 rows selected. Execution Plan \------------------------------------------------------- 0 select statement optimizer=shoose 1 0 concatenation 2 1 table access (by index rowid) of ‘unionvsor’ 3 2 index (range scan) of ‘UB’ (non-unique) 4 1 table access (by index rowid) of ‘unionvsor’ 5 4 index (range scan) of ‘UA’ (non-unique) Statistics \------------------------------------------------------- 0 recursive calls 0 db block gets 144 consistent gets 0 physical reads 0 redo size 63749 bytes sent via SQL*Net to client 7751 bytes received via SQL* Net from client 68 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1003 rows processed SQL> select * from unionvsor /*2nd test*/ 2 where b = 1 or a = 1003; 1003 rows selected. Execution Plan \------------------------------------------------------- 0 select statement optimizer=shoose 1 0 concatenation 2 1 table access (by index rowid) of ‘unionvsor’ 3 2 index (range scan) of ‘UA’ (non-unique) 4 1 table access (by index rowid) of ‘unionvsor’ 5 4 index (range scan) of ‘UB’ (non-unique) Statistics \------------------------------------------------------- 0 recursive calls 0 db block gets 143 consistent gets 0 physical reads 0 redo size 63749 bytes sent via SQL*Net to client 7751 bytes received via SQL* Net from client 68 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1003 rows processed SQL> select * from unionvsor /*3rd test*/ 2 where a = 1003 3 union 4 select * from unionvsor 5 where b = 1; 1003 rows selected. Execution Plan \------------------------------------------------------ 0 select statement optimizer=choose 1 0 sort (unique) 2 1 union-all 3 2 table access (by index rowid) of ‘unionvsor’ 4 3 index (range scan) of ‘UA’ (non-unique) 5 2 table access (by index rowid) of ‘unionvsor’ 6 5 index (range scan) of ‘UB’ (non-unique) Statistics \------------------------------------------------------ 0 recursive calls 0 db block gets 10 consistent gets 0 physical reads 0 redo size 63735 bytes sent via SQL*Net to client 7751 bytes received via SQL*Net from client 68 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 1003 rows processed 用Union的效果可以从Consistent gets和SQL*Net的数据交换量的减少看出。 ### 三十七、用In来替换Or 下面的查询可以被更有效率的语句替换: 低效: ```sql Select … from location where loc_id = 10 or loc_id = 20 or loc_id = 30; ``` 高效: ```sql Select … from location where loc_id in (10,20,30); ``` 译者按:这是一条简单易记的规则,但是实际的执行效果还须检验,在Oracle8i下,两者的执行路径似乎是相同的。 ### 三十八、避免在索引列上使用Is null和Is not null 避免在索引中使用任何可以为空的列,Oracle将无法使用该索引。对于单列索引,如果列包含空值,索引中将不存在此记录。对于复合索引,如果每个列都为空,索引中同样不存在此记录。如果至少有一个列不为空,则记录存在于索引中。 例如: 如果唯一性索引建立在表的A列和B列上,并且表中存在一条记录的A,B值为(123,null),Oracle将不接受下一条具有相同A,B值(123,null)的记录(插入)。然而如果所有的索引列都为空,Oracle将认为整个键值为空而空不等于空。因此你可以插入1000条具有相同键值的记录,当然它们都是空! 因为空值不存在于索引列中,所以Where子句中对索引列进行空值比较将使Oracle停用该索引。 例如: 低效(索引失效) ```sql Select … from department where dept_code is not null; ``` 高效(索引有效) ```sql Select … from department where dept_code >=0; ``` ### 三十九、总是使用索引的第一个列 如果索引是建立在多个列上,只有在它的第一个列(leading column)被Where子句引用时,优化器才会选择使用该索引。 译者按:这也是一条简单而重要的规则。见以下实例。 SQL> create table multiindexusage (inda number,indb number,descr varchar2(10)); Table created. SQL> create index nultindex on multiindexusage (inda,indb); Index created. SQL> set autotrace traceonly SQL> select * from multiindexusage where inda = 1; Execution Plan \----------------------------------------------------------- 0 select statement optimizer=choose 1 0 table access (by index rowid) of ‘multiindexusage’ 2 1 index (range scan) of ‘multindex’ (non-unique) SQL> select * from multiindexusage where indb = 1; Execution Plan \----------------------------------------------------------- 0 select statement optimizer=choose 1 0 table access (full) of ‘multiindexusage’ 很明显,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引。 ### 四十、Oracle内部操作 当执行查询时,Oracle采用了内部的操作。下表显示了几种重要的内部操作。 | Oracle Clause | 内部操作 | | ------------------------------ | ---------------------------------- | | Order By | Sort order by | | Union | Union-all | | Minus | Minus | | Intersect | Intersect | | Distinct,Minus,Intersect,Union | Sort Unique | | Min,Max,Count | Sort Aggregate | | Group By | Sort Group By | | Rownum | Count or Count Stopkey | | Queries involving Joins | Sort Join,Merge Join,Nested Loops | | Connect By | Connect By | ### 四十一、用Union-all替换Union(如果有可能的话) 当SQL语句需要Union两个查询结果集合时,这两个结果集合会以Union-all的方式被合并,然后在输出最终结果前进行排序。 如果用Union all替代Union,这样排序就不是必要了。效率就会因此得到提高。 例如: 低效: ```sql Select acct_num,balance_amt from debit_transactions where tran_date = ’31-DEC-95’ union Select acct_num,balance_amt from debit_transactions where tran_date = ’31-DEC-95’ ``` 高效: ```sql Select acct_num,balance_amt from debit_transactions where tran_date = ’31-DEC-95’ union all Select acct_num,balance_amt from debit_transactions where tran_date = ’31-DEC-95’ ``` 译者按:需要注意的是,Union all将重复输出两个结果集合中相同记录。因此各位还是要从业务需求分析使用Union all的可行性。 Union将对结果集合排序,这个操作会使用到Sort_area_size这块内存。对于这块内存的优化也是相当重要的。下面的SQL可以用来查询排序的消耗量。 ```sql select substr(name, 1, 25) "Sort Area Name", substr(value, 1, 15) "Value" from v$sysstat where name like 'sort%' ``` ### 四十二、Oracle 9i启用Execution Plan的方法 主要有四步: Ø 创建表,通过utlxplan.sql脚本 SQL> @?/rdbms/admin/utlxplan.sql Ø 创建同义词,为了多个用户可以共享一个plan_table并授权给public SQL> create public synonym plan_table for plan_table; SQL> grant all on plan_table to public; Ø 创建plustrace角色 SQL> @?/sqlplus/admin/plustrce.sql执行这个脚本就可以了 Ø 将角色权限授予public SQL> grant plustrace to public; 完成以上几步就可以使用autotrace功能 Set timing on –显示执行时间 Set autotrace on –查看执行计划和查询信息 Set autotrace traceonly –只显示执行计划,但不显示查询输出 ### 四十三、使用提示(Hints) 对于表的访问,可以使用两种Hints:Full和Rowid。 Ø Full hint告诉Oracle使用全表扫描的方式访问指定表。 例如: ```sql Select */\*+full(emp)\*/** from emp where empno = 7893; ``` Ø Rowid hint告诉Oracle使用Table access by rowid的操作访问表。 通常你需要采用Table access by rowid的方式,特别是当访问大表的时候,使用这种方式,你需要知道Rowid的值或者使用索引。 如果一个大表没有被设定为缓存(Cached)表而你希望它的数据在查询结束时仍然停留在SGA中,你就可以使用Cache hint来告诉优化器把数据保留在SGA中。通常Cache hint和Full hint一起使用。 例如: select full(worker) cache(worker) from work; 索引hint告诉Oracle使用基于索引的扫描方式。你不必说明具体的索引名称。 例如: select index(lodging) lodging from lodging where manager = 'bill gates'; 在不使用hint的情况下,以上的查询应该也会使用索引,然而,如果该索引的重复值过多而你的优化器是CBO,优化器就可能忽略索引。在这种情况下,你可以用Index hint强制Oracle使用该索引。 Oracle hints还包括All_rows,First_rows,Rule,Use_NL,Use_merge,Use_hash等等。 译者按:使用Hint,表示我们对Oracle优化器缺省的执行路径不满意,需要手工修改。这是一个很有技巧性的工作。我建议只针对特定的,少数的SQL进行hint的优化。对Oracle的优化器还是要有信息(特别是CBO). ### 四十四、用Where替代Order by Order by子句只在两种严格的条件下使用索引。 Order by中所有的列必须饮食在相同的索引中并保持在索引中的排列顺序 Order by中所有的列必须定义为非空。 Where子句使用的索引和Order by子句中所使用的索引不能并列。 例如: 表Dept包含以下列: Dept_code PK Not Null Dept_desc Not Null Dept_type Null 非唯一性的索引(Dept_type) 低效(索引不被使用) ```sql Select dept_code from dept order by dept_type Explain Plan: Sort Order by Table access full ``` 高效(使用索引) ```sql Select dept_code from dept where dept_type>0 Explain Plan: Table access by rowid on emp Index range scan on dept_idx ``` 译者按:Order by也能使用索引!这的确是个容易被忽略的知识点。我们来验证一下: SQL> select * from emp order by empno; Execution Plan \------------------------------------------------ 0 select statement optimizer=choose 1 0 table access (by index rowid) of ‘emp’ 2 1 index (full scan) of ‘empno’ (unique) ### 四十五、避免改变索引列的类型 当比较不同数据类型的数据时,Oracle自动对列进行简单的类型转换。 假设Empno是一个数值类型的索引列。 ```sql Select … from emp where empno = ‘123’; --实际上,经过Oracel类型转换,语句转化为: Select … from emp where empno = to_number(‘123’) ``` 幸运的是,类型转换没有发生在索引列上,索引的用途没有被改变。 现在,假设EMP_TYPE是一个字符类型的索引列。 ```sql Select … from emp where emp_type=123 ``` 这个语句被Oracle转换为: ```sql Select … from emp where to_number(emp_type)=123 ``` 因为内部发生的类型转换,这个索引将不会被用到! 译者按:为了避免Oracle对你的SQL进行隐式的类型转换,最好把类型转换用显式表现出来。注意当字符和数值比较时,Oracle会优先转换数值类型到字符类型。 ### 四十六、需要当心的Where子句 某些Select语句中的Where子句不使用索引。这里有一些例子。 Ø 在下面的例子里,’!=’将不使用索引。记住,索引只能告诉你什么存在于表中,而不能告诉你什么不存在于表中。 不使用索引: ```sql Select account_name from transaction where amount !=0; ``` 使用索引: ```sql Select account_name from transaction where amount > 0; ``` Ø 下面的例子中,’||’是字符连接函数。就象其他函数那样,停用了索引。 不使用索引: ```sql Select account_name,amount from transaction where account_name||account_type = ‘Amexa’; ``` 使用索引: ```sql Select account_name,amount from transaction where account_name = ‘amex’ and account_type = ‘A’ ``` Ø 下面的例子中,’+’是数学函数。就象其他数学函数那样,停用了索引。 不使用索引: ```sql Select account_name,amount from transaction where amount + 3000 > 5000; ``` 使用索引: ```sql Select account_name,amount from transaction where amount > 2000; ``` Ø 下面的例子中,相同的索引列不能互相比较,这将会启用全表扫描。 不使用索引: ```sql Select account_name,amount from transaction where account_name = nvl(:acc_name,account_name); ``` 使用索引: ```sql Select account_name,amount from transaction where account_anme like nvl(:acc_name,’%’); ``` 译者按:如果一定要对使用函数的列启用索引,Oracle新的功能:基于函数的索引(Function-Based Index)也许是一个较好的方案。 Create index emp_i on emp (upper(ename)); /*建立基于函数的索引*/ Select * from emp where upper(ename) = ‘blacksnail’; /*将使用索引*/ ### 四十七、连接多个扫描 如果你对一个列和一组有限的值进行比较,优化器可能执行多次扫描并对结果进行合并连接。 例如: ```sql Select * from lodgingr where manage in (‘bill gates’,’ken muller’); ``` 优化器可能将它转换成以下形式 ```sql Select * from lodging where manager = ‘bill gates’ or manager = ‘ken muller’; ``` 当选择执行路径时,优化器可能对每个条件采用Lodging$Manager上的索引范围扫描。返回的Rowid用来访问Lodging表的记录(通过Table access by rowid的方式)。最后两组记录以连接(Concatenation)的形式被组合成一个单一的集合。 Explain Plan: Select statement optimizer=choose Concatenation Table access (by index rowid) of lodging Index (range scan) of lodging$Manager (non-unique) Table access (by index rowid) of lodging Index (range scan) of lodging$Manager (non-unique) 译者按:本节和第37节似乎有矛盾之处。 ### 四十八、CBO下使用更具选择性的索引 基于成本的优化器(CBO,Cost-Based Optimizer)对索引的选择性进行判断来决定索引的使用是否能提高效率。 如果索引有很高的选择性,那就是说对于每个不重复的索引键值,只对应数量很少的记录。 比如,表中共有100条记录而其中有80个不重复的索引键值。这个索引的选择性就是80/100=0.8。选择性越高,通过索引键值检索出的记录就越少。 如果索引的选择性很低,检索数据就需要大量的索引范围查询操作和Rowid访问表的操作。也许会比全表扫描的效率更低。 译者按: 下列经验请参阅: Ø 如果检索数据量超过30%的表中记录数。使用索引将没有显著的效率提高。 Ø 在特定情况下,使用索引也许会比全表扫描慢,但这是同一个数量级上的区别。而通常情况下,使用索引比全表扫描要快几倍乃至几千倍! ### 四十九、避免使用耗费资源的操作 带有Distinct,Union,Minus,Intersect,Order by的SQL语句会启动SQL引擎执行耗费资源的排序(Sort)功能。Distinct需要一次排序操作,而其他的至少需要执行两次排序。 例如,一个Union查询,其中每个查询都带有Group By子句,Group By会触发嵌入排序(Nested Sort);这样,每个查询需要执行一次排序,然后在执行Union时,又一个唯一排序(Sort Unique)操作被执行而且它只能在前面的嵌入排序结束后才能开始执行。嵌入的排序的深度会大大影响查询的效率。 通常还有Union,Minus,Intersect的SQL语句都可以用其他方式重写。 译者按:如果你的数据库的Sort_area_size调配得好,使用Union,Minus,Intersect也是可以考虑的,毕竟它们的可读性很强。 ### 五十、优化Group By 提高Group By语句的效率,可以通过将不需要的记录在Group by之前过滤掉。下面两个查询返回相同结果但第二个明显就快了许多。 低效: ```sql Select job,avg(sal) from emp group by job having job = ‘president’ or job = ‘manager’ ``` 高效: ```sql Select job,avg(sal) from emp where job = ‘president’ or job = ‘manager’ group by job; ``` 译者按:本节和14节相同。可略过。 ### 五十一、使用日期 使用日期时,需要注意如果有超过5位小数加到日期上,这个日期会进到下一天。 例如: ```sql Select to_date(’01-jan-93’+.99999) from dual; Returns:”01-jan-93 23:59:59” Select to_date(’01-jan-93’+.999999) from dual; ``` Returns:”02-jan-93 00:00:00” 译者按:虽然本节和SQL性能优化没有关系,但是作者的功力可见一般。 ### 五十二、使用显式的游标(Cursors) 使用隐式的游标将会执行两次操作。第一次检索记录,第二次检查Too Many Rows这个Exception。而显式游标不执行第二次操作。 ### 五十三、优化Export和Import 使用较大的Buffer(比如10MB,10240000)可以提高Export和Import的速度。 Oracle将尽可能地获取你所指定的内存大小,即使在内存不满足,也不会报错。这个值至少要和表中最大的列相当,否则列值会被截断。 译者按:可以肯定的是,增加Buffer会大提高Export,Import的效率。(曾经碰到过一个Case,增加Buffer后,Import/Export快了10倍!) 作者可能犯了一个错误:“这个值至少要和表中最大的列相当,否则列值会被截断”。其中最大的列也许是指最大的记录大小。 关于Export/Import的优化,CSDN论坛中有一些总结性的帖子,比如关于Buffer参数,Commit参数等等,详情请查。 分离表和索引 总是将你的表和索引建立在不同的表空间内(Tablespaces)。决不要将不属于Oracle内部系统的对象存放到System表空间里。同时,确保数据表空间和索引表空间置于不同的硬盘上。 译者按:“同时,确保数据表空间和索引表空间置于不同的硬盘上。”可能改为如下更为准确“同时,确保数据表空间和索引表空间置于不同的硬盘控制卡控制的硬盘上。”
点赞
已赞
上一篇:没有更多了
下一篇:我35岁,工作13年,明天退休
作者:
admin
(扫码联系作者)
发表时间:
2020-05-09 12:05:59
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
公众号转载:请在文末添加作者二维码
提交
分类
MORE
Java
8
数据库
4
前端技术
2
操作系统
2
开发运维
2
Spring
1
标签
MORE
Java
3
Web项目
3
Springboot
3
运维
2
Maven
1
Git
1
logback
1
Oracle
1
最新推荐
Oracle数据库开发常用操作
MySql数据库笔记
JAVA环境变量的配置
常用数据库连接信息
JavaScript正则表达式
CentOS 6下安装Apache反向代理
本站信息
文章总数:
20
篇
分类总数:
10
个
标签:
10
个
评论总数:
1
条
总访问量:
1776
次
今日访问量:
1
次
建站时长:
传送门
传送门
我已经为您精心准备了小惊喜,赶快出发吧 go go go......
公众号
欢迎关注我的公众号