2011-04-27 16 views
6

Esta es una pregunta de seguimiento a Strategy to improve Oracle DELETE performance. Para recapitular, tenemos una gran base de datos con una jerarquía de tablas que representan los datos de salida 1D a 4D de un sistema de optimización. Leer y escribir esta información es rápido y proporciona un medio conveniente para que nuestros diversos sistemas utilicen la información.Solución de particionamiento de Oracle para DELETE problema de rendimiento

Sin embargo, eliminar los datos no utilizados se ha convertido en un oso. La jerarquía de la tabla actual está debajo.

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

Lo que estoy buscando es un medio de dividir los datos Case tal que podía caer una partición en relación con el caso para eliminar sus datos. Idealmente, OptimizationRun tendría una partición de intervalo basada en CaseId y esto se filtraría a través de sus hijos. Sin embargo, 11g no admite la combinación de particiones INTERVAL y REF.

Estoy bastante seguro de que ENABLE ROW MOVEMENT está fuera de cuestión en función del tamaño de la base de datos y el requisito de que los espacios de tabla vivan en ASSM. Tal vez RANGE partición en OptimizationRun y particionamiento REF en el resto?

Mi conjetura es que la estrategia con la que iba a necesitar un disparador que logra algo como lo siguiente:

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; 

Y luego el gatillo requisito para antes de la eliminación:

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; 

Buena idea? ¿O es esta una idea del comercial de SNL Bad Idea Jeans?

actualización, para referencia del tamaño:

  • tablas de datos 1D ~ 1,7 g
  • tablas de datos 2D ~ 12,5 g
  • tablas de datos 3D ~ 117.3G
  • tablas de datos 4D ~ 315,2 G
+0

cómo se marcan las eliminaciones? (cuál es la lógica detrás de esto). ¿Basado en una fecha tal vez? (envejecimiento de los registros). ¿Algo más?Esto puede ayudar a obtener el mejor enfoque – tbone

+0

'DeleteFlag' es establecido por el usuario, lo que hace que un disparador aplique un' DeleteDate' de 'SYSDATE + 14', en caso de que el usuario desee deshacer su decisión. – user7116

Respuesta

2

Estoy bastante seguro de que está en el camino correcto con la partición para hacer frente a su eliminación p problema de rendimiento Sin embargo, no creo que puedas mezclar esto con disparadores. una lógica compleja con disparadores siempre me ha molestado, pero aparte de esto aquí son los problemas que se podrían presentar:

  • instrucciones DDL rompen la lógica de transacciones desde Oracle realiza una confirmación de la transacción actual antes de cualquier instrucción DDL.
  • Afortunadamente, no puede confirmar en un desencadenador (ya que Oracle está en el medio de una operación y la base de datos no está en un estado consistente).
  • El uso de transacciones autónomas para realizar DDL sería una solución (¿pobre?) Para la inserción pero es poco probable que funcione para el BORRAR, ya que esto probablemente interferiría con la lógica ON DELETE CASCADE.

Sería más fácil de codificar y más fácil de mantener procedimientos que tienen que ver con el goteo y creación de particiones tales como:

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; 

En cuanto a la caída de las particiones, usted tiene que comprobar si este trabaja con integridad referencial. Puede ser necesario deshabilitar las restricciones de clave externa antes de descartar una partición de tabla primaria en una relación de tabla principal-secundaria.

También tenga en cuenta que los índices globales se dejarán en un estado inutilizable después de una caída de partición. Tendrás que volver a compilarlos a menos que especifiques UPDATE GLOBAL en tu declaración desplegable (obviamente, esto los reconstruiría automáticamente, pero tomaría más tiempo).

+0

Podría hacer un disparador 'AFTER INSERT' en' Case' para agregar la partición ya que las entradas en 'OptimizationRun' se agregarán más tarde. No puedo cambiar exactamente a un procedimiento que no creo, porque la aplicación actual no usa un procedimiento. Intentando mantener esta actualización en el DB. En cuanto a las restricciones FK, si mis tablas hijas están en particiones REF que no deberían tener problemas, ya que se descartarán con su tabla padre. ¿Derecha? – user7116

+0

El desencadenador debería declararse como una transacción autónoma, puede funcionar para INSERTAR. En cuanto a la eliminación, no he usado la partición REF todavía, así que no sé qué ocurre cuando intentas eliminar una partición padre. Realizar una caída de partición al mismo tiempo que intenta eliminar las filas con un DELETE CASCADE puede no funcionar a la perfección :) –

+0

Parece que me equivoqué, evidentemente tuvimos la previsión de (leer: por mala suerte) crear nuevos casos como un procedimiento almacenado . Ahora para descubrir la partición de la tabla hija. – user7116

1

No es posible: no se puede emitir DDL así en un desencadenador de nivel de fila.

[posible problema de diseño comentarios redactados, como dirigida]

¿Ha considerado la paralelización de la secuencia de comandos? En lugar de utilizar una sweeper que depende de la eliminación de cascada, en lugar de eso, aprovecha DBMS_SCHEDULER para paralelizar el trabajo. Puede ejecutar eliminaciones paralelas contra tablas en el mismo nivel del árbol de dependencias de forma segura.

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

Eso solo configura los programas. Lo que tenemos que hacer a continuación es configurar una cadena de trabajo para que podamos unirlos.

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

Ahora hacemos pasos en la cadena de trabajo que utilizan los programas de antes:

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

Ahora tenemos que vincular los pasos de la cadena juntos. Los puestos de trabajo se abren en abanico, así:

  1. instantánea del CaseIds, OptIds y StepIds para purgar.
  2. purgar todas las tablas dependientes en OptimizationStep.
  3. Purga todas las tablas que dependen de OptimizationRun.
  4. Purga todas las tablas que dependen de Case.
  5. purga Case.

Así que el código a continuación serían:

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 ble de la cadena de trabajo:

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

Puede ejecutar manualmente la cadena:

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

O crear un trabajo para programarlo:

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

Actualicé el diseño para resaltar mi error en la estructura FK real. Mis disculpas señor – user7116

+0

No hay problema. He hecho algunas revisiones también. –

+0

Concepto interesante. Supongo que aún se correría el problema de reducir el rendimiento de todo el sistema a través del paralelismo, ¿no? Digamos que obtengo el paralelismo 2x, ¿el aumento en CPU/IO daña el resto? – user7116

Cuestiones relacionadas