Evitar el error Oracle ORA-04091 (tabla mutante)

Evitar el error Oracle ORA-04091 (tabla mutante)

Este error ocurre usualmente al realizar una consulta de un campo desde un trigger y ese campo es el que hace, que salte el trigger.

 

Vamos a ver un ejemplo, que produce este error:

-- Creamos una tabla
CREATE TABLE usuarios
(id Number,
nombre VARCHAR2(10));

-- Insertamos registros de prueba
INSERT INTO usuarios values (1, 'Pepe');
INSERT INTO usuarios values (2, 'Juan');
INSERT INTO usuarios values (3, 'Luis');
COMMIT;

-- Creamos un trigger
CREATE OR REPLACE TRIGGER TAU_usuarios
    AFTER UPDATE OF nombre ON usuarios
    FOR EACH ROW
DECLARE
    v_Contador NUMBER;
BEGIN
    SELECT count(*)
      INTO v_Contador
      FROM usuarios
     WHERE nombre = 'Luis';

    DBMS_OUTPUT.PUT_LINE(‘El número de Luises son: ‘ || v_Contador);
END;

 

Y ahora actualizamos el campo que es consultado en el trigger:

UPDATE usuarios 
   SET nombre = 'Luis' 
 WHERE id = 1;

 

Y nos salta el error de tabla mutante:

ERROR en línea 1:
ORA-04091: la tabla usuarios está mutando, puede que el disparador/la función no puedan verla
ORA-06512: en "USER.TAU_USUARIOS", línea 5
ORA-04088: error durante la ejecución del disparador 'USER.TAU_USUARIOS'

 

 

SOLUCIONES A LOS ERRORES TABLA MUTANTE (ORA-04091)

 

SOLUCIÓN 1: EVITAR TRATAMIENTO A NIVEL DE FILA

En vez de tratar a nivel de registro, se pudiera tratar a nivel de sentencia evitando poner “FOR EACH ROW”, se solventaría el problema. Claro está, siempre que lo que se pretenda con el trigger este cubierto con esta menor granularidad.

 

Quedaría de la siguiente manera:

CREATE OR REPLACE TRIGGER TAU_usuarios
     AFTER UPDATE OF nombre ON usuarios
DECLARE
    v_Contador NUMBER;
BEGIN
   SELECT count(*)
     INTO v_Contador
     FROM usuarios
    WHERE nombre = 'Luis';
    
    DBMS_OUTPUT.PUT_LINE(‘El número de Luises son: ‘ || v_Contador);
END;

 

Actualizamos otra vez:

UPDATE usuarios 
   SET nombre = 'Luis' 
 WHERE id = 1;
El número de Luises son: 2

1 fila actualizada.

 

Como vemos finaliza correctamente. Ha finalizado además indicando correctamente el número de Luises totales a 2, que son los que hay al final en la tabla.

 

 

SOLUCIÓN 2: TRANSACCIONES AUTÓNOMAS

Las transacciones autónomas es una forma de aislar distintas transacciones.
Comprobamos como está actualmente la tabla

select * from usuarios;

        ID NOMBRE
---------- ----------
         1 Luis
         2 Juan
         3 Luis

 

Creamos el trigger.

CREATE OR REPLACE TRIGGER TAU_usuarios
    AFTER UPDATE OF nombre ON usuarios
    FOR EACH ROW
DECLARE
    PRAGMA AUTONOMOUS_TRANSACTION;
    v_Contador NUMBER;

BEGIN
   SELECT count(*)
     INTO v_Contador
     FROM usuarios
    WHERE nombre = 'Luis';

    DBMS_OUTPUT.PUT_LINE(‘El número de Luises son: ‘ || v_Contador);
END;

 

Lanzamos el UPDATE:

UPDATE usuarios 
   SET nombre = 'Carmen' 
 WHERE id IN (1, 2);
El número de Luises son: 2
El número de Luises son: 2

2 filas actualizadas.

 

Pero si nos fijamos en los datos de la tabla:

select * from usuarios;

        ID NOMBRE
---------- ----------
         1 Carmen
         2 Carmen
         3 Luis

En el resultado de la traza al hacer el UPDATE aparecían 2 Luises, que son los que había iniciales y no contabilizando los registros actualizados. Por otro lado aparece dos veces, porque se han actualizado dos registros. Por lo que hay que tener mucho cuidado si se utiliza esta solución y darse cuenta de lo que podría ocurrir.

 

 

SOLUCIÓN 3: TRIGGERS COMPUESTOS (ORACLE 11G)

Esta opción sólo es válida a partir de la versión 11G y es la solución más sencilla y elegante.

CREATE OR REPLACE TRIGGER TRG_COMPUESTO
        FOR UPDATE  ON usuarios
        COMPOUND TRIGGER

    v_Contador NUMBER;

    -- Se lanzará después de cada fila actualizada
    AFTER EACH ROW IS
    BEGIN
        dbms_output.put_line('Se realizó la actualización');
    END AFTER EACH ROW;

    -- Se lanzará después de la sentencia
    AFTER STATEMENT IS
    BEGIN
       SELECT count(*)
         INTO v_Contador
         FROM usuarios
        WHERE nombre = 'Luis';

        DBMS_OUTPUT.PUT_LINE(‘El número de Luises son: ‘ || v_Contador);
    END AFTER STATEMENT;
END TRG_COMPUESTO;

 

Vamos a ver el contenido de la tabla:

SELECT *  FROM usuarios;

        ID NOMBRE
---------- ----------
         1 Carmen
         2 Carmen
         3 Luis

 

Vamos a actualizar:

El número de Luises son: 1
Se realizó la actualización
El número de Luises son: 1
Se realizó la actualización
El número de Luises son: 2

2 filas actualizadas.

 

Y el estado final:

SELECT *  FROM usuarios;

    ID NOMBRE
------ ----------
     1 Luis
     2 Carmen
     3 Luis

En la actualización nos aparece previamente a cada registro la cantidad de Luises, que había y después de la actualización se ve el total de Luises actualizados.

Comments are closed.