Es sutil.
Si el requisito empresarial es "Quiero auditar los cambios en los datos, ¿quién hizo qué y cuándo?", Normalmente puede utilizar tablas de auditoría (según el ejemplo de activación que publicó Keethanjan). No soy un gran admirador de los desencadenantes, pero tiene la gran ventaja de ser relativamente fácil de implementar: su código existente no necesita saber sobre los desencadenantes y las cosas de auditoría.
Si el requisito comercial es "muéstreme cuál era el estado de los datos en una fecha determinada en el pasado", significa que el aspecto del cambio a lo largo del tiempo ha entrado en su solución. Si bien puede, prácticamente, reconstruir el estado de la base de datos con solo mirar las tablas de auditoría, es difícil y propenso a errores, y para cualquier lógica de base de datos complicada, se vuelve difícil de manejar. Por ejemplo, si la empresa quiere saber "encontrar las direcciones de las cartas que deberíamos haber enviado a los clientes que tenían facturas pendientes de pago el primer día del mes", es probable que tenga que rastrear media docena de tablas de auditoría.
En su lugar, puede incorporar el concepto de cambio a lo largo del tiempo en el diseño de su esquema (esta es la segunda opción que sugiere Keethanjan). Este es un cambio en su aplicación, definitivamente a nivel de lógica empresarial y persistencia, por lo que no es trivial.
Por ejemplo, si tiene una mesa como esta:
CUSTOMER
---------
CUSTOMER_ID PK
CUSTOMER_NAME
CUSTOMER_ADDRESS
y desea realizar un seguimiento a lo largo del tiempo, lo modificaría de la siguiente manera:
CUSTOMER
------------
CUSTOMER_ID PK
CUSTOMER_VALID_FROM PK
CUSTOMER_VALID_UNTIL PK
CUSTOMER_STATUS
CUSTOMER_USER
CUSTOMER_NAME
CUSTOMER_ADDRESS
Cada vez que desee cambiar un registro de cliente, en lugar de actualizar el registro, configure VALID_UNTIL en el registro actual en NOW () e inserte un nuevo registro con VALID_FROM (ahora) y VALID_UNTIL nulo. Establece el estado "CUSTOMER_USER" en el ID de inicio de sesión del usuario actual (si necesita conservarlo). Si es necesario eliminar el cliente, utilice la marca CUSTOMER_STATUS para indicarlo; nunca puede eliminar registros de esta tabla.
De esa manera, siempre puede encontrar el estado de la tabla de clientes para una fecha determinada: ¿cuál era la dirección? ¿Han cambiado de nombre? Al unirse a otras tablas con fechas valid_from y valid_until similares, puede reconstruir la imagen completa históricamente. Para encontrar el estado actual, busque registros con una fecha VALID_UNTIL nula.
Es difícil de manejar (estrictamente hablando, no necesita valid_from, pero facilita un poco las consultas). Complica su diseño y el acceso a su base de datos. Pero hace que reconstruir el mundo sea mucho más fácil.
Aquí tienes una forma sencilla de hacer esto:
Primero, cree una tabla de historial para cada tabla de datos que desee rastrear (consulta de 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 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 realizar la secuenciación de esta manera si el motor utilizado por la tabla de historial es MyISAM ( consulte '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 de activación debajo de eso), reemplace 'primary_key_column' con el nombre real de esa columna en su tabla de datos.
Y luego creas los disparadores:
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')
Para mostrar los cambios para una columna o columnas determinadas de actualización a actualización, deberá unir la tabla de historial a sí misma en la clave principal y las columnas de secuencia. Puede crear una vista para este propósito, por ejemplo:
Editar: Oh wow, a la gente le gusta mi tabla de historial de hace 6 años: P
Mi implementación sigue avanzando, haciéndose 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í que va.
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 (tener índices transferidos, significativamente. Si transfieres í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é tu 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á bloqueada de alguna manera. En algunas raras ocasiones me sucedió esto y no sabía la causa.
Encontré que esta solución es bastante eficaz, utilizando desencadenadores como lo hace. Además, MyISAM es rápido en inserciones, que es todo lo que hacen los desencadenantes. 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. Durante todo el tiempo que estuve ejecutando la base de datos MySQL en la que estuvo la implementación de la tabla de historial, nunca fue la causa de ninguno de los (muchos) problemas de rendimiento que surgieron.
si recibe inserciones repetidas, verifique su capa de software para las consultas de tipo INSERT IGNORE. Mmmm, no puedo recordar ahora, pero creo que hay problemas con este esquema y 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 del historial coloquen columnas en la consulta que no existen (debido a d. * En las consultas de activación) y el activador falla. Sería increíble si MySQL tuviera algo como activadores de esquema, donde podría alterar la tabla de historial si se agregan columnas a la tabla de datos. ¿MySQL tiene eso ahora? Reacciono estos días: P
fuente
CREATE TABLE MyDB.data_history as select * from MyDB.data limit 0;
owner
campo, y para actualizar podría agregar unupdatedby
campo, pero para eliminar, no estoy seguro de cómo podría hacerlo a través de los activadores. actualizar ladata_history
fila con la identificación de usuario en se siente sucio: PPodría crear desencadenantes para resolver esto. Aquí hay un tutorial para hacerlo (enlace archivado).
Otra solución sería mantener un campo de Revisión y actualizar este campo al guardar. Puede decidir que el máximo es la revisión más reciente o que 0 es la fila más reciente. Eso depende de usted.
fuente
Así es como lo solucionamos
una tabla de usuarios se veía así
Y el requisito comercial cambió y necesitábamos verificar todas las direcciones y números de teléfono anteriores que un usuario haya tenido. el nuevo esquema se ve así
Para encontrar la dirección actual de cualquier usuario, buscamos UserData con revisión DESC y LIMIT 1
Para obtener la dirección de un usuario entre un cierto período de tiempo, podemos usar created_on bewteen (fecha1, fecha 2)
fuente
revision=1
de laid_user=1
? Primero pensé que su conteo era,0,2,3,...
pero luego vi que paraid_user=2
el conteo de revisiones es0,1, ...
id
yid_user
columnas. Just use a group ID of
id` (ID de usuario) yrevision
.MariaDB admite el control de versiones del sistema desde la versión 10.3, que es la función estándar de SQL que hace exactamente lo que usted desea: almacena el historial de los registros de la tabla y proporciona acceso a él mediante
SELECT
consultas. MariaDB es una bifurcación de desarrollo abierto de MySQL. Puede encontrar más información sobre el control de versiones del sistema a través de este enlace:https://mariadb.com/kb/en/library/system-versioned-tables/
fuente
¿Por qué no utilizar simplemente archivos de registro bin? Si la replicación está configurada en el servidor Mysql y el formato de archivo binlog está configurado en ROW, entonces se pueden capturar todos los cambios.
Se puede usar una buena biblioteca de Python llamada noplay. Más info aquí .
fuente
Solo mis 2 centavos. Crearía una solución que registre exactamente lo que cambió, muy similar a la solución de transient.
Mi CambiosTable sería simple:
DateTime | WhoChanged | TableName | Action | ID |FieldName | OldValue
1) Cuando se cambia una fila completa en la tabla principal, muchas entradas entrarán en esta tabla, PERO eso es muy poco probable, por lo que no es un gran problema (las personas generalmente solo cambian una cosa) 2) OldVaue (y NewValue si want) tiene que ser una especie de "anytype" épico, ya que podría ser cualquier dato, podría haber una manera de hacer esto con tipos RAW o simplemente usando cadenas JSON para convertir dentro y fuera.
Uso mínimo de datos, almacena todo lo que necesita y se puede utilizar para todas las tablas a la vez. Estoy investigando esto yo mismo en este momento, pero podría terminar siendo mi camino.
Para Crear y Eliminar, solo el ID de fila, no se necesitan campos. Sería bueno eliminar una bandera en la tabla principal (¿activa?).
fuente
La forma directa de hacer esto es crear disparadores en tablas. Establezca algunas condiciones o métodos de mapeo. Cuando se realiza una actualización o eliminación, se insertará automáticamente en la tabla de "cambios".
Pero la mayor parte es qué pasaría si tuviéramos muchas columnas y muchas tablas. Tenemos que escribir el nombre de cada columna de cada tabla. Obviamente, es una pérdida de tiempo.
Para manejar esto de manera más hermosa, podemos crear algunos procedimientos o funciones para recuperar el nombre de las columnas.
También podemos usar la herramienta de tercera parte simplemente para hacer esto. Aquí, escribo un programa java Mysql Tracker
fuente
create table like table
Creo que replica todas las columnas fácilmente