sql >> Base de Datos >  >> RDS >> Mysql

¿Existe una opción/característica de MySQL para rastrear el historial de cambios en los registros?

Esta es una forma sencilla de hacerlo:

Primero, cree una tabla de historial para cada tabla de datos que desee rastrear (consulte el ejemplo a continuación). Esta tabla tendrá una entrada para cada consulta de inserción, actualización y eliminación realizada en cada fila de la tabla de datos.

La estructura de la tabla de historial será la misma que la de la tabla de datos que rastrea, excepto por tres columnas adicionales:una columna para almacenar la operación que ocurrió (llamémosla 'acción'), la fecha y hora de la operación y una columna para almacenar un número de secuencia ("revisión"), que se incrementa por operación y se agrupa por la columna de clave principal de la tabla de datos.

Para realizar este comportamiento de secuenciación, se crea un índice de dos columnas (compuesto) en la columna de clave principal y la columna de revisión. Tenga en cuenta que solo puede secuenciar de esta manera si el motor utilizado por la tabla de historial es MyISAM (Ver 'Notas de MyISAM' en esta página)

La tabla de historial es bastante fácil de crear. En la consulta ALTER TABLE a continuación (y en las consultas desencadenantes debajo), reemplace 'primary_key_column' con el nombre real de esa columna en su tabla de datos.

CREATE TABLE MyDB.data_history LIKE MyDB.data;

ALTER TABLE MyDB.data_history MODIFY COLUMN primary_key_column int(11) NOT NULL, 
   DROP PRIMARY KEY, ENGINE = MyISAM, ADD action VARCHAR(8) DEFAULT 'insert' FIRST, 
   ADD revision INT(6) NOT NULL AUTO_INCREMENT AFTER action,
   ADD dt_datetime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER revision,
   ADD PRIMARY KEY (primary_key_column, revision);

Y luego creas los activadores:

DROP TRIGGER IF EXISTS MyDB.data__ai;
DROP TRIGGER IF EXISTS MyDB.data__au;
DROP TRIGGER IF EXISTS MyDB.data__bd;

CREATE TRIGGER MyDB.data__ai AFTER INSERT ON MyDB.data FOR EACH ROW
    INSERT INTO MyDB.data_history SELECT 'insert', NULL, NOW(), d.* 
    FROM MyDB.data AS d WHERE d.primary_key_column = NEW.primary_key_column;

CREATE TRIGGER MyDB.data__au AFTER UPDATE ON MyDB.data FOR EACH ROW
    INSERT INTO MyDB.data_history SELECT 'update', NULL, NOW(), d.*
    FROM MyDB.data AS d WHERE d.primary_key_column = NEW.primary_key_column;

CREATE TRIGGER MyDB.data__bd BEFORE DELETE ON MyDB.data FOR EACH ROW
    INSERT INTO MyDB.data_history SELECT 'delete', NULL, NOW(), d.* 
    FROM MyDB.data AS d WHERE d.primary_key_column = OLD.primary_key_column;

Y tu estas listo. Ahora, todas las inserciones, actualizaciones y eliminaciones en 'MyDb.data' se registrarán en 'MyDb.data_history', brindándole una tabla de historial como esta (menos la columna artificial 'data_columns')

ID    revision   action    data columns..
1     1         'insert'   ....          initial entry for row where ID = 1
1     2         'update'   ....          changes made to row where ID = 1
2     1         'insert'   ....          initial entry, ID = 2
3     1         'insert'   ....          initial entry, ID = 3 
1     3         'update'   ....          more changes made to row where ID = 1
3     2         'update'   ....          changes made to row where ID = 3
2     2         'delete'   ....          deletion of row where ID = 2 

Para mostrar los cambios de una columna o columnas dadas de una actualización a otra, deberá unir la tabla de historial a sí misma en las columnas de clave principal y secuencia. Podría crear una vista para este propósito, por ejemplo:

CREATE VIEW data_history_changes AS 
   SELECT t2.dt_datetime, t2.action, t1.primary_key_column as 'row id', 
   IF(t1.a_column = t2.a_column, t1.a_column, CONCAT(t1.a_column, " to ", t2.a_column)) as a_column
   FROM MyDB.data_history as t1 INNER join MyDB.data_history as t2 on t1.primary_key_column = t2.primary_key_column 
   WHERE (t1.revision = 1 AND t2.revision = 1) OR t2.revision = t1.revision+1
   ORDER BY t1.primary_key_column ASC, t2.revision ASC

Editar:Oh, vaya, a la gente le gusta mi tabla de historia de hace 6 años:P

Mi implementación sigue funcionando, cada vez más grande y más difícil de manejar, supongo. Escribí vistas y una interfaz de usuario bastante agradable para ver el historial en esta base de datos, pero no creo que se haya usado mucho. Así es.

Para abordar algunos comentarios sin ningún orden en particular:

  • Hice mi propia implementación en PHP que fue un poco más complicada y evité algunos de los problemas descritos en los comentarios (significativamente, tener índices transferidos). Si transfiere índices únicos a la tabla de historial, las cosas se romperán. Hay soluciones para esto en los comentarios). Seguir esta publicación al pie de la letra podría ser una aventura, dependiendo de qué tan establecida esté su base de datos.

  • Si la relación entre la clave principal y la columna de revisión parece incorrecta, generalmente significa que la clave compuesta está alterada de alguna manera. En algunas raras ocasiones me sucedió esto y estaba perdido para la causa.

  • Encontré que esta solución es bastante eficaz, usando disparadores como lo hace. Además, MyISAM es rápido en las inserciones, que es todo lo que hacen los activadores. Puede mejorar esto aún más con la indexación inteligente (o la falta de...). Insertar una sola fila en una tabla MyISAM con una clave principal no debería ser una operación que deba optimizar, en realidad, a menos que tenga problemas importantes en otro lugar. En todo el tiempo que estuve ejecutando la base de datos MySQL, esta implementación de la tabla de historial estuvo activada, nunca fue la causa de ninguno de los (muchos) problemas de rendimiento que surgieron.

  • si recibe inserciones repetidas, verifique su capa de software para consultas de tipo INSERT IGNORE. Hrmm, no puedo recordar ahora, pero creo que hay problemas con este esquema y las transacciones que finalmente fallan después de ejecutar múltiples acciones DML. Algo a tener en cuenta, al menos.

  • Es importante que los campos de la tabla de historial y la tabla de datos coincidan. O, más bien, que su tabla de datos no tiene MÁS columnas que la tabla de historial. De lo contrario, las consultas de inserción/actualización/eliminación en la tabla de datos fallarán, cuando las inserciones en las tablas de historial coloquen columnas en la consulta que no existen (debido a d.* en las consultas desencadenantes), y el desencadenador falla. Sería increíble si MySQL tuviera algo así como activadores de esquema, donde pudiera modificar la tabla de historial si se agregaran columnas a la tabla de datos. ¿MySQL tiene eso ahora? Reacciono estos días :P