Antipatterns PostgreSQL: cambio de datos sin pasar por un disparador

Tarde o temprano, muchos se enfrentan a la necesidad de arreglar masivamente algo en las entradas de la tabla. Ya dije cómo hacerlo mejor y cómo, es mejor no hacerlo. Hoy hablaré sobre el segundo aspecto de la actualización masiva: los desencadenantes .

Por ejemplo, en una mesa en la que necesita arreglar algo, se cuelga un gatillo malvado que ON UPDATEtransfiere todos los cambios a algunos agregados. Y debe actualizar todo (inicializar un nuevo campo, por ejemplo) con tanto cuidado que estas unidades no se vean afectadas.

¡Solo apaguemos los disparadores!


BEGIN;
  ALTER TABLE ... DISABLE TRIGGER ...;
  UPDATE ...; --  -
  ALTER TABLE ... ENABLE TRIGGER ...;
COMMIT;

En realidad, eso es todo: todo ya está colgando .

Debido a que ALTER TABLEimpone un bloqueo AccessExclusive , bajo el cual nadie que se ejecuta en paralelo, incluso uno simple SELECT, puede leer cualquier cosa de la tabla. Es decir, hasta que se complete esta transacción, todos los que quieran "simplemente leer" esperarán. Y recordamos que UPDATEhacemos-o-olgi ...

¡Apagémoslo rápidamente, luego enciéndalo rápidamente!


BEGIN;
  ALTER TABLE ... DISABLE TRIGGER ...;
COMMIT;

UPDATE ...;

BEGIN;
  ALTER TABLE ... ENABLE TRIGGER ...;
COMMIT;

Aquí la situación es mejor, el tiempo de espera es mucho más corto. Pero solo dos problemas estropean toda la belleza:

  • ALTER TABLE él espera todas las demás operaciones sobre la mesa, incluso largas SELECT
  • Mientras el gatillo esté apagado, cualquier cambio en la tabla pasará volando , ni siquiera el nuestro. Y bueno, no entrará en las unidades, aunque debería. ¡Problema!

Gestión de variables de sesión


Entonces, en la versión anterior, nos topamos con un punto crucial: necesitamos enseñar de alguna manera el disparador para distinguir "nuestros" cambios en la tabla de "no los nuestros". Omita "el nuestro" tal como está, pero "no nuestro" - disparador. Puede usar variables de sesión para esto .

session_replication_role


Lee el manual :
El mecanismo de activación también se ve afectado por la variable de configuración session_replication_role . Los activadores que están habilitados sin instrucciones adicionales (predeterminado) se activarán cuando el rol de replicación sea "origen" (predeterminado) o "local". Los disparadores habilitados por una indicación ENABLE REPLICAse dispararán solo si el modo de sesión actual es "réplica", y los disparadores habilitados por una indicación ENABLE ALWAYSse dispararán independientemente del modo de replicación actual.
Insisto en que la configuración no se aplica a todo a la vez ALTER TABLE, sino solo a nuestra conexión especial separada. Total para que ninguna aplicación active el fuego:

SET session_replication_role = replica; --  
UPDATE ...;
SET session_replication_role = DEFAULT; --    

Condición dentro del gatillo


Pero la opción anterior funciona para todos los desencadenantes a la vez (o necesita "desencadenar" desencadenantes por adelantado que no desea deshabilitar). ¿Y si necesitamos "apagar" un disparador específico ?

La variable de sesión "usuario" nos ayudará con esto :
Los nombres de los parámetros de extensión se escriben de la siguiente manera: nombre de la extensión, punto y luego el nombre del parámetro en sí, como los nombres completos de los objetos en SQL. Por ejemplo: plpgsql.variable_conflict.
Dado que los parámetros que no son del sistema se pueden establecer en procesos que no cargan el módulo de extensión correspondiente, PostgreSQL acepta valores para cualquier nombre con dos componentes .
Primero, modifique el disparador, algo como esto:
BEGIN
    --     
    IF current_setting('mycfg.my_table_convert_process') = 'TRUE' THEN
        IF TG_OP IN ('INSERT', 'UPDATE') THEN
            RETURN NEW;
        ELSE
            RETURN OLD;
        END IF;
    END IF;
...

Por cierto, esto se puede hacer de manera "rentable", sin bloqueos, a través CREATE OR REPLACEde una función de disparo. Y luego, en la conexión especial, seleccionamos "nuestra" variable:

SET mycfg.my_table_convert_process = 'TRUE';
UPDATE ...;
SET mycfg.my_table_convert_process = ''; --    

¿Conoces otras formas? Comparte en los comentarios.

All Articles