为啥 PL/SQL Bulk DML 对具有父子约束表的大型数据集运行缓慢?

Posted

技术标签:

【中文标题】为啥 PL/SQL Bulk DML 对具有父子约束表的大型数据集运行缓慢?【英文标题】:Why is PL/SQL Bulk DML running slowing for large datasets with parent-child constrained tables?为什么 PL/SQL Bulk DML 对具有父子约束表的大型数据集运行缓慢? 【发布时间】:2011-07-13 18:02:12 【问题描述】:

我一直试图弄清楚为什么对于记录表有几十万或更多记录的数据集,这个 PL/SQL 清除脚本运行缓慢。在脚本执行之前,记录表的某个子集被标记为清除 - 大约 75%。

是什么导致 Record_Part 表的删除比其他表的删除时间长这么多?是不是因为它位于 3-table 父子层次结构的中间?我是否在索引或约束方面错过了一些知识?我可以做些什么来加快这个定期清除过程?

这是一个 Oracle 10g 数据库。

提前感谢您阅读我的问题。

架构(部分):

记录表是父表 Record_Part 表是 Record 的子表(Record 有很多 Record_Part) Record_Person 是 Record_Part 的子项(Record_Part 有多个 Record_Person) 典型比率为 1:7:9(记录:记录部分:记录人)

记录

PK - sysid 物理标识 待定 purge_in_progress

Record_Part

PK - Part_pk FK - record_sysid

Record_Person

PK - sysid FK - Part_pk

运行时

50000 条记录条目

record_person forall 在 1:40 分钟内完成 record_part forall 在 1:20 分钟内完成 记录在 10 秒内完成

300000 条记录条目

record_person forall 在 9 分钟内完成 record_part forall 在 2 小时内完成 记录在 20 分钟内完成

2000000 条记录

record_person forall 在 1 内完成 小时 record_part forall 在 13 内完成 小时 (!) 记录在 8 分钟内完成

索引和约束 DDL

alter table Record add constraint record_REC_PK primary key (SYSID) using index tablespace DB_INDEX1;
alter table Record_Part add constraint RECPART_REC_PK primary key (Part_PK) using index tablespace DB_INDEX1;
alter table Record_Part add constraint RECPART_FK foreign key (RECORD_SYSID) references record (SYSID);
alter table Record_Person add constraint RECPERSON_REC_PK primary key (SYSID) using index tablespace DB_INDEX1;
alter table Record_Person add constraint RECPERSON_FK foreign key (Part_PK) references Record_Part (Part_PK);

CREATE INDEX REC_PURGE_IDX ON record (PURGE_IN_PROGRESS);
CREATE INDEX REC_PHYSID_IDX ON record (PHYSICALID);
CREATE INDEX REC_PENDING_IDX ON record (PENDING);
CREATE INDEX RECPART_RECORD_SYSID_IDX ON Record_Part (RECORD_SYSID);
CREATE INDEX RECPERSON_PARTPK_IDX on Record_Person (PART_PK);

脚本: (下面的脚本省略了时间戳打印)

DECLARE

TYPE sSysid IS TABLE OF record.sysid%TYPE
    INDEX BY PLS_INTEGER;

TYPE physicalid IS TABLE OF record.physicalid%TYPE
    INDEX BY PLS_INTEGER;    

l_sid sSysid;
l_physicalid physicalid;

BEGIN
    SELECT sysid, physicalid
    BULK COLLECT INTO l_sid, l_physicalid
        FROM record
        where purge_in_progress = 1;

FORALL i IN l_sid.FIRST .. l_sid.LAST
    delete from record_person where Part_pk like concat(l_sid(i), '%') or Part_pk like concat(l_physicalid(i), '%');

commit;

FORALL i IN l_sid.FIRST .. l_sid.LAST
    delete from record_Part where record_sysid = l_sid(i);

commit;

FORALL i IN l_sid.FIRST .. l_sid.LAST
    delete from record where sysid = l_sid(i);

END;
/

commit;

【问题讨论】:

您真的是一次将 200 万个元素收集到一个集合中,而不是每次都使用 LIMIT 来获取较少数量的元素吗?您是否跟踪会话以查看时间花费在哪里?如果是这样,您可以发布 tkprof 输出吗? 谢谢贾斯汀。我对批量收集的 rownum 限制运行脚本测试以获取运行时,但我想我没有考虑或意识到收集大小对执行速度的重要性。 不要使用 rownum 限制。在 BULK COLLECT 中使用 LIMIT 子句。 谢谢你,贾斯汀。您对提交频率的评论也很有帮助。 【参考方案1】:

查看此主题的第一个回复。正如 Justin 所指出的,您需要使用 limit 子句来获取固定数量的记录(通常使用 100 条,您可以对其进行参数化,看看哪种方式适合您的情况)。

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::p11_question_id:5918938803188

【讨论】:

感谢 Rajesh,我现在正在运行测试,其中有一个 while 循环围绕具有 rownum 限制的批量收集。问题 - 我应该多久提交一次?每 100 个,每 1000 个,还是仅在 while 循环之后一次?在此脚本的上下文中,提交率如何影响速度?谢谢! @kg - 提交只会减慢您的处理速度。仅在您完成逻辑事务时提交。我认为这意味着在您的情况下,除非您有特定的理由不这样做,否则您只会在流程结束时提交一次。【参考方案2】:

您能否在测试环境中禁用 FK 约束以查看是否有帮助?

另一种可能性是将 FK 约束重新创建为可延迟的,并在脚本开头延迟约束,例如:

alter table Record_Part 
      add constraint RECPART_FK foreign key (RECORD_SYSID) 
                                references record (SYSID) DEFERRABLE;
alter table Record_Person 
      add constraint RECPERSON_FK foreign key (Part_PK) 
                                  references Record_Part (Part_PK) DEFERRABLE;

SET CONSTRAINTS ALL DEFERRED;
...run your purge
SET CONSTRAINTS ALL IMMEDIATE;

请注意,任何 COMMIT 都会导致立即设置约束。您必须在每次提交后重新发出第一个设置约束语句。

约束将是我在这里的第一个嫌疑人。

【讨论】:

由于清除脚本将与使用这些表的应用程序同时运行,因此我需要始终保持引用完整性。不过,我确实很欣赏有关延迟约束的信息,这对我将来很有用。谢谢。 可延迟约束仅适用于会话 - 您不会在其他会话中丢失约束检查。 谢谢,我也不知道这个!我将运行一个测试,看看延迟约束是否会加快脚本运行时间。

以上是关于为啥 PL/SQL Bulk DML 对具有父子约束表的大型数据集运行缓慢?的主要内容,如果未能解决你的问题,请参考以下文章

PL/SQL批处理语句BULK COLLECT

带有 BULK COLLECT 的 Oracle PL/SQL 6504

Oracle PL/SQL BULK 从数组更新

如何在 PL/SQL 中使用 BULK COLLECT 和 FORALL 替换 CURSOR FOR LOOP?

PL/SQL程序设计基础语法详解

Oracle --- PL/SQL