Oracle索引“破解”

时间:2011-12-20 09:28:21

标签: oracle plsql indexing oracle11g

我正在开发一个数据仓库项目,因此,我一直在实现包中的一些ETL函数。我第一次在开发笔记本电脑时遇到了问题,并认为它与我的oracle安装有关,但现在已经“扩散”到生产服务器上。 两个功能“有时”变得令人难以置信的缓慢。我们已经实现了一个日志系统,在每个x行的日志表上提供输出。当功能通常需要每个块10秒时,“有时”功能需要长达3分钟。重建一些索引并重新启动该功能后,它再次像以前一样快。 不幸的是,我不知道它究竟是哪个索引,因为重新启动函数并构建它用于工作的游标需要一些时间,我们没有时间自己检查每个索引,所以我只重建所有索引函数可能使用的索引并重新启动它。

有问题的函数使用游标从表中选择大约5千万到2亿条目的数据,由一个包含大约50-500个条目的小表连接。连接条件是字符串比较。然后,我们使用从连接获得的小表中的主键来更新主表上的外键。更新过程由一个forall循环完成,这已被证明可以节省大量时间。

以下是两个表的表结构的简化版本:

CREATE TABLE "maintable" 
(   "pkmid" NUMBER(11,0) NOT NULL ENABLE, 
"fkid" NUMBER(11,0), 
"fkstring" NVARCHAR2(4) NOT NULL ENABLE, 
 CONSTRAINT "PK_MAINTABLE" PRIMARY KEY ("pkmid");

CREATE TABLE "smalltable" 
(   "pksid" NUMBER(11,0) NOT NULL ENABLE, 
"pkstring" NVARCHAR2(4) NOT NULL ENABLE, 
 CONSTRAINT "PK_SMALLTABLE" PRIMARY KEY ("pksid");

两个表的字符串列都有索引。因此,添加主键时,每次发生问题时都会重建4个索引。

我们以某种方式获取数据,我们只在维护中使用fkstring,并将fkid设置为null。在第一步中,我们填充小表。这只需要几分钟,并按以下方式完成:

INSERT INTO smalltable (pksid, pkstring)
    SELECT SEQ_SMALLTABLE.NEXTVAL, fkstring
        FROM 
        (
            SELECT DISTINCT mt.fkstring
                FROM maintable mt                        
            MINUS
            SELECT st.pkstring
                FROM smalltable st
        );
        commit;

此功能不会造成任何麻烦。

以下函数(它是函数的简化版本 - 我已经删除了日志记录和异常处理并重命名了一些变量):

function f_set_fkid return varchar2 is

    cursor lCursor_MAINTABLE is
        SELECT MT.PKmID, st.pksid
            FROM maintable mt
            JOIN smalltable st ON (mt.fkstring = st.pkstring)
            WHERE mt.fkid IS NULL;
    lIndex number := 0;
    lExitLoop boolean := false;

    type lCursorType is table of lCursor_MAINTABLE%rowtype index by pls_integer;
    lCurrentRow lCursor_MAINTABLE%rowtype;
    lTempDataArray lCursorType;
    lCommitEvery constant number := 1000;

    begin

        open lCursor_MAINTABLE;
            loop

                -- get next row, set exit condition
                fetch lCursor_MAINTABLE into lCurrentRow;
                if (lCursor_MAINTABLE%notfound) then
                    lExitLoop := true;
                end if;

                -- in case of cache being full, flush cache
                if ((lTempDataArray.count > 0) AND (lIndex >= lCommitEvery OR lExitLoop)) then
                    forall lIndex2 in lTempDataArray.FIRST..lTempDataArray.LAST
                        UPDATE maintable mt 
                            set fkid = lTempDataArray(lIndex2).pksid
                            WHERE mt.pkmid = lTempDataArray(lIndex2).pkmid;     
                    commit;
                    lTempDataArray.delete;
                    lIndex := 0;                           
                end if;                                                 

                -- data handling, fill cache
                if (lExitLoop = false) then
                    lIndex := lIndex + 1;                                 
                    lTempDataArray(lIndex). := lCurrentRow;
                end if;

                exit when lExitLoop;

            end loop;

        close lCursor_MAINTABLE; 

        return null;

    end;  

我会非常感谢任何帮助。

P.S。我知道批量收集会加速函数,也可能会稍微缓解代码,但目前我们对它通常具有的函数速度感到满意。更改使用批量收集的功能是我们明年的计划,但目前它不是一个选项(我怀疑它会解决这个索引问题)。

2 个答案:

答案 0 :(得分:2)

如果你有一个行,其中行数会大幅波动(就像在进行ETL加载时一样),我会在整个加载过程中使用满载表的统计信息。

因此,在表完全加载时生成统计信息,然后将这些统计信息用于后续加载。

如果使用表格半载时的统计信息,可能会欺骗优化器不使用索引或不使用最快的索引。如果按顺序加载数据以使低值,高值和密度偏斜,则尤其如此。

在您的情况下,列fkstringfkid的统计信息非常重要,因为这两列严重涉及性能问题的过程。

答案 1 :(得分:0)

function f_set_fkid return varchar2 is 

        cursor lCursor_MAINTABLE is 
            SELECT MT.PKmID, st.pksid 
                FROM maintable mt 
                JOIN smalltable st ON (mt.fkstring = st.pkstring) 
                WHERE mt.fkid IS NULL; 
        commit_every INTGER := 1000000;
        commit_counter INTEGER :=0;     
begin 
   for c in lCursor_MAINTABLE 
   loop
      UPDATE maintable mt  
      set fkid = c.pksid 
      WHERE mt.pkmid = c.pkmid;      
      commit_counter := commit_counter+1;
      if mod(commit_every,commit_counter) = 0
      then
         commit;
         commit_counter := 0;
      end if;
   end loop; 
   return null; 
end;