oracle优化学习笔记
2009-04-23 17:18
393 查看
关于in,exists,表连接
我在看oracle性能优化一书时,讲到用exists替代in,用表链接替代exists,关于前者,一般效果比较明显,exists效率明显比in高,但是如果要想表连接的效率比exists高,必须在from子句中,将记录多的表放在前面,记录少的表放后面.
假设数据表ta是数据量大的表(500万条),tb数据量较小的表(1万条),两表以id字段相关联,tb表的一条记录对应ta表的多条记录。现在统计时间2008.2.1以来ta表的记录数,由于只有tb表才有记录时间字段,故采用联合查询。
,却比方法2要低很多。后来,将from字句中表名位置换了一下,变成方法3b,意外的发现,效率远远高于方法2,相对方法1就更不用说了。
其它优化事项:
1. 带通配符(%)的like语句一般能用到索引,但当%出现在搜索词的最前面是,无法使用索引。
2. 在where语句中对列的任何操作(函数,计算,拼接等)都会使用该列上的索引失效。
3. is null或is not null 无法使用索引。
4. 任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。绝对避免在order by子句中使用表达式。
5. 使用and替代不等于(<>, !=),不等于操作无法使用索引:如 ......where seq<>3000; 应改写为:......where seq>3000 and seq<3000;
6. 应尽可能使用NOT EXISTS来代替NOT IN,尽管二者都使用了NOT(不能使用索引而降低速度),但NOT EXISTS要比NOT IN查询效率更高。
关于select/fetch... bulk collect into ... limit ...句型
在使用如上句型时,通常我们都会用for或forall循环批量进行insert/update/delete操作。for/forall循环方法有好几种,如 :
上面的第1种方法有一个致命的弱点,就是在select/fetch... bulk collect into ... limit ...没有取到数据时,如果没有exit,则第一种方法会报错:ORA-06502: PL/SQL: numeric or value error。因为tmp.FIRST访问的对象不存在,为空值。必须对错误进行错误处理。 第2种方法不会出现这种问题。
这种语法对海量数据的操作是极为有利的,比如下面就是一种使用绑定变量的批量删除数据的存储过程:
上面的1000条是一个可以设定的数,根据你的服务器性能可以扩大或缩小。
关于用exit when...跳出循环
记住,通常情况下,exit when... 只跳出当前层的循环,与其它程序设计语言的break语句类似。一直弄不明白为什么网上老是看到有人说“exit语句会退出整个pl/sql或所有循环,不知道如何跳出嵌套的循环”。在嵌套的循环中,如果要直接从内层循环跳出外面多层的循环,可使用'exit 标签 when...'形式的语句,举例如下:
符号<<标签名>>用来标识一个标签的开始。从上面可以看出,普通情况下,exit when... 只跳出当前层的循环。 用exit 标签 when...则跳出了外层的循环。
关于游标的属性
游标有四个属性%isopen,%found, %notfound, %rowcount.在普通的fetch...into...句型中,每次取单条记录,这四个属性均能正常工作,不会出现意外情况。但是若用在句型fetch...bulk collect into...limint...中,千万要注意,不要随便使用%found, %notfound, %rowcount三个属性。比如:
1. 使用sql%notfound;
2. 使用sql%rowcount=0;
3. 使用集合的 COUNT属性。
上面1,2两种方法就是直接把 exit when mycur%NOTFOUND 替换成 exit when SQL%NOTFOUND或 exit when SQL%ROWCOUNT=0,第3种方法就是将 exit when mycur%NOTFOUND 替换成 exit when rowIds.COUNT=0. 具体如下:
另外一个关于%rowcount的误解,我们看下面一段代码:
我在看oracle性能优化一书时,讲到用exists替代in,用表链接替代exists,关于前者,一般效果比较明显,exists效率明显比in高,但是如果要想表连接的效率比exists高,必须在from子句中,将记录多的表放在前面,记录少的表放后面.
假设数据表ta是数据量大的表(500万条),tb数据量较小的表(1万条),两表以id字段相关联,tb表的一条记录对应ta表的多条记录。现在统计时间2008.2.1以来ta表的记录数,由于只有tb表才有记录时间字段,故采用联合查询。
方法1,使用in: select count(*) from ta where id in( select id from tb where dt >'20080201'); 方法2,使用exists: select count(*) from ta where exists( select 'X' from tb where tb.id=ta.id and dt >'20080201'); 方法3a,使用表连接 select count(*) from tb, ta where tb.id=ta.id and dt >'20080201'; 方法3b,使用表连接 select count(*) from ta, tb where tb.id=ta.id and dt >'20080201';在实验过程中,方法2效率明显比方法1要高,但方法3a效率尽管比方法1要高
,却比方法2要低很多。后来,将from字句中表名位置换了一下,变成方法3b,意外的发现,效率远远高于方法2,相对方法1就更不用说了。
其它优化事项:
1. 带通配符(%)的like语句一般能用到索引,但当%出现在搜索词的最前面是,无法使用索引。
2. 在where语句中对列的任何操作(函数,计算,拼接等)都会使用该列上的索引失效。
3. is null或is not null 无法使用索引。
4. 任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。绝对避免在order by子句中使用表达式。
5. 使用and替代不等于(<>, !=),不等于操作无法使用索引:如 ......where seq<>3000; 应改写为:......where seq>3000 and seq<3000;
6. 应尽可能使用NOT EXISTS来代替NOT IN,尽管二者都使用了NOT(不能使用索引而降低速度),但NOT EXISTS要比NOT IN查询效率更高。
关于select/fetch... bulk collect into ... limit ...句型
在使用如上句型时,通常我们都会用for或forall循环批量进行insert/update/delete操作。for/forall循环方法有好几种,如 :
方法一: declare type p_tb_bat_dtl is table of tb_bat_dtl%rowtype; v_tb_bat_dtl p_tb_bat_dtl ; v_bat_dtl p_tb_bat_dtl ; cursor cur_tb_batch_dtl is select * from tb_bat_dtl; begin open cur_tb_batch_dtl; fetch cur_tb_batch_dtl bulk collect into v_tb_bat_dtl; for i in v_tb_bat_dtl.FIRST..v_tb_bat_dtl.LAST loop delete from tb_bat_dtl where bat_id=v_tb_bat_dtl(i).bat_id ; end loop; close cur_tb_batch_dtl; end; declare type p_bat_id is table of tb_bat_dtl.bat_id%type; v_bat_id p_bat_id ; cursor cur_tb_batch_dtl is select bat_id from tb_bat_dtl; begin open cur_tb_batch_dtl; fetch cur_tb_batch_dtl bulk collect into v_bat_id; forall i in v_bat_id.FIRST .. v_bat_id.LAST delete from tb_bat_dtl where bat_id=v_bat_id(i) ; close cur_tb_batch_dtl; end; 方法二: declare type p_tb_bat_dtl is table of tb_bat_dtl%rowtype; v_tb_bat_dtl p_tb_bat_dtl ; cursor cur_tb_batch_dtl is select * from tb_bat_dtl; begin open cur_tb_batch_dtl; fetch cur_tb_batch_dtl bulk collect into v_tb_bat_dtl; for i in 1..v_tb_bat_dtl.count loop delete from tb_bat_dtl where bat_id=v_tb_bat_dtl(i).bat_id ; end loop; close cur_tb_batch_dtl; end; declare type p_bat_id is table of tb_bat_dtl.bat_id%type; v_bat_id p_bat_id ; cursor cur_tb_batch_dtl is select bat_id from tb_bat_dtl; begin open cur_tb_batch_dtl; fetch cur_tb_batch_dtl bulk collect into v_bat_id; forall i in 1..v_bat_id.count delete from tb_bat_dtl where bat_id=v_bat_id(i) ; close cur_tb_batch_dtl; end;
上面的第1种方法有一个致命的弱点,就是在select/fetch... bulk collect into ... limit ...没有取到数据时,如果没有exit,则第一种方法会报错:ORA-06502: PL/SQL: numeric or value error。因为tmp.FIRST访问的对象不存在,为空值。必须对错误进行错误处理。 第2种方法不会出现这种问题。
这种语法对海量数据的操作是极为有利的,比如下面就是一种使用绑定变量的批量删除数据的存储过程:
PROCEDURE DelRrds AS type RowIdArray is table of rowid index by binary_integer; rowIds RowIdArray; BEGIN loop select rowid BULK COLLECT into rowIds from tb_bat_dtl where bat_id like '0625%' and rownum < 1001; exit when SQL%NOTFOUND; forall i in 1 .. rowIds.COUNT delete from tb_bat_dtl where rowid = rowIds(i); commit; end loop; EXCEPTION when OTHERS then rollback; END DelRrds;
上面的1000条是一个可以设定的数,根据你的服务器性能可以扩大或缩小。
关于用exit when...跳出循环
记住,通常情况下,exit when... 只跳出当前层的循环,与其它程序设计语言的break语句类似。一直弄不明白为什么网上老是看到有人说“exit语句会退出整个pl/sql或所有循环,不知道如何跳出嵌套的循环”。在嵌套的循环中,如果要直接从内层循环跳出外面多层的循环,可使用'exit 标签 when...'形式的语句,举例如下:
SQL> BEGIN 2 <<outerloop>> 3 FOR v_outerloopcounter IN 1..2 LOOP 4 <<innerloop>> 5 FOR v_innerloopcounter IN 1..4 LOOP 6 DBMS_OUTPUT.PUT_LINE('Outer Loop counter is ' 7 || v_outerloopcounter || 8 ' Inner Loop counter is ' || v_innerloopcounter); 9 EXIT WHEN v_innerloopcounter = 3; 10 END LOOP innerloop; 11 END LOOP outerloop; 12 END; 13 / Outer Loop counter is 1 Inner Loop counter is 1 Outer Loop counter is 1 Inner Loop counter is 2 Outer Loop counter is 1 Inner Loop counter is 3 Outer Loop counter is 2 Inner Loop counter is 1 Outer Loop counter is 2 Inner Loop counter is 2 Outer Loop counter is 2 Inner Loop counter is 3 PL/SQL procedure successfully completed. SQL> BEGIN 2 <<outerloop>> 3 FOR v_outerloopcounter IN 1..2 LOOP 4 <<innerloop>> 5 FOR v_innerloopcounter IN 1..4 LOOP 6 DBMS_OUTPUT.PUT_LINE('Outer Loop counter is ' 7 || v_outerloopcounter || 8 ' Inner Loop counter is ' || v_innerloopcounter); 9 EXIT outerloop WHEN v_innerloopcounter = 3; 10 END LOOP innerloop; 11 END LOOP outerloop; 12 END; 13 / Outer Loop counter is 1 Inner Loop counter is 1 Outer Loop counter is 1 Inner Loop counter is 2 Outer Loop counter is 1 Inner Loop counter is 3 PL/SQL procedure successfully completed.
符号<<标签名>>用来标识一个标签的开始。从上面可以看出,普通情况下,exit when... 只跳出当前层的循环。 用exit 标签 when...则跳出了外层的循环。
关于游标的属性
游标有四个属性%isopen,%found, %notfound, %rowcount.在普通的fetch...into...句型中,每次取单条记录,这四个属性均能正常工作,不会出现意外情况。但是若用在句型fetch...bulk collect into...limint...中,千万要注意,不要随便使用%found, %notfound, %rowcount三个属性。比如:
declare type RowIdArray is table of rowid index by binary_integer; rowIds RowIdArray; cursor mycur is select rowid from tb_bat_dtl; BEGIN open mycur; loop fetch mycur BULK COLLECT into rowIds limit 1000; exit when mycur%NOTFOUND; forall i in 1 .. rowIds.COUNT delete from tb_bat_dtl where rowid = rowIds(i); commit; end loop; close mycur; EXCEPTION when OTHERS then rollback; END ;上面的例子的意图是循环每次从游标中取1000行删除,如果没有取到数据,则跳出循环。但实际上,exit when mycur%NOTFOUND 无法达到目的。在fetch...bulk collect into...limint...句型中,只有fetch到的记录数大于等于limit设定的值时,游标属性%NOTFOUND才为false,属性%rowcount值才不为0。在上面的例子中,若数据库中符合条件的数据记录数值不能整除1000,那么余数不足1000条的数据永远无法删除。解决这种问题的方法有几种:
1. 使用sql%notfound;
2. 使用sql%rowcount=0;
3. 使用集合的 COUNT属性。
上面1,2两种方法就是直接把 exit when mycur%NOTFOUND 替换成 exit when SQL%NOTFOUND或 exit when SQL%ROWCOUNT=0,第3种方法就是将 exit when mycur%NOTFOUND 替换成 exit when rowIds.COUNT=0. 具体如下:
declare type RowIdArray is table of rowid index by binary_integer; rowIds RowIdArray; cursor mycur is select rowid from tb_bat_dtl; BEGIN open mycur; loop fetch mycur BULK COLLECT into rowIds limit 1000; exit when rowIds.COUNT=0; forall i in 1 .. rowIds.COUNT delete from tb_bat_dtl where rowid = rowIds(i); commit; end loop; close mycur; END ;
另外一个关于%rowcount的误解,我们看下面一段代码:
begin loop update table_tmp set name='nana' where sex_cd ='1' and rownum <10000; commit; exit when (SQL%ROWCOUNT =0 ); end loop; end;可能一些人认为上面的代码没有问题,能将表table_tmp中所有符合条件的记录全新更新掉。但实际上,这段代码只有在表table_tmp中符合条件的记录数小于10000时,才是正确的。 因为在执行commit语句后,游标 %rowcount 的值始终是0,无法起到判断的作用。实际上,执行commit后,所有的公用游标都会被初始化。 因此,必须在commit前使用游标属性。我们只需将commit语句的位置调整一下就可以了。
begin loop update table_tmp set name='nana' where sex_cd ='1' and rownum <10000; exit when (SQL%ROWCOUNT =0 ); commit; end loop; end;
相关文章推荐
- Oracle性能优化学习笔记之共享Sql语句
- Oracle性能优化学习笔记之WHERE子句中的连接顺序
- ORACLE学习笔记--性能优化6
- oracle 学习笔记——sql的优化
- Oracle性能优化顺序表名称来选择最有效的学习笔记
- ORACLE SQL性能优化(学习笔记)
- Oracle性能优化学习笔记之WHERE子句中的连接顺序
- ORACLE学习笔记--性能优化7
- Oracle 性能优化求生指南-学习笔记1
- Oracle性能优化的学习笔记
- Oracle性能优化的学习笔记
- Oracle性能优化学习笔记之选择最有效率的表名顺序
- ORACLE学习笔记--性能优化2
- 学习笔记:ORACLE 性能优化求生指南
- 【解剖】Oracle-----Oracle优化-学习笔记(1.1)
- Oracle性能优化学习笔记WHERE在连接顺序的条款
- Oracle性能优化学习笔记之选择最有效率的表名顺序
- Oracle性能优化学习笔记之共享Sql语句
- iOS学习笔记-044.UIScrollView分页加强——连续滚动优化
- 5.Oracle深度学习笔记——内存架构之PGA SQL Work 区