2011-04-27 97 views
6

这是针对Strategy to improve Oracle DELETE performance的后续问题。回顾一下,我们有一个大型数据库,其中包含表示层次结构的表示来自优化系统的1D到4D输出数据。读取和写入这些数据的速度非常快,并为我们各种系统利用这些信息提供了一个便利的途径。用于DELETE性能问题的Oracle分区解决方案

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

/* 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分区的组合。

我很确定ENABLE ROW MOVEMENT基于数据库大小以及表空间生活在ASSM中的要求而不存在问题。也许RANGE分区OptimizationRun和其余的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商业广告中的一个想法吗?

更新,大小的参考

  • 1D数据表〜1.7G
  • 2D数据表〜12.5G
  • 三维数据表〜117.3G
  • 4D数据表〜315.2 G
+0

如何在删除标记? (它是什么逻辑)。基于一个日期也许? (老化记录)。还有别的吗?这可能有助于推导出最佳方法 – tbone 2011-04-27 19:46:06

+0

'DeleteFlag'由用户设置,这会导致触发器应用'SYSDATE + 14'的'DeleteDate',以防用户想要撤销其决定。 – user7116 2011-04-27 20:34:33

回答

2

我很确定你在分区的正确轨道上处理你的删除p性能问题。但是,我认为你不能将这与触发器混合。与触发复杂的逻辑一直困扰着我,但是除了这个你在这里可能会遇到的问题:

  • DDL语句打破事务逻辑,因为Oracle执行任何DDL语句之前提交当前事务。
  • 幸运的是,您不能在触发器中提交(因为Oracle处于操作过程中且数据库处于不一致状态)。
  • 使用自治事务执行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(显然这会自动重建它们,但会花费更多时间)。

+0

我可以在'Case'上做'AFTER INSERT'触发器来添加分区,因为'OptimizationRun'中的条目稍后添加。我不能完全切换到我不认为的过程,因为当前的应用程序不使用过程。试图在数据库中保留此更新。至于FK约束,如果我的子表在REF分区上,这应该是一个非问题,因为它们将与父表一起被删除。对? – user7116 2011-04-27 14:34:20

+0

触发器需要声明为自治事务,它可以用于INSERT。至于删除我还没有使用REF分区,所以我不知道当你尝试删除父分区时会发生什么。在尝试使用DELETE CASCADE删除行的同时执行分区删除可能无法完美运行:) – 2011-04-27 14:40:56

+0

看起来我错过了,我们显然有过深思熟虑(阅读:通过剪切运气)创建新案例作为存储过程。现在要弄清楚子表分区。 – user7116 2011-04-27 14:48:24

1

不可能 - 您不能在行级触发器中发出类似DDL的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. 清除全部依赖Case.
  3. 清除Case.

的依赖OptimizationStep.

  • 清除所有表的表依赖于OptimizationRun.
  • 清除所有的表格,以便代码将被:

    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; 
    /
    

    Ena竹叶提取作业链:

    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; 
    /
    
  • +0

    我更新了设计以突出显示实际FK结构中的错误。先生,我的歉意。 – user7116 2011-04-27 14:50:32

    +0

    没问题。我也做了一些修改。 – 2011-04-27 15:53:01

    +0

    有趣的概念。我想这仍然会遇到通过并行性降低整个系统性能的问题,不是吗?假设我获得了2倍的并行度,CPU/IO的增加是否会损害其余部分? – user7116 2011-04-27 16:56:58