Решение для разделения Oracle для УДАЛЕНИЯ проблемы с производительностью - PullRequest
8 голосов
/ 27 апреля 2011

Это дополнительный вопрос к Стратегия улучшения производительности 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 не поддерживает комбинацию разделов 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
  • 3D-таблицы данных ~ 117,3G
  • 4D таблицы данных ~ 315.2G

Ответы [ 2 ]

4 голосов
/ 27 апреля 2011

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

  • Операторы 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;

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

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

1 голос
/ 27 апреля 2011

Невозможно - вы не можете выдавать 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. Снимок 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;
/
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...