La mejor manera de eliminar un conjunto de registros muy grande en Oracle

18

Administro una aplicación que tiene un back-end de base de datos Oracle muy grande (casi 1 TB de datos con más de 500 millones de filas en una tabla). La base de datos realmente no hace nada (sin SProcs, sin desencadenantes ni nada) es solo un almacén de datos.

Todos los meses estamos obligados a purgar registros de las dos tablas principales. El criterio para la purga varía y es una combinación de antigüedad de fila y un par de campos de estado. Por lo general, terminamos purgando entre 10 y 50 millones de filas por mes (agregamos alrededor de 3-5 millones de filas por semana a través de importaciones).

Actualmente tenemos que hacer esta eliminación en lotes de aproximadamente 50,000 filas (es decir, eliminar 50000, comit, eliminar 50000, commit, repetir). Intentar eliminar todo el lote al mismo tiempo hace que la base de datos no responda durante aproximadamente una hora (dependiendo del número de filas). Eliminar las filas en lotes como este es muy difícil para el sistema y, por lo general, tenemos que hacerlo "según lo permita el tiempo" en el transcurso de una semana; permitir que la secuencia de comandos se ejecute continuamente puede provocar una degradación del rendimiento que es inaceptable para el usuario.

Creo que este tipo de eliminación por lotes también degrada el rendimiento del índice y tiene otros impactos que eventualmente hacen que el rendimiento de la base de datos se degrade. Hay 34 índices en una sola tabla, y el tamaño de los datos del índice es en realidad mayor que los datos en sí.

Aquí está el script que una de nuestras personas de TI usa para hacer esta purga:

BEGIN
LOOP

delete FROM tbl_raw 
  where dist_event_date < to_date('[date]','mm/dd/yyyy') and rownum < 50000;

  exit when SQL%rowcount < 49999;

  commit;

END LOOP;

commit;

END;

Esta base de datos debe estar al 99.99999% y solo tenemos una ventana de mantenimiento de 2 días una vez al año.

Estoy buscando un mejor método para eliminar estos registros, pero aún no he encontrado ninguno. ¿Alguna sugerencia?

Gorila codificante
fuente
También tenga en cuenta que hay más de 30 índices en juego aquí
jcolebrand

Respuestas:

17

La lógica con 'A' y 'B' podría estar "oculta" detrás de una columna virtual en la que podría realizar la partición:

alter session set nls_date_format = 'yyyy-mm-dd';
drop   table tq84_partitioned_table;

create table tq84_partitioned_table (
  status varchar2(1)          not null check (status in ('A', 'B')),
  date_a          date        not null,
  date_b          date        not null,
  date_too_old    date as
                       (  case status
                                 when 'A' then add_months(date_a, -7*12)
                                 when 'B' then            date_b
                                 end
                        ) virtual,
  data            varchar2(100) 
)
partition   by range  (date_too_old) 
( 
  partition p_before_2000_10 values less than (date '2000-10-01'),
  partition p_before_2000_11 values less than (date '2000-11-01'),
  partition p_before_2000_12 values less than (date '2000-12-01'),
  --
  partition p_before_2001_01 values less than (date '2001-01-01'),
  partition p_before_2001_02 values less than (date '2001-02-01'),
  partition p_before_2001_03 values less than (date '2001-03-01'),
  partition p_before_2001_04 values less than (date '2001-04-01'),
  partition p_before_2001_05 values less than (date '2001-05-01'),
  partition p_before_2001_06 values less than (date '2001-06-01'),
  -- and so on and so forth..
  partition p_ values less than (maxvalue)
);

insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '2008-04-14', date '2000-05-17', 
 'B and 2000-05-17 is older than 10 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '1999-09-19', date '2004-02-12', 
 'B and 2004-02-12 is younger than 10 yrs, must be kept');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2000-06-16', date '2010-01-01', 
 'A and 2000-06-16 is older than 3 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2009-06-09', date '1999-08-28', 
 'A and 2009-06-09 is younger than 3 yrs, must be kept');

select * from tq84_partitioned_table order by date_too_old;

-- drop partitions older than 10 or 3 years, respectively:

alter table tq84_partitioned_table drop partition p_before_2000_10;
alter table tq84_partitioned_table drop partition p_before_2000_11;
alter table tq84_partitioned_table drop partition p2000_12;

select * from tq84_partitioned_table order by date_too_old;
René Nyffenegger
fuente
Puede que haya simplificado demasiado la lógica detrás de cómo se determinan los registros para purgar, pero esta es una idea muy interesante. Sin embargo, una cosa que debe considerarse es el rendimiento diario. La purga es "nuestro problema", el cliente no aceptará un rendimiento degradado solo para resolverlo. ¿Parece, por algunos de los comentarios y la respuesta de Gary, que esto podría ser un problema con la partición?
Coding Gorilla
No estoy seguro de si esta es la respuesta que estamos buscando, pero este es definitivamente un enfoque muy interesante que investigaremos.
Coding Gorilla
14

La solución clásica para esto es dividir sus tablas, por ejemplo, por mes o por semana. Si no los ha encontrado antes, una tabla particionada es como varias tablas idénticamente estructuradas con un implícito UNIONal seleccionar, y Oracle almacenará automáticamente una fila en la partición apropiada al insertarla en función de los criterios de partición. Usted menciona los índices; bueno, cada partición también tiene sus propios índices particionados. Es una operación muy barata en Oracle dejar caer una partición (es análogo a unTRUNCATEen términos de carga porque eso es lo que realmente está haciendo: truncar o soltar una de estas subtablas invisibles). Será una gran cantidad de procesamiento dividir "después del hecho", pero no tiene sentido llorar por la leche derramada: las ventajas de hacerlo hasta ahora superan los costos. Cada mes dividiría la partición superior para crear una nueva partición para los datos del próximo mes (puede automatizar fácilmente esto con a DBMS_JOB).

Y con las particiones también puede explotar la consulta paralela y la eliminación de particiones , lo que debería hacer que sus usuarios estén muy contentos ...

Gayo
fuente
FWIW usamos esta técnica en mi sitio en una base de datos de 30Tb +
Gaius
El problema con la partición es que no hay una forma clara de particionar los datos. En una de las dos tablas (no la que se muestra a continuación), los criterios utilizados para realizar la purga se basan en dos campos de fecha diferentes (y distintos) y un campo de estado. Por ejemplo, si el estado es Aentonces DateAmayor de 3 años, se purga. Si el estado es By DateBtiene más de 10 años, se purga. Si mi comprensión de la partición es correcta, entonces la partición no sería útil en una situación como esta (al menos en lo que respecta a la purga).
Coding Gorilla
Puede particionar por estado y subpartición por rango de fechas. Pero si el estado (o la fecha) cambia, efectivamente se elimina de una subpartición y se inserta en la otra. En resumen, puede obtener un éxito en sus procesos diarios para ahorrar tiempo en su purga.
Gary
66
Alternativamente, puede crear una columna virtual que muestre DateA cuando el estado es A y DateB cuando el estado es B y luego particionar en la columna virtual. Se produciría la misma migración de partición, pero ayudaría a su purga. Parece que esto ya se publicó como respuesta.
Leigh Riffel
4

Un aspecto a tener en cuenta es la cantidad de rendimiento de eliminación resultante de los índices y la cantidad de la tabla sin formato. Cada registro eliminado de la tabla requiere la misma eliminación de la fila de cada índice btree. Si tiene más de 30 índices btree, sospecho que la mayor parte de su tiempo se dedica al mantenimiento del índice.

Esto tiene un impacto en la utilidad de la partición. Digamos que tiene un índice de nombre. Un índice Btree estándar, todo en un segmento, podría tener que hacer cuatro saltos para pasar del bloque raíz al bloque hoja y una quinta lectura para obtener la fila. Si ese índice está dividido en 50 segmentos y no tiene la clave de partición como parte de la consulta, entonces será necesario verificar cada uno de esos 50 segmentos. Cada segmento será más pequeño, por lo que es posible que solo tenga que hacer 2 saltos, pero aún así puede terminar haciendo 100 lecturas en lugar de las 5 anteriores.

Si son índices de mapa de bits, las ecuaciones son diferentes. Probablemente no esté utilizando índices para identificar filas individuales, sino conjuntos de ellas. Entonces, en lugar de una consulta que usa 5 IO para devolver un solo registro, estaba usando 10,000 IO. Como tal, la sobrecarga adicional en particiones adicionales para el índice no importará.

Gary
fuente
2

la eliminación de 50 millones de registros por mes en lotes de 50,000 es solo 1000 iteraciones. si elimina 1 cada 30 minutos, debe cumplir con su requisito. una tarea programada para ejecutar la consulta que publicó pero eliminar el bucle para que solo se ejecute una vez no debería causar una degradación notable para los usuarios. Hacemos aproximadamente el mismo volumen de registros en nuestra planta de fabricación que funciona casi las 24 horas del día, los 7 días de la semana y satisface nuestras necesidades. De hecho, lo distribuimos un poco más de 10,000 registros cada 10 minutos, que se ejecuta en aproximadamente 1 o 2 segundos ejecutándose en nuestros servidores Oracle Unix.

Jason Jakob
fuente
¿Qué pasa con la generación masiva de 'deshacer' y 'rehacer' 'eliminar'? También ahoga IO ... el enfoque basado en 'eliminar' ciertamente debería ser un NO ... NO para tablas grandes.
pahariayogi
1

Si el espacio en el disco no es muy importante, podría crear una copia de "trabajo" de la tabla, por ejemplo my_table_new, utilizando CTAS (Crear tabla como selección) con criterios que omitirían los registros que se eliminarán. Puede hacer la declaración de creación en paralelo, y con la sugerencia de agregar para hacerlo más rápido, y luego construir todos sus índices. Luego, una vez que haya terminado (y probado), cambie el nombre de la tabla existente my_table_oldy cambie el nombre de la tabla de "trabajo" my_table. Una vez que te sientas cómodo con todo drop my_table_old purgepara deshacerte de la vieja mesa. Si hay un montón de restricciones de clave externa, eche un vistazo al dbms_redefinition paquete PL / SQL . Clonará sus índices, restricciones, etc. cuando use las opciones apropiadas. Este es un resumen de una sugerencia de Tom Kyte de AskTomfama. Después de la primera ejecución, puede automatizar todo, y la tabla de creación debería ir mucho más rápido, y se puede hacer mientras el sistema está activo, y el tiempo de inactividad de la aplicación se limitaría a menos de un minuto para cambiar el nombre de las tablas. Usar CTAS será mucho más rápido que hacer varias eliminaciones por lotes. Este enfoque puede ser particularmente útil si no tiene licencia de partición.

CTAS de muestra, manteniendo filas con datos de los últimos 365 días y flag_inactive = 'N':

create /*+ append */ table my_table_new 
   tablespace data as
   select /*+ parallel */ * from my_table 
       where some_date >= sysdate -365 
       and flag_inactive = 'N';

-- test out my_table_new. then if all is well:

alter table my_table rename to my_table_old;
alter table my_table_new rename to my_table;
-- test some more
drop table my_table_old purge;
Mark Stewart
fuente
1
Esto se puede considerar si (a) la purga es una tarea única. (b) si usted menos filas para retener y la mayor parte de los datos para eliminar ...
pahariayogi
0

al soltar una partición, deja inutilizables los índices globales, que necesitan reconstruirse, la reconstrucción de los índices globales sería un gran problema, ya que si lo hace en línea, será bastante lento, de lo contrario necesitará tiempo de inactividad. en cualquier caso, no puede cumplir con el requisito.

"Normalmente terminamos purgando entre 10 y 50 millones de filas por mes"

recomendaría usar PL / SQL batch delete, varias horas está bien, creo.

iceburge5
fuente
1
Si tiene una clave primaria, al soltar una partición no debería inutilizarse ningún índice global. Pero si el OP tiene muchos índices globales, habrá un alto costo para eliminar particiones. En un caso ideal, cuando alguien está particionando una tabla, la partición se basa en la clave primaria y no necesita ningún índice global. Que cada consulta puede aprovechar la poda de partición.
Gandolf989
@ Gandolf989 dejar caer una partición siempre hará que un índice global sea inutilizable
milagro173