oracle sql优化

1. 选用适合的oracle 优化器

oracle 的优化器共有3a. rule (基于规则rbo) b. cost (基于成本cbo) c. choose (选择性)设置缺省的优化器可以通过对init.ora文件中optimizer_mode参数的各种声明rulecostchooseall_rowsfirst_rows 你当然也在SQL句级或是会话(session)级对其进行覆盖为了使用基于成本的优化器(cbo, cost-based optimizer)你必须经常运行analyze 命令以增加数据库中的对象统计信息(object statistics)的准确性如果数据库的优化器模式设置为选择性(choose)那么实际的优化器模式将和是否运行过analyze 命令有关如果table已经被analyze 优化器模式将自动成为cbo反之数据库将采用rule形式的优化器在缺省情况下oracle采用choose优化器为了避免那些不必要的全表扫描(fulltable scan),你必须尽量避免使用choose优化器而直接采用基于规则或者基于成本的优化器

( nick_huo:SQL server则提供: update  statistics  cust  with fullscan  更新数据库中的对象统计信息object statistics

Oracle则提供:analyze  table  report.rpt_sum_class_item compute statistics; &  analyze  table report.rpt_sum_type validate structure; login user必须是table owner 或有analyze  table权限 )。

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 语句能否在这两个用户之间共享

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是表的所有者,对象不同。

c. 两个SQL语句中必须使用相同的名字的绑定变量(bind variables)

例如:

第一组的两个SQL语句是相同的(可以共享)而第二组中的两个语句是不同的(即使在运行时赋于不同的绑定变量相同的值)

1.

select pin , name from people where pin = blk1.pin

select pin , name from people where pin = blk1.pin

2

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.cat_no=c.cat_no    and e.locn=l.locn  and  e.emp_no  between  1000  and  2000 高效

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子句的末尾

(nick_huo:原则--可以过滤掉最大数量记录、限制性最大的条件最早让解析器读到。ibm db2采用自上而下的顺序解析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 子句中避免使用  * 

1、 当你想在select子句中列出所有的column使用动态SQL列引用*是一个方便的方法这是一个非常低效的方法oracle 在解析的过程中会将*依次转换成所有的列名这个工作是通过查询数据字典(sys.dba_tab_columns)完成的这意味着将耗费更多的时间使用最小column 原则。即使是必须使用全部column,应列出column_name,不要使用“*”,省去oracle查系统字典sys.dba_tab_columns把“*”替换成sys_user的全部column_name

select  owner, table_name  from  dba_all_tables  
where table_name in ( select  table_name  from  dba_tab_columns  where      column_name ='PROD_CAT_ID')  and  owner='IBSS';

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

(nick_huo:改为select  emp_name ,  salary ,  grade  from   emp   where  emp_no = 342 or  emp_no = 291;)。

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 子句中

 

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;    

上面的个查询可以被合并成一个

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 只在删除全表适用,即不可有where子句。 truncate table emp;truncate ddl 不是dml, oracle中ddl是自动commit。)

 

12. 尽量多使用commit

只要有可能在程序中尽量多使用commit这样程序的性能得到提高需求也会因commit所释放的资源而减少

commit 所释放的资源

a. 回滚段上用于恢复数据的信息

b. 被程序语句获得的锁。

c. redo log buffer 中的空间。

d. oracle 为管理上述3种资源中的内部花费。

(nick_huo:第一时间commit可以优化rollback segments的多用户共享。)

13. 计算记录条数

和一般的观点相反count(*)count(1)稍快,当然如果可以通过索引检索对索引列的计数仍旧是最快的eg: create nonclustered index emp_id_index on emp(emp_id);

则:select count (emp_id) from emp; 最快的。

14. where 子句替换having 子句

避免使用having子句having只会在检索出所有记录之后才对结果集进行过滤这个处理需要排序总计等操作如果能通过where子句限制记录的数目那就能减少这方面的开销

例如

低效:(因为检索出location所有记录,然后按region分组,having对分组结果集进行过滤去掉相应的分组结果。做多左无用功。

select  region,avg(log_size)  

from location  

group by region  

having region region != ‘sydney’  

and region != ‘perth’;    

高效:(因为where子句检索出location合适的记录,然后按region分组即可,不需使用having子句)。

select  region,avg(log_size)  

from location  

where  region region != ‘sydney’  

and  region != ‘perth’  

group by region;    

(译者按: having 中的条件一般用于对一些集合函数的比较,count() 等等除此而外一般的条件尽可能写在where子句中)

15. 减少对表的查询

在含有子查询的SQL语句中要特别注意减少对表的查询

例如

低效:(因为子查询查询dba_tab_columns两次

select table_name from dba_all_tables

where table_name=(select table_name  from  dba_tab_columns  where  version = 604)

and  db_ver= ( select db_ver   from  dba_tab_columns  where version= 604)

高效:(使用oracle扩展的多列子查询特性,只需查询dba_tab_columns一次

select table_name from dba_all_tables where (table_name,db_ver)= ( select table_name, db_ver  from  dba_tab_columns  where  version = 604)

update 多个column 例子:(原因同上)

低效:

update emp 

set emp_cat=(select  max(category)  from  emp_categories),

sal_range=(select  max(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. 通过procedurefunctionpackage提高SQL效率.

 (译者按经常在论坛中看到如能不能用一个SQL写出…’的贴子,复杂的SQL往往牺牲了执行效率。使用SQL应充分利用其4gl特性,但不是完全放弃其3gl特性,在pl/SQL中3gl&4gl相容的。通过procedure、function、package,充分发挥pl/SQL的3gl&4gl特性在实际工作中是非常有意义的。当然,在实际中3gl&4gl也要相互衡量著使用,例如能够用动态表(4GL特性)实现暂存临时数据的,就不要用中间表(3GL特性,以过程方式实现),因为与动态表相比,确实多了create & 管理中间表和向中间表insert data的过程)

 

17. 使用表的别名(alias)

当在SQL语句中连接多个表时请使用表的别名并用别名引领每个column这样可以减少解析的时间,减少由于column歧义引起的语法错误和使SQL具有良好一致性(假设原来查询tb_cm_serv_char0,现在改为查询tb_cm_serv_char1。使用表的别名并用别名引领每个column,则只需把from子句中的tb_cm_serv_char0改为tb_cm_serv_char1即可,不必逐一修正column list中每个相关的column)

(译者注: column歧义指的是由于SQL中不同的表具有相同的columnSQL语句中出现这个columnSQL解析器无法判断这个column的归属)

18. exists 替代in

在许多基于基础表的查询中为了满足一个条件(通常是foreign key)往往需要对另一个表进行联接在这种情况下使用exists(not exists)通常将提高查询的效率

低效:(原因是使用in处理子查询)

select cust_id,  cust_name,  cust_desc from cust  where area_id in (select area_id  from area  where area_name like 大中华%) and cust_id like kit%

 

高效:(使用相关子查询和exists替代in

select cust_id,  cust_name,  cust_desc from cust  where exists ( select 1 from area where cust.area_id=area.area_id and area.area_name like 大中华% ) and  cust_id like kit%

高效:(使用table join 替代in子查询)

select tc.cust_id, tc.cust_name, tc.cust_desc from cust tc inner join (select area_id  from area where  area_name like 大中华%) ta on tc.area_id=ta.area_id and tc.cust_id like kit%

 

select  owner, table_name  from  dba_all_tables  
where table_name in ( select  table_name  from  dba_tab_columns  where  column_name ='PROD_CAT_ID')  and  owner='IBSS';

 

select  t1.owner, t1.table_name  from  dba_all_tables t1
where  exists  ( select 'a' from dba_tab_columns t2 
                 where  t1.table_name=t2.table_name 
                    and   column_name ='PROD_CAT_ID' )
   and t1.owner='IBSS';

 

select  t1.owner,t1.table_name from dba_all_tables t1 

inner join

select  distinct  table_name from dba_tab_columns  where column_name ='PROD_CAT_ID') t1 

on t1.table_name=t2.table_name

where t1.owner='IBSS';

由于动态表t1的查询结果重复,必须用distinct,否则查询结果不对,看起来inner join失效似的.

(译者按相对来说,not exists替换not in 将更显著地提高效率,下一节中将指出)

19. 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(+)  and  b.dept_no is null  and b.dept_cat(+)=a;(sample1)

select  a.*  from emp a  outer join  dept b  on  a.dept_no=b.dept_no 

where b.dept_no is null and b.dept_cat=a; (sample2)

select  a.*  from emp a  right outer join  (select dept_no from  dept  where dept_cat=a) b  on  a.dept_no=b.dept_no  where b.dept_no is null ; (sample3)

 

(方法二最高效)

select *  from emp  e  where  not  exists( select 1 from dept  d  where  d.dept_no=e.dept_no  and  d.dept_cat=’a);

 

20. 用表连接替换exists

通常来说,采用表连接的方式比exists 更有效率。

高效:(使用exists)

select cust_id,  cust_name,  cust_desc from cust  where exists ( select 1 from area where cust.area_id=area.area_id and area.area_name like 大中华% ) and  cust_id like kit%

更高效:(使用table join 替代exists)

select tc.cust_id, tc.cust_name, tc.cust_desc from cust tc inner join (select area_id  from area where  area_name like 大中华%) ta on tc.area_id=ta.area_id and tc.cust_id like kit%

(译者按rbo 的情况下,前者的执行路径包括filter,后者使用nested loop)

21in替代 not inexist替代in, table join 替代exits

最开始的需求:

select cust_id,  cust_name,  cust_desc from cust  where area_id  not  in (select area_id  from area  where area_name like 大中华%)  and cust_id like kit%

in 替代 not in

select cust_id,  cust_name,  cust_desc from cust  where area_id  in (select area_id  from area  where area_name   not  like 大中华%)  and cust_id like kit%

 

exists 替代in:

select cust_id,  cust_name,  cust_desc from cust  where exists ( select 1 from area where cust.area_id=area.area_id and area.area_name not like 大中华% ) and  cust_id like kit%

table join 替代exits:

select tc.cust_id, tc.cust_name, tc.cust_desc from cust tc 

inner join (select area_id  from area where  area_name not like 大中华%) ta 

on tc.area_id=ta.area_id and tc.cust_id like kit%

 

22exists 替换distinct

当提交一个包含一对多表信息(比如部门表和雇员表)的查询时避免在select子句中使用distinct。一般可以考虑用existe替换。

例如

低效

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  1  from  emp  e  where  d.deppt_no=e.dept_no);

exists使查询更为迅速因为rdbms核心模块将在子查询的条件一旦满足后立刻返回结果

23. 识别低效执行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 工具来

解决问题始终是一个最好的方法)

24. 使用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手册来了解具体的配置)

25explain plan 分析SQL 语句

explain plan是一个很好的分析SQL语句的工具它甚至可以在不执行SQL的情况下分析语句通过分析我们就可以知道oracle是怎么样连接表使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称你需要按照从里到外,从上到下的次序解读分析的结果 explain plan分析的结果是用缩进的格式排列的最内部的操作将被最先解读如果两个操作处于同一层中带有最小操作号的将被首先执行nested loop是少数不按照上述规则处理的操作正确的执行路径是检查对nestedloop 提供数据的操作其中操作号最小的将被最先处理

译者按:通过实践感到还是用SQLplus中的set trace 功能比较方便.

举例:

SQL> list

1 select *

2 from dept, emp

3* where emp.deptno = dept.deptno

SQL> set autotrace traceonly /*traceonly 可以不显示执行结果*/

SQL> /

14 rows selected.

execution plan

----------------------------------------------------------

0 select statement optimizer=choose

1 0 nested loops

2 1 table access (full) of 'emp'

3 1 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)

目前许多第三方的工具如toadoracle本身提供的工具如omsSQL analyze都提供了极其方便的explain plan 工具也许喜欢图形化界面的朋友们可以选用它们

26用索引提高效率

索引是表的一个概念部分用来提高检索数据的效率实际上oracle 使用了一个复杂

的自平衡b-tree 结构通常通过索引查询数据比全表扫描要快oracle 找出执行查询和update语句的最佳路径时oracle优化器将使用索引同样在联结多个表时使用索引也可以提高效率(索引必须是建立在table join时用到的pk&fk上)另一个使用索引的好处是它提供了主键(primary key)的唯一性验证除了那些longlong raw数据类型你可以索引几乎所有的列通常在大型表中使用索引特别有效当然你也会发现在扫描小表时使用索引同样能提高效率虽然使用索引能得到查询效率的提高但是我们也必须注意到它的代价索引需要空间来存储也需要定期维护每当有记录在表中增减或索引列被修改时索引本身也会被修改这意味着每条记录的insertdeleteupdate将为此多付出45次的磁盘i/o因为索引需要额外的存储空间和处理那些不必要的索引反而会使查询反应时间变慢

译者按:定期的重构索引是有必要的

alter index <indexname> rebuild <tablespacename>

27索引的操作

oracle 对索引有两种访问模式:

第一:索引唯一扫描 (index unique sacn)大多数情况下优化器通过where子句访问index

例如

表lodging有两个索引:建立在lodging列上的唯一性索引lodging_pk和建立在manager列上的非唯一性索引lodging$manager。

select  *  from  lodging   

where  lodging=rose;

在内部,上述SQL将被分成两步执行首先, lodging_pk索引将通过索引唯一扫描的方式被访问,获得相对应的rowid通过rowid访问表的方式执行下一步检索如果被检索返回的列包括在index列中oracle 将不执行第二步的处理(通过rowid访问表) 因为检索数据保存在索引中单单访问索引就可以完全满足查询结果(index覆盖的作用)

下面SQL只需要index unique scan 操作

select   lodging  from  lodging  

where  lodging=rose;

第二:索引范围查询(index range scan)适用于两种情况:

1. 基于一个范围的检索

2. 基于非唯一性索引的检索

1

select   lodging  from  lodging  

where  lodging  like  rose%;

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访问表的操作

select   lodging  from  lodging  

where  manager  likr  %gates

where子句中如果索引列所对应的值的第一个字符由通配符(wildcard)开始索引将不被采用,在这种情况下,oracle 将使用全表扫描(nick_huo: where子句避免 where subsrt(manager,1,3)=kit,将导致manager列上的lodging$manager索引在查询中失效,应改为where manager  like =kit%)。

 

28基础表的选择

基础表(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表的lodging列上有一个索引而且worker表中没有相比较的索引,worker表将被作为查询中的基础表

30. 多个平等的索引

SQL 语句的执行路径可以使用分布在多个表上的多个索引时oracle 会同时使用多个索引并在运行时对它们的记录进行合并检索出仅对全部索引有效的记录oracle 选择执行路径时唯一性索引的等级高于非唯一性索引然而这个规则只有当where子句中索引列和常量比较才有效如果索引列和其他表的索引类相比较这种子句在优化器中的等级是非常低的如果不同表中两个想同等级的索引将被引用,,from子句中表的顺序将决定哪个会被率先使用。from子句中最后的表的索引将有最高的优先级如果相同表中两个想同等级的索引将被引用,where子句中最先被引用的索引将有最高的优先级

举例

dept_no上有一个非唯一性索引,emp_cat也有一个非唯一性索引

select  ename,  from emp  

where  dept_no = 20  and  emp_cat = ‘a’;    

dept_no索引将被最先检索然后同emp_cat索引检索出的记录进行合并执行路径如下

table  access  by  rowid  on  emp  and-equal  

index range scan on dept_idx  

index range scan on cat_idx 

   

31. 等式比较和范围比较

where子句中有索引列oracle 不能合并它们oracle 将用范围比较举例:dept_no上有一个非唯一性索引emp_cat也有一个非唯一性索引

select  ename  from  emp  

where  dept_no > 20  and  emp_cat = ‘a’

这里只有emp_cat索引被用到然后所有的记录将逐条与dept_no条件进行比较执行路径:

table  access  by  rowed  on  emp  

index  range  scan  on  cat_idx 

32不明确的索引等级

oracle 无法判断索引的等级高低差别优化器将只使用一个索引它就是在where子句中被列在最前面的举例:dept_no上有一个非唯一性索引emp_cat也有一个非唯一性索引

select  ename  from  emp  

where  dept_no > 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 是唯一性索引,但是由于它所做的是范围比较等级要比非唯一性索引的等式比较低!

33. 强制索引失效

如果两个或以上索引具有相同的等级你可以强制命令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上没有索引

select  ename  from  emp  

where  emp_type = ‘a’  and  emp_class = ‘x’;    

优化器会注意到emp_type上的索引并使用它这是目前唯一的选择如果一段时间以后另一个非唯一性建立在emp_class优化器必须对两个索引进行选择在通常情况下优化器将使用两个索引并在他们的结果集合上执行排序及合并然而如果其中一个索引(emp_type)接近于唯一性而另一个索引(emp_class)上有几千个重复的值将导致排序及合并就会成为一种不必要的负担在这种情况下你希望使优化器屏蔽掉emp_class索引

用下面的方案就可以解决问题

select  ename  from  emp  

where  emp_type = ‘a’  and  emp_class ||’’ = ‘x’

34避免在索引列上使用计算

select   lodging  from  lodging  

where  manager  likr  %gates

where子句中如果索引列所对应的值的第一个字符由通配符(wildcard)开始索引将不被采用,在这种情况下,oracle 将使用全表扫描(nick_huo: where子句避免 where subsrt(manager,1,3)=kit,将导致manager列上的lodging$manager索引在查询中失效,应改为where manager  like =kit%)。where子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描。

译者按:

这是一个非常实用的规则,请务必牢记

 

35. 自动选择索引

如果表中有两个以上(包括两个)索引,其中有一个唯一性索引,而其他是非唯一性。在这种情况下,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 

36>=替代>

如果deptno有一个索引,

高效

select *  from  emp  where deptno>=4;

低效

select *  from  emp  where deptno>3;

两者的区别在于前者dbms将直接跳到第一个deptno等于4的记录而后者将首先定位到deptno=3的记录并且向前扫描到第一个deptno>3的记录

37oracel SQL优化一招

ibss黄伟恩

tb_cm_serv 表关联查询注意事项:现在发现 tb_cm_serv 表的联合索引

ix_cm_serv_union1   serv_id,prod_id 对查询的性能造成影响。当多表查询中包含 tb_cm_serv 表,并且在传入条件中包含 serv_id 和 prod_id时,请不要直接传入。如

select a.acc_nbr

    from tb_cm_serv a, tb_cm_serv_relat b

    where a.serv_id = b.serv_id2

      and b.serv_id = 1083320

      and a.prod_id = 47

因为这样会触发 ix_cm_serv_union1   serv_id,prod_id。 使查询变慢。

可以利用exists 隐藏 prod_id 或者利用prod_id+0表达式来避开这个索引,使数据库直接使用 pk_cm_serv索引加快查询速度。如:

select a.acc_nbr

    from tb_cm_serv a, tb_cm_serv_relat b

    where a.serv_id = b.serv_id2

      and b.serv_id = 1083320

      and a.prod_id+0 = 47

或者

select a.acc_nbr

    from tb_cm_serv a, tb_cm_serv_relat b

    where a.serv_id = b.serv_id2

      and b.serv_id = 1083320

and exists(select 1 from tb_cm_serv where prod_id = 47 and serv_id=a.serv_id)

 

优化前执行 134.375 secs(因为tb_cm_serv 表的联合索引 ix_cm_serv_union1   serv_id,prod_id 对查询的性能造成影响),优化后执行 0.078 secs。

优化前SQL:

select acc_nbr

    from tb_cm_serv

    where prod_id = 47

      and serv_id = 1083320

union all

select a.acc_nbr

    from tb_cm_serv a, tb_cm_serv_relat b

    where a.serv_id = b.serv_id2

      and b.serv_id = 1083320

      and a.prod_id = 47

优化后SQL:

select acc_nbr

    from tb_cm_serv

    where prod_id+0 = 47

      and serv_id = 1083320

union all

select a.acc_nbr

    from tb_cm_serv a, tb_cm_serv_relat b

    where a.serv_id = b.serv_id2 

     and a.prod_id+0 = 47

     and b.serv_id = 1083320;

39. ANALYZE statement & ANALYZE triggers

ANALYZE triggers fire whenever an ANALYZE statement is executed to collect or delete optimizer statistics, validates the structure of a database object, or list chained rows into a chained_row table. These triggers can be either on the entire database or on an individual schema

 

CREATE OR REPLACE TRIGGER Restrict_analyze_Statements

BEFORE ANALYZE ON DATABASE

DECLARE

   sql_stmt        VARCHAR2(32700);

   sql_lines       BINARY_INTEGER;

   triggering_sql  dbms_standard.ora_name_list_t;

   msg             VARCHAR2(255);

   NewLine         VARCHAR2(2) := CHR(13);

BEGIN

   -- get the triggering SQL

   sql_lines := sql_txt(triggering_sql);

   FOR loop_counter IN triggering_sql.FIRST..triggering_sql.LAST

   LOOP

      sql_stmt := sql_stmt||triggering_sql(loop_counter)||NewLine;

   END LOOP;

   IF (regexp_instr(sql_stmt,'validate[[:space:]]+structure',modifier=>'i')

      +regexp_instr(sql_stmt,'list[[:space:]]+chained[[:space:]]+rows' ,modifier=>'i') ) = 0 THEN

      -- This analyze statement is trying to manage optimizer statistics

      -- which should be done via the DBMS_STATS package.

      -- raise an exception with instructions for help

      msg := 'ANALYZE shouldn''t be used for managing optimizer statistics.  ';

      msg := msg||'DBMS_STATS should be used instead.  ';

      msg := msg||'Contact Sally DBA if you need assistance with the DBMS_STATS package';

      RAISE_APPLICATION_ERROR(-20010,msg);

   END IF;

END;

 

analyze table REPORT.RPT_SUM_CLASS_ITEM compute statistics;
analyze  table REPORT.RPT_SUM_TYPE validate structure;

login user必须是table owner 或有analyze  table权限

40. group by子句中使用函数

select substr(table_name,1,5) ,count(*) from dba_all_tables 
group by substr(table_name,1,5)
having count(*)=37;(oracle sample)

select substring(au_fname,1,1)as ta,count(*) as tn from dbo.authors

group by substring(au_fname,1,1)

having count(*)>=2 (sql server 2k sample)

group by子句中直接使用函数(eg上例)oracle & sql server 2k是支持的,但在IBM DB2中不行。可改成:

select t1.tname,count(*) 

from (select substr(table_name,1,5AS tname  from dba_all_tables) t1
group by t1.tname having count(*)=37;

41. 减少查询次数

减少查询次数一般是用CURSOR或者select dict_id bulk collect into vcSpeed_dict_id from 块读技术。详见《大数据量的TABLE横向扩宽的解决方案

1、 CURSOR

declare cursor custCursor is select c.cust_id,c.cust_name,c.area_id,a.area_name from cust c left join  area a on c.area_id=a.area_id  where c.cust_cat_id=01;

CURSOR的使用详见附件文档。CURSOR适用于比较大的数据量遍历。但不要使用CURSOR里面再定义CURSOR,用类似FOR循环嵌套来遍历两个相关的CURSOR

2、 块读技术

select dict_id,dict_name bulk collect  into vcSpeed_dict_id,vcSpeed_value_in_Dict 

     from summary.rpt_gzdata_speed_dict

where dict_typeid='PM_ADSLSL' and dict_id <>'50' and dict_status='S0A';  

 

for i in 1..vcSpeed_value_in_Dict.count loop

vcSpeed_Unit:=substr(vcSpeed_value_in_Dict(i),length( RTRIM(vcSpeed_value_in_Dict(i),' ')),1);    

vcSpeed_value_temp:=translate(vcSpeed_value_in_Dict(i),'ADSL',' ');  'ADSL'替换成空格

vnSpeed_value_in_Temp:=to_number(substr(vcSpeed_value_temp,1,length( RTRIM(vcSpeed_value_temp,' '))-1));

end loop;

end;  

 

select dict_id bulk collect into vcSpeed_dict_id from 块读技术+FOR循环适用于数据量比较小的遍历。不要使用FOR循环嵌套来遍历数据。Eg:效率会很差。

for i in 1.. vcSpeed_dict_id.count loop

for j in 1..vcSpeed_value_in_Dict.count loop

end loop;

end loop;

 

43. insert into  selectfrom的新用法

insert into summary.rpt_gzdata_comm_all_serv(serv_id,prod_id,prod_cat_id,serv_stat_id,create_date,

pm_passnettype,pm_linelevel,speed_attr_id,pm_speed_dict_name,speed_value_in_sum)

select  serv.serv_id,serv.prod_id,serv.prod_cat_id,serv.serv_stat_id,serv.create_date,

     (select pm_passnettype from summary.rpt_gzdata_pm_passnettype where serv_id=serv.serv_id) pm_passnettype,

     (select pm_linelevel from summary.rpt_gzdata_pm_linelevel where serv_id=serv.serv_id) pm_linelevel,

     (select speed_attr_id from summary.rpt_gzdata_pm_speed where serv_id=serv.serv_id ) speed_attr_id,

     (select pm_speed_dict_name from summary.rpt_gzdata_pm_speed where serv_id=serv.serv_id ) pm_speed_dict_name,

 (select speed_value_in_sum from summary.rpt_gzdata_pm_speed where serv_id=serv.serv_id ) speed_value_in_sum     

    from    

        summary.rpt_gzdata_comm_cm_serv serv

oracle9i & SQL SERVER2k 均支持该特性。

 

44. update 的新用法

oracle9i & SQL SERVER2k 均支持该特性。

修改summary.rpt_gzdata_comm_all_serv中全部REC

update summary.rpt_gzdata_comm_all_serv serv
set serv.pm_passnettype=(select pm_passnettype from summary.rpt_gzdata_pm_passnettype where serv_id=serv.serv_id), 
serv.pm_linelevel=(select pm_linelevel from summary.rpt_gzdata_pm_linelevel where serv_id=serv.serv_id), 
serv.speed_value_in_sum=(select speed_value_in_sum from summary.rpt_gzdata_pm_speed where serv_id=serv.serv_id )

 

修改summary.rpt_gzdata_comm_all_serv中指定的REC

update summary.rpt_gzdata_comm_all_serv serv
set serv.pm_passnettype=(select pm_passnettype from summary.rpt_gzdata_pm_passnettype where serv_id=serv.serv_id), 
serv.pm_linelevel=(select pm_linelevel from summary.rpt_gzdata_pm_linelevel where serv_id=serv.serv_id), 
serv.speed_value_in_sum=(select speed_value_in_sum from summary.rpt_gzdata_pm_speed where serv_id=serv.serv_id )

where serv.serv_id>= 6916931

 

该技术类似于SQL SERVER:

update summary.rpt_gzdata_comm_all_serv  set  serv.pm_passnettype=P. pm_passnettype 

from summary.rpt_gzdata_comm_all_serv serv, summary.rpt_gzdata_pm_passnettype p

   where serv.serv_id=p.serv_id

 

update  cust set area_id=ca.area_id from cust,cust_area ca where cust.cust_id=ca.cust_id

update  cust set area_id=ca.area_id from cust inner join cust_area ca on cust.cust_id=ca.cust_id

update  cust set area_id=ca.area_id from cust left join cust_area ca on cust.cust_id=ca.cust_id

update cust set area_id=(select area_id from cust_area ca  where cust_id=cust.cust_id)

 

insert  into cust1 (cust_id,cust_name,cust_val,area_id)

  select cust_id,cust_name,cust_val,

(select  area_id  from  cust_area  where cust_id=cust.cust_id) as area_id

from cust

 

 

 

 

45. union all 替代or来改写SQL优化性能

select * from sys_user 

where user_code = 'zhangyong' 

or  user_code  in  (select grp_code  from sys_grp where sys_grp.user_code = 'zhangyong')

sys_user 和sys_grp 的数据量

SQL> select count(*) from sys_user;
COUNT(*)

----------

15190

SQL> select count(*) from sys_grp;

COUNT(*)

----------

25130

例子中有30590 consistent gets逻辑读。

eygle优化:降低逻辑读是优化SQL的基本原则之一。例子中有30590 consistent gets逻辑读。我们尝试通过降低逻辑读来加快SQL的执行。这里我们用union all 替代or来改写SQL

select * from sys_user where user_code = 'zhangyong' 

union all

select * from sys_user where user_code <> 'zhangyong' 

and user_code in (select grp_code from sys_grp where sys_grp.user_code = 'zhangyong')

Nick_huo优化:

2、 select * from sys_user,使用最小column 原则。即使是必须使用全部column,应列出column_name,不要使用“*”,省去oracle查系统字典sys.dba_tab_columns把“*”替换成sys_user的全部column_name

3、 union all 替代or来改写SQL

4、 create  index  pk_ sys_user_index  on  sys_user(user_code)

5、 create  indesx  pk_sys_grp_index  on  sys_grp(grp_cpde,user_code)

 

46. group by 替代 distinct

select distinct serv_id from summary.rpt_data_comm._ba_prod_attr  where  attr_id=319

select serv_id from summary.rpt_data_comm._ba_prod_attr where attr_id=319 group by serv_id

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值