您的位置:首页 > 数据库 > Oracle

oracle sql优化

2013-09-14 23:47 197 查看

1. 选用适合的oracle 优化器

oracle 的优化器共有3种:a. rule (基于规则rbo) b. cost (基于成本cbo) 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优化器,为了避免那些不必要的全表扫描(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’;

上面的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 只在删除全表适用,即不可有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. 通过procedure、function、package提高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中不同的表具有相同的column名,当SQL语句中出现这个column时,SQL解析器无法判断这个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)

21. 用in替代 not in, 用exist替代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%’;

22. 用exists 替换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手册来了解具体的配置。)

25. 用explain 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)

注: 目前许多第三方的工具如toad和oracle本身提供的工具如oms的SQL analyze都提供了极其方便的explain plan 工具,也许喜欢图形化界面的朋友们可以选用它们。

26. 用索引提高效率

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

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

37. oracel 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,5) AS 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 … select…from的新用法

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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: