【问题标题】:Oracle partitioning solution for DELETE performance problem针对DELETE性能问题的Oracle分区解决方案
【发布时间】:2011-04-27 13:56:19
【问题描述】:

这是Strategy to improve Oracle DELETE performance 的后续问题。回顾一下,我们有一个大型数据库,其中包含代表优化系统的 1D 到 4D 输出数据的表层次结构。读取和写入这些数据的速度很快,并且为我们的各种系统利用这些信息提供了一种便捷的方式。

但是,删除未使用的数据已成为一种负担。当前表层次结构如下。

/* Metadata tables */
Case(CaseId, DeleteFlag, ...) On Delete Cascade CaseId
OptimizationRun(OptId, CaseId, ...) On Delete Cascade OptId
OptimizationStep(StepId, OptId, ...) On Delete Cascade StepId

/* Data tables */
Files(FileId, CaseId, Blob) /* deletes are near instantateous here */

/* Data per run */
OnedDataX(OptId, ...)
TwoDDataY1(OptId, ...) /* packed representation of a 1D slice */

/* Data not only per run, but per step */
TwoDDataY2(StepId, ...)  /* packed representation of a 1D slice */
ThreeDDataZ(StepId, ...) /* packed representation of a 2D slice */
FourDDataZ(StepId, ...)  /* packed representation of a 3D slice */
/* ... About 10 or so of these tables exist */

我正在寻找一种对Case 数据进行分区的方法,以便我可以删除与案例相关的分区以删除其数据。理想情况下,OptimizationRun 将有一个基于CaseId 的间隔分区,这将过滤到其子级。但是,11g 不支持 INTERVAL 和 REF 分区的组合。

根据数据库大小和表空间位于 ASSM 中的要求,我相当肯定 ENABLE ROW MOVEMENT 是不可能的。也许OptimizationRun 上的RANGE 分区和其余部分上的REF 分区?

我的猜测是使用该策略,我需要一个触发器来完成以下操作:

CREATE OR REPLACE TRIGGER Case_BeforeInsert_MakePartitions
BEFORE INSERT
    ON Case
    FOR EACH ROW
DECLARE
    v_PartName varchar(64)       := 'CASE_OPTPART_' || :new.CaseId;
    v_PartRange Case.CaseId%type := :new.CaseId
BEGIN
    -- Take :new.CaseId and create the partition
    ALTER TABLE OptimizationRun
        ADD PARTITION v_PartName
        VALUES LESS THAN ( v_PartRange );
END;

然后是删除前的必要触发器:

CREATE OR REPLACE TRIGGER Case_BeforeDelete_RemovePartitions
BEFORE DELETE
    ON Case
    FOR EACH ROW
DECLARE
    v_PartName varchar(64) := 'CASE_OPTPART_' || :old.CaseId;
BEGIN
    -- Drop the partitions associated with the case
    ALTER TABLE OptimizationRun
        DROP PARTITION v_PartName;
END;

好主意?或者这是 SNL Bad Idea Jeans 广告中的创意?

更新,供尺寸参考

  • 一维数据表~1.7G
  • 2D 数据表 ~12.5G
  • 3D 数据表 ~117.3G
  • 4D 数据表 ~315.2G

【问题讨论】:

  • 如何标记删除? (它背后的逻辑是什么)。也许基于日期? (老化记录)。还有什么?这可能有助于得出最佳方法
  • DeleteFlag 由用户设置,这会导致触发器应用DeleteDateSYSDATE + 14,以防用户想要撤消他们的决定。

标签: oracle oracle11g


【解决方案1】:

我很确定您在分区处理删除性能问题方面走在了正确的轨道上。但是,我认为您不能将其与触发器混合使用。触发器的复杂逻辑一直困扰着我,但除此之外,您可能会遇到以下问题:

  • DDL 语句会破坏事务逻辑,因为 Oracle 在任何 DDL 语句之前执行当前事务的提交。
  • 幸运的是,您无法在触发器中提交(因为 Oracle 处于操作中间,而 DB 未处于一致状态)。
  • 使用自治事务来执行 DDL 将是一种(糟糕的?)插入解决方法,但不太可能适用于 DELETE,因为这可能会干扰 ON DELETE CASCADE 逻辑。

处理删除和创建分区的过程会更容易编码和维护,例如:

CREATE PROCEDURE add_case (case_id, ...) AS
BEGIN
   EXECUTE IMMEDIATE 'ALTER TABLE OptimizationRun ADD partition...';
   /* repeat for each child table */
   INSERT INTO Case VALUES (...);
END;

关于删除分区,您必须检查这是否适用于参照完整性。在删除父子表关系中的父表分区之前,可能需要禁用外键约束。

另请注意,分区删除后全局索引将处于不可用状态。除非您在 drop 语句中指定 UPDATE GLOBAL,否则您必须重新构建它们(显然这会自动重新构建它们,但会花费更多时间)。

【讨论】:

  • 我可以在Case 上执行AFTER INSERT 触发器来添加分区,因为稍后会添加OptimizationRun 中的条目。我不能完全切换到我不认为的过程,因为当前应用程序不使用过程。试图将此更新保存在数据库中。至于 FK 约束,如果我的子表在 REF 分区上,那应该不是问题,因为它们将与父表一起被删除。对吗?
  • 触发器需要声明为自治事务,它可能适用于 INSERT。至于删除,我还没有使用 REF 分区,所以我不知道当您尝试删除父分区时会发生什么。在尝试使用 DELETE CASCADE 删除行的同时执行分区删除可能无法正常工作:)
  • 看来我说错了,我们显然有先见之明(阅读:靠运气)将新案例创建为存储过程。现在要弄清楚子表的分区。
  • 另外一个问题,Oracle 一张表可以支持多少个分区?我们有大约 500-1k 个案例。
  • 对于10gR211gR1,限制似乎是1024k 分区。
【解决方案2】:

不可能 - 您不能在行级触发器中发出这样的 DDL。

[可能的设计问题评论已编辑,已解决]

您是否考虑过并行化您的脚本?而不是依赖删除级联的清扫器,而是利用 DBMS_SCHEDULER 来并行化工作。您可以安全地对依赖关系树同一级别的表运行并行删除。

begin
  dbms_scheduler.create_program
    (program_name => 'snapshot_purge_cases',
     program_type => 'PLSQL_BLOCK',
     program_action => 
      'BEGIN
         delete from purge$Case;
         insert into purge$Case
         select CaseId 
           from Case
          where deleteFlag = 1;

         delete from purge$Opt;
         insert into purge$Opt
         select OptId 
           from OptimizationRun
          where CaseId in (select CaseId from purge$Case);

         delete from purge$Step;
         insert into purge$Step
         select StepId 
           from OptimizationStep
          where OptId in (select OptId from purge$Opt);

         commit;
       END;',
     enabled => true,
     comments => 'Program to snapshot keys for purging';           
    );

  dbms_scheduler.create_program 
    (program_name => 'purge_case',
     program_type => 'PLSQL_BLOCK',
     program_action => 'BEGIN 
                          loop
                            delete from Case 
                             where CaseId in (select Case from purge$Case)
                            where rownum <= 50000;
                            exit when sql%rowcount = 0;
                            commit;
                          end loop;
                          commit;
                        END;',
     enabled => true,
     comments => 'Program to purge the Case Table'
    );

  -- repeat for each table being purged

end;
/

那只是设置程序。我们接下来需要做的是建立一个工作链,这样我们就可以把它们放在一起。

BEGIN
  dbms_scheduler.create_chain 
   (chain_name => 'purge_case_chain');
END;
/

现在我们使用之前的程序在工作链中执行步骤:

BEGIN
  dbms_scheduler.define_chain_step
   (chain_name => 'purge_case_chain',
    step_name  => 'step_snapshot_purge_cases',
    program_name => 'snapshot_purge_cases'
   );

  dbms_scheduler.define_chain_step
   (chain_name => 'purge_case_chain',
    step_name  => 'step_purge_cases',
    program_name => 'purge_case'
   );

  -- repeat for every table
END;
/

现在我们必须将链式步骤链接在一起。这些作业会像这样散开:

  1. 快照CaseIdsOptIdsStepIds 以清除。
  2. 清除所有依赖于OptimizationStep. 的表
  3. 清除所有依赖于OptimizationRun. 的表
  4. 清除所有依赖于Case. 的表
  5. 清除Case.

所以代码将是:

begin
  dbms_scheduler.define_chain_rule
   (chain_name => 'purge_case_chain',
    condition  => 'TRUE',
    action     => 'START step_snapshot_purge_cases',
    rule_name  => 'rule_snapshot_purge_cases'
   );

  -- repeat for every table dependent on OptimizationStep
  dbms_scheduler.define_chain_rule
   (chain_name => 'purge_case_chain',
    condition  => 'step_snapshot_purge_cases COMPLETED',
    action     => 'START step_purge_TwoDDataY2',
    rule_name  => 'rule_purge_TwoDDataY2'
   );

  -- repeat for every table dependent on OptimizationRun     
  dbms_scheduler.define_chain_rule
   (chain_name => 'purge_case_chain',
    condition  => 'step_purge_TwoDDataY2  COMPLETED and
                   step_purge_ThreeDDataZ COMPLETED and
                   ... ',
    action     => 'START step_purge_OnedDataX',
    rule_name  => 'rule_purge_OnedDataX'
   );

  -- repeat for every table dependent on Case  
  dbms_scheduler.define_chain_rule
   (chain_name => 'purge_case_chain',
    condition  => 'step_purge_OneDDataX  COMPLETED and
                   step_purge_TwoDDataY1 COMPLETED and
                   ... ',
    action     => 'START step_purge_Files',
    rule_name  => 'rule_purge_Files'
   );

  dbms_scheduler.define_chain_rule
   (chain_name => 'purge_case_chain',
    condition  => 'step_purge_Files           COMPLETED and
                   step_purge_OptimizationRun COMPLETED and 
                   ... ',
    action     => 'START step_purge_Case',
    rule_name  => 'rule_purge_Case'
   );

  -- add a rule to end the chain
  dbms_scheduler.define_chain_rule
   (chain_name => 'purge_case_chain',
    condition  => 'step_purge_Case COMPLETED',
    action     => 'END',
    rule_name  => 'rule_purge_Case'
   );

end;
/

启用作业链:

BEGIN
  DBMS_SCHEDULER.enable ('purge_case_chain');
END;
/

您可以手动运行链:

BEGIN
  DBMS_SCHEDULER.RUN_CHAIN
   (chain_name => 'chain_purge_case',
    job_name   => 'chain_purge_case_run'
   );
END;
/

或者创建一个作业来安排它:

BEGIN
  DBMS_SCHEDULER.CREATE_JOB (
    job_name        => 'job_purge_case',
    job_type        => 'CHAIN',
    job_action      => 'chain_purge_case',
    repeat_interval => 'freq=daily',
    start_date      => ...
    end_date        => ...
    enabled         => TRUE);
END;
/

【讨论】:

  • 我更新了设计以突出我在实际 FK 结构中的错误。对不起,先生。
  • 有趣的概念。我想这仍然会遇到通过并行性降低整个系统性能的问题,不是吗?假设我获得了 2 倍并行度,CPU/IO 的增加是否会损害其余部分?
  • 您可以通过调整 JOB_QUEUE_PROCESSES(我上次检查)来调整作业的并行度以限制效果。但我怀疑为每个父母的每个孩子进行单独删除会更昂贵,因为存在语句级开销。您当前的实现几乎可以肯定是串行的并且是缓慢的,这是两全其美的。
  • 另外,您的所有DELETE CASCADE 约束必须返回DELETE RESTRICT 才能看到任何性能变化。这可能是为什么大规模删除孩子并不能提高你的表现。 Oracle 仍在幕后运行delete from OptimizationRun where CaseId = :somevalue;即使没有任何行被删除,它仍在运行查询。
  • 如果 OptimizationRun 有 ~2000 行,这有关系吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-06
  • 1970-01-01
  • 2020-12-28
  • 1970-01-01
  • 2016-03-18
  • 2021-07-08
  • 2010-09-11
相关资源
最近更新 更多