Решение секционирования Oracle для проблемы удалить производительность

это последующий вопрос к Стратегия повышения производительности Oracle DELETE. Напомним, что у нас есть большая БД с иерархией таблиц, представляющих 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 не поддерживает комбинацию интервального и рефракционного разбиения.

Я довольно уверен, что включить движение строк не может быть и речи на основе размера БД и требования, чтобы табличные пространства жили в ASSM. Возможно Разбиение диапазона на 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?

обновление, для справки в размере:

  • таблицы данных 1D ~1,7 г
  • 2D таблицы данных ~12.5 G
  • 3D таблицы данных ~117.3 G
  • таблицы данных 4D ~315.2 G

2 ответов


Я уверен, что вы на правильном пути с partitionning, чтобы справиться с проблемой производительности удаления. Однако я не думаю, что вы сможете смешать это с триггерами. Сложная логика с триггерами всегда беспокоила меня, но помимо этого здесь есть проблемы, с которыми вы, вероятно, столкнетесь:

  • операторы DDL нарушают логику транзакций, поскольку Oracle выполняет фиксацию текущей транзакции перед любым оператором DDL.
  • к счастью, вы не можете фиксация в триггере (поскольку Oracle находится в середине операции, а DB не находится в согласованном состоянии).
  • использование автономных транзакций для выполнения DDL было бы (плохо?) обходной путь для вставки, но вряд ли будет работать для удаления, поскольку это, вероятно, помешает логике каскада удаления.

было бы проще кодировать и проще поддерживать процедуры, которые касаются удаления и создания разделов, таких как as:

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;

Что касается удаления разделов, вам нужно будет проверить, работает ли это с ссылочной целостностью. Возможно, потребуется отключить ограничения внешнего ключа перед удалением секции родительской таблицы в связи родительская-дочерняя таблица.

также обратите внимание, что глобальные индексы будут оставлены в непригодном состоянии после удаления раздела. Вам придется перестроить их, если вы не укажете UPDATE GLOBAL в своем операторе drop (очевидно, это перестроит их автоматически, но займет больше времени).


невозможно - вы не можете выдавать DDL, как это в триггере уровня строки.

[возможно оформление комментария удалено, а имя]

вы рассматривали возможность распараллеливания вашего сценария? Вместо метельщика, который полагается на delete cascade, вместо этого используйте 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. снимок CaseIds, OptIds и StepIds очистить.
  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;
/