BORRADO muy lento en PostgreSQL, solución alternativa?

30

Tengo una base de datos en PostgreSQL 9.2 que tiene un esquema principal con alrededor de 70 tablas y un número variable de esquemas idénticamente estructurados por cliente de 30 tablas cada uno. Los esquemas del cliente tienen claves externas que hacen referencia al esquema principal y no al revés.

Acabo de comenzar a llenar la base de datos con algunos datos reales tomados de la versión anterior. La base de datos había alcanzado aproximadamente 1.5 GB (se espera que crezca a varios 10 GB en semanas) cuando tuve que hacer una eliminación masiva en una tabla muy central en el esquema principal. Todas las claves foráneas en cuestión están marcadas EN ELIMINAR CASCADA.

No fue una sorpresa que esto llevara mucho tiempo, pero después de 12 horas quedó claro que era mejor comenzar de nuevo, dejar caer la base de datos y volver a iniciar la migración. Pero, ¿qué sucede si necesito repetir esta operación más tarde cuando el DB está activo y es mucho más grande? ¿Existen métodos alternativos y más rápidos?

¿Sería mucho más rápido si escribiera un script que explorara las tablas dependientes, comenzando en la tabla más alejada de la tabla central, eliminando las filas dependientes tabla por tabla?

Un detalle importante es que hay desencadenantes en algunas de las tablas.

jd.
fuente
44
Después de 5 años, estoy cambiando la respuesta aceptada. Los DELETEs lentos casi siempre son causados ​​por índices faltantes en claves externas que hacen referencia directa o indirectamente a la tabla que se está eliminando. Los disparadores que se activan en las declaraciones DELETE también pueden ralentizar las cosas, aunque la solución es casi siempre hacer que se ejecuten más rápido (por ejemplo, agregando índices faltantes) y casi nunca deshabilitar todos los disparadores.
jd.

Respuestas:

30

Tuve un problema similar. Como resultado, esos ON DELETE CASCADEdesencadenantes estaban ralentizando un poco las cosas, porque esas eliminaciones en cascada eran muy lentas.

Resolví el problema creando índices en los campos de clave externa en las tablas de referencia, y pasé de tomar un montón de horas para la eliminación a unos segundos.

ailnlv
fuente
Wow, esto me ayudó a eliminar registros de 8M en unos minutos. Pero lo que no entiendo es que mi tabla solo contenía referencias a otras tablas, ninguna otra tabla tiene referencias a mi tabla. Entonces, ¿cuál es exactamente el efecto aquí? (No estoy usando ON DELETE CASCADE)
msrd0
2
Esto también lo resolvió para mí. Para cualquiera que intente esto, puede hacer una EXPLAIN (ANALYZE, BUFFERS)consulta en una sola fila y debe mostrar qué restricciones de clave externa tomaron más tiempo (al menos lo hicieron para mí).
Justin Workman
Igual, tuvo que eliminar en cascada 600k filas y al principio tomaba entre 2 y 10 por operación con un uso del 100% de la CPU. Ahora solo tomó unos minutos eliminarlos a todos con un 80% de uso de CPU.
fillobotto
Es importante tener en cuenta que si tiene una referencia extranjera a cualquier lugar, la columna fuente debe tener un índice real o el rendimiento se verá afectado. No estoy seguro de si el PRIMARYíndice es suficiente, pero el UNIQUEíndice definitivamente no es lo suficientemente bueno para este propósito.
Mikko Rantalainen
26

Tienes pocas opciones. La mejor opción es ejecutar una eliminación por lotes para que no se disparen los disparadores. Deshabilite los desencadenantes antes de eliminarlos, luego vuelva a habilitarlos. Esto le ahorra una gran cantidad de tiempo. Por ejemplo:

ALTER TABLE tablename DISABLE TRIGGER ALL; 
DELETE ...; 
ALTER TABLE tablename ENABLE TRIGGER ALL;

Una clave importante aquí es que desea minimizar la profundidad de las subconsultas. En este caso, es posible que desee configurar tablas temporales para almacenar información relevante para evitar subconsultas profundas en su eliminación.

Chris Travers
fuente
En mi caso, comencé el comando DELETE FROM antes de irme a la cama y aún no lo había hecho cuando regresé a mi computadora al día siguiente. 100% de uso de CPU en un núcleo todo el tiempo. Después de deshabilitar los disparadores y volver a intentarlo, tardó 3 segundos en eliminar 200k registros. ¡Gracias!
Nick Woodhams el
13

La mejor manera de resolver el problema es el tiempo para consultar detallada del PostgreSQL: EXPLAIN. Para esto, necesita encontrar como mínimo una única consulta que se complete pero que tarde más de lo esperado. Digamos que esta línea se vería así

delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';

En lugar de ejecutar realmente ese comando, puedes hacer

begin;
explain (analyze,buffers,timing) delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
rollback;

La reversión al final permite ejecutar esto sin modificar realmente la base de datos, pero aún así obtienes el tiempo detallado de lo que tomó cuánto. Después de ejecutar eso, puede encontrar en la salida que algunos disparadores causan grandes demoras:

...
Trigger for constraint XYZ123: time=12311.292 calls=1
...

El valor timeestá en ms (milisegundos), por lo que la comprobación de esta restricción llevó unos 12,3 segundos. Debe agregar una nueva INDEXsobre las columnas requeridas para que este desencadenador se pueda calcular de manera efectiva. Para referencias de clave externa, la columna que hace referencia a otra tabla debe indexarse ​​(es decir, la columna de origen, no la columna de destino). PostgreSQL no crea automáticamente dichos índices para usted y DELETEes la única consulta común en la que realmente necesita ese índice. Como resultado, es posible que haya acumulado años de datos hasta llegar al caso dondeDELETE es demasiado lento debido a la falta de un índice.

Una vez que haya corregido el rendimiento de esa restricción (o alguna otra cosa que tomó demasiado tiempo), repita el comando en begin/rollback block para que pueda comparar el nuevo tiempo de ejecución con el anterior. Continúe hasta que esté satisfecho con el tiempo de respuesta de eliminación de una sola línea (obtuve una consulta para pasar de 25.6 segundos a 15 ms simplemente agregando diferentes índices). Luego puede proceder a completar su eliminación completa sin ningún truco.

(Tenga en cuenta que EXPLAINnecesita una consulta que pueda completarse con éxito. Una vez tuve un problema en el que PostgreSQL tardó demasiado en darse cuenta de que una eliminación violaría una restricción de clave externa y, en ese caso EXPLAIN, no se puede usar porque no emitirá tiempo para fallar consultas. No conozco ninguna forma fácil de depurar problemas de rendimiento en tal caso).

Mikko Rantalainen
fuente
8

La desactivación de los desencadenantes puede ser una amenaza para la integridad de la base de datos y no se puede recomendar; sin embargo, si está seguro de que su operación es a prueba de fallas de restricciones, puede deshabilitar los desencadenantes, con lo siguiente:SET session_replication_role = replica;

Ejecute el DELETEaquí.

Para restaurar los desencadenantes, ejecute: SET session_replication_role = DEFAULT;

Fuente aquí.

Pinimo
fuente
0

Si tiene activadores ON DELETE CASCADE, es de esperar que estén allí por algún motivo y, por lo tanto, no deberían deshabilitarse. Otro truco (todavía agrega tus índices) que funciona para mí es crear una función de eliminación que elimine manualmente los datos comenzando con las tablas al final de la cascada, y que trabaje hacia la tabla principal. (Esto es lo mismo que tendría que hacer si tuviera un activador ON DELETE RESTRICT)

CREATE TABLE tablea (
    tablea_uid integer
);

CREATE TABLE tableb (
    tableb_uid integer,
    tablea_rid integer REFERENCES tablea(tablea_uid)
);

CREATE TABLE tablec (
    tablec_uid integer,
    tableb_rid integer REFERENCES tableb(tableb_uid)
);

En este caso, elimine los datos en tablec luego tableb luego tablea

CREATE OR REPLACE FUNCTION delete_in_order()
 RETURNS void AS $$

    DELETE FROM tablec;
    DELETE FROM tableb;
    DELETE FROM tablea;

$$ LANGUAGE SQL;
ciego
fuente