PL/SQL优化案例之一

有个存储过程从周五晚上跑了到了周一尚未跑完,存储过程代码以下:正则表达式

TMP_NBR_NO_XXXX共有400w行数据,180MB。sql

For in 后面的查询 select nli.*, .......   and ns2l.nbr_level_id between 201 and 208 order by nl2i.priority; 查询返回43行数据。express

 

在优化sql的时候,咱们第一眼要找出可能出现性能的地方:oop

一、这个案例逻辑是比较复杂的,loop套loop,这就形成了 笛卡尔乘积,避免少扫描,优化逻辑。(可使用循环表的方式来减小loop循环次数)性能

二、where 条件过滤 是否走索引了,避免全表扫描,fetch

三、select * 要优化掉,避免查询数据字典优化

四、找到驱动表,尽可能小表在前(这里跟sql语句不同,CBO 会自动选择驱动表,可是在plsql里面loop嵌套loop ,逻辑都固定死了,哪一个在前哪一个就是驱动表)spa

五、内层循环为啥要排序?有必要吗?code

六、regexp_like 正则表达式不走索引,优化掉。(业务逻辑须要,这个不能优化)regexp

七、最内层的update 是否有索引?如没有,那么将会全表扫描不少次。

八、这里值得一提的是 开发写了批量提交,在大量DML操做的时候 这个是一个很好的方法。

九、是否收集了统计信息?(这个不必定,统计信息收集是要根据执行计划来判断的,有的sql收集了统计信息反而慢了)

有问题的地方我都圈起来了,以下:

嵌套循环就是一个loop套loop至关于笛卡尔积。该PLSQL代码中有loop套loop的状况,这就致使UPDATE TMP_NBR_NO_XXXX要执行400w*43次,TMP_NBR_NO_XXXX.no列没有索引,TMP_NBR_NO_XXXX每次更新都要进行全表扫描。这就是为何存储过程从周五跑到周一还没跑完的缘由。

有读者可能会问,为何不用MERGE进行改写呢?在PLSQL代码中是用regexp_like关联的.没法走hash链接,也没法走排序合并链接,两表只能走嵌套循环而且被驱动表没法走索引。若是强行使用MERGE进行改写,由于该SQL执行时间很长,会致使UNDO不释放,所以,没有采用MERGE INTO对代码进行改写。

    有读者可能也会问,为何不对TMP_NBR_NO_XXXX.no创建索引呢?由于关联更新能够采用ROWID批量更新,因此没有采用创建索引方法优化。

下面采用ROWID批量更新方法改写上面PLSQL,为了方便读者阅读PLSQL代码,先建立一个临时表用于存储43记录:

create table TMP_DATE_TEST as

  select  nli.expression, nl.nbr_level_id, priority   from tmp_xxx_item

  ...... and ns2l.nbr_level_id between 201 and 208;

 建立另一个临时表,用于存储要被更新的表的ROWID以及no字段:

create table TMP_NBR_NO_XXXX_TEXT as

select rowid rid, nbn.no from TMP_NBR_NO_XXXX nbn

 Where nbn.level_id=1 and length(nbn.no)= 8;  

 改写以后的PLSQL代码:

declare
  type rowid_table_type is table of rowid index by pls_integer;
  updateCur sys_refcursor;v_rowid rowid_table_type;
begin
  for c_no_data in (select t.expression, t.nbr_level_id, t.priority   from TMP_DATE_TEST t order by 3) loop
    open updateCur for  select rid   from TMP_NBR_NO_XXXX_TEXT nbn
       where regexp_like(nbn.no, c_no_data.expression);
    loop
      fetch updateCur bulk collect  into v_rowid LIMIT 20000;
      forall i in v_rowid.FIRST .. v_rowid.LAST
      update TMP_NBR_NO_XXXX  set level_id = c_no_data.nbr_level_id   
       where rowid = v_rowid(i); commit;
      exit when updateCur%notfound;
    end loop;
    CLOSE updateCur;
  end loop;
end;

循环嵌套的逻辑改为了循环表,循环表总比循环嵌套游标要好的多,改写后的PLSQL能在4小时左右跑完。有没有什么办法进一步优化呢?单个进程能在4小时左右跑完,若是开启8个并行进程,那应该能在30分钟左右跑完。可是PLSQL怎么开启并行呢?正常状况下PLSQL是没法开启并行的,若是直接在多个窗口中执行同一个PLSQL代码,会遇到锁争用,若是能解决锁争用,在多个窗口中执行同一个PLSQL代码,这样就变相实现了PLSQL开并行功能。能够利用ROWID切片变相实现并行:

select DBMS_ROWID.ROWID_CREATE(1,c.oid,e.RELATIVE_FNO,e.BLOCK_ID,0) minrid,
       DBMS_ROWID.ROWID_CREATE(1,c.oid,e.RELATIVE_FNO,e.BLOCK_ID+e.BLOCKS-1,     10000) maxrid from dba_extents e,
(select max(data_object_id)oid from dba_objects where object_name= 
'TMP_NBR_NO_XXXX_TEXT' and owner='RESXX2')and data_object_id is not null) c
 where e.segment_name='TMP_NBR_NO_XXXX_TEXT'and e.owner = 'RESXX2';

可是这时发现,切割出来的数据分布严重不均衡,这是由于建立表空间的时候没有指定uniform size 的Extent所致使的。因而新建一个表空间,指定采用uniform size方式管理Extent:

create tablespace TBS_BSS_FIXED datafile '/oradata/bs_bss_fixed_500.dbf' 
       size 500M extent management local uniform size 128k;

重建一个表用来存储要被更新的ROWID:

create table RID_TABLE
( rowno  NUMBER,  minrid VARCHAR2(18),  maxrid VARCHAR2(18)) ;

将ROWID插入到新表中:

insert into rid_table 
select rownum rowno, 
 DBMS_ROWID.ROWID_CREATE(1,c.oid,e.RELATIVE_FNO,e.BLOCK_ID, 0) minrid,
 DBMS_ROWID.ROWID_CREATE(1,c.oid,e.RELATIVE_FNO,e.BLOCK_ID + e.BLOCKS - 1, 10000) maxrid  from dba_extents e,
(select max(data_object_id)oid from dba_objects where object_name= 
'TMP_NBR_NO_XXXX_TEXT' and owner='RESXX2')and data_object_id is not null) c
 where e.segment_name='TMP_NBR_NO_XXXX_TEXT'and e.owner = 'RESXX2';

 这样RID_TABLE中每行指定的数据都很均衡,大概4035条数据。最终更改的PLSQL代码:

create or replace  procedure  pro_phone_grade(flag_num in number)
as 
 type rowid_table_type is table of  rowid index  by  pls_integer;  
 updateCur  sys_refcursor;v_rowid  rowid_table_type;
 v_rowid2  rowid_table_type;
begin
for  rowid_cur in (select  *  from  rid_table  where mod(rowno, 8)=flag_num
 loop
    for c_no_data in (select t.expression, t.nbr_level_id, t.priority  from TMP_DATE_TEST t order by 3 ) 
       loop
         open  updateCur  for  select rid,rowid  from TMP_NBR_NO_XXXX_TEXT  nbn
           where rowid between rowid_cur.minrid and rowid_cur.maxrid  
          and regexp_like(nbn.no, c_no_data.expression);
          loop
            fetch updateCur  bulk collect  into  v_rowid, v_rowid2  LIMIT 20000;
              forall i in v_rowid.FIRST ..v_rowid.LAST
              update TMP_NBR_NO_XXXX  set  level_id = c_no_data.nbr_level_id          
               where rowid = v_rowid(i);    commit;           
            exit when  updateCur%notfound;
         end loop; 
         CLOSE updateCur; 
       end loop;
   end loop;
end;

而后在8个窗口中同时运行上面PLSQL代码:

Begin  pro_phone_grade(0); end; ..... Begin pro_phone_grade(7); end;

最终能在29分左右跑完全部存储过程。本案例技巧就在于ROWID切片实现并行,而且考虑到了数据分布对并行的影响,其次还使用了ROWID关联更新技巧。

相关文章
相关标签/搜索