我正在开发一个数据仓库项目,因此,我一直在实现包中的一些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。我知道批量收集会加速函数,也可能会稍微缓解代码,但目前我们对它通常具有的函数速度感到满意。更改使用批量收集的功能是我们明年的计划,但目前它不是一个选项(我怀疑它会解决这个索引问题)。
答案 0 :(得分:2)
如果你有一个行,其中行数会大幅波动(就像在进行ETL加载时一样),我会在整个加载过程中使用满载表的统计信息。
因此,在表完全加载时生成统计信息,然后将这些统计信息用于后续加载。
如果使用表格半载时的统计信息,可能会欺骗优化器不使用索引或不使用最快的索引。如果按顺序加载数据以使低值,高值和密度偏斜,则尤其如此。
在您的情况下,列fkstring
和fkid
的统计信息非常重要,因为这两列严重涉及性能问题的过程。
答案 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;