C'est subtil.
Si l'exigence métier est "Je veux auditer les modifications apportées aux données - qui a fait quoi et quand?", Vous pouvez généralement utiliser des tables d'audit (selon l'exemple de déclencheur publié par Keethanjan). Je ne suis pas un grand fan des déclencheurs, mais cela a le grand avantage d'être relativement simple à mettre en œuvre - votre code existant n'a pas besoin de connaître les déclencheurs et les audits.
Si l'exigence métier est "montre-moi quel était l'état des données à une date donnée dans le passé", cela signifie que l'aspect du changement dans le temps est entré dans votre solution. Bien que vous puissiez, à peu près, reconstruire l'état de la base de données simplement en regardant les tables d'audit, c'est difficile et sujet aux erreurs, et pour toute logique de base de données compliquée, cela devient compliqué. Par exemple, si l'entreprise veut savoir "trouver les adresses des lettres que nous aurions dû envoyer aux clients qui avaient des factures impayées et impayées le premier jour du mois", vous devrez probablement parcourir une demi-douzaine de tableaux d'audit.
Au lieu de cela, vous pouvez intégrer le concept de changement au fil du temps dans la conception de votre schéma (c'est la deuxième option suggérée par Keethanjan). Il s'agit d'une modification de votre application, certainement au niveau de la logique métier et de la persistance, donc ce n'est pas anodin.
Par exemple, si vous avez une table comme celle-ci:
CUSTOMER
---------
CUSTOMER_ID PK
CUSTOMER_NAME
CUSTOMER_ADDRESS
et vous vouliez garder une trace au fil du temps, vous le modifieriez comme suit:
CUSTOMER
------------
CUSTOMER_ID PK
CUSTOMER_VALID_FROM PK
CUSTOMER_VALID_UNTIL PK
CUSTOMER_STATUS
CUSTOMER_USER
CUSTOMER_NAME
CUSTOMER_ADDRESS
Chaque fois que vous souhaitez modifier un enregistrement client, au lieu de mettre à jour l'enregistrement, vous définissez VALID_UNTIL sur l'enregistrement actuel sur NOW () et insérez un nouvel enregistrement avec un VALID_FROM (maintenant) et un VALID_UNTIL nul. Vous définissez le statut "CUSTOMER_USER" sur l'ID de connexion de l'utilisateur actuel (si vous devez le conserver). Si le client doit être supprimé, vous utilisez l'indicateur CUSTOMER_STATUS pour l'indiquer - vous ne pouvez jamais supprimer des enregistrements de cette table.
De cette façon, vous pouvez toujours trouver quel était le statut de la table client à une date donnée - quelle était l'adresse? Ont-ils changé de nom? En vous joignant à d'autres tables avec des dates valid_from et valid_until similaires, vous pouvez reconstruire l'image entière de manière historique. Pour trouver l'état actuel, vous recherchez des enregistrements avec une date VALID_UNTIL nulle.
C'est peu maniable (à proprement parler, vous n'avez pas besoin de valid_from, mais cela rend les requêtes un peu plus faciles). Cela complique votre conception et votre accès à la base de données. Mais cela rend la reconstruction du monde beaucoup plus facile.
Voici un moyen simple de le faire:
Tout d'abord, créez une table d'historique pour chaque table de données que vous souhaitez suivre (exemple de requête ci-dessous). Cette table aura une entrée pour chaque requête d'insertion, de mise à jour et de suppression effectuée sur chaque ligne de la table de données.
La structure de la table d'historique sera la même que la table de données qu'elle suit à l'exception de trois colonnes supplémentaires: une colonne pour stocker l'opération qui s'est produite (appelons-la `` action ''), la date et l'heure de l'opération, et une colonne pour stocker un numéro de séquence ('révision'), qui s'incrémente par opération et est regroupé par la colonne de clé primaire de la table de données.
Pour effectuer ce comportement de séquencement, un index à deux colonnes (composite) est créé sur la colonne de clé primaire et la colonne de révision. Notez que vous ne pouvez effectuer de séquençage de cette manière que si le moteur utilisé par la table d'historique est MyISAM ( voir 'MyISAM Notes' sur cette page)
La table d'historique est assez facile à créer. Dans la requête ALTER TABLE ci-dessous (et dans les requêtes de déclenchement ci-dessous), remplacez «primary_key_column» par le nom réel de cette colonne dans votre table de données.
Et puis vous créez les déclencheurs:
Et tu as fini. Désormais, toutes les insertions, mises à jour et suppressions dans 'MyDb.data' seront enregistrées dans 'MyDb.data_history', vous donnant une table d'historique comme celle-ci (moins la colonne 'data_columns' artificielle)
Pour afficher les modifications d'une ou plusieurs colonnes données de mise à jour à mise à jour, vous devrez joindre la table d'historique à elle-même sur les colonnes de clé primaire et de séquence. Vous pouvez créer une vue à cet effet, par exemple:
Edit: Oh wow, les gens aiment ma table d'histoire d'il y a 6 ans: P
Ma mise en œuvre continue de fredonner, de plus en plus grande et de plus en plus lourde, je suppose. J'ai écrit des vues et une interface utilisateur assez sympa pour regarder l'historique de cette base de données, mais je ne pense pas qu'elle ait jamais été beaucoup utilisée. Alors ça va.
Pour répondre à certains commentaires sans ordre particulier:
J'ai fait ma propre implémentation en PHP qui était un peu plus complexe, et j'ai évité certains des problèmes décrits dans les commentaires (transfert d'index, de manière significative. Si vous transférez des index uniques vers la table d'historique, les choses vont casser. Il existe des solutions pour ceci dans les commentaires). Suivre ce message à la lettre pourrait être une aventure, selon la façon dont votre base de données est établie.
Si la relation entre la clé primaire et la colonne de révision semble incorrecte, cela signifie généralement que la clé composite est bloquée d'une manière ou d'une autre. À quelques rares occasions, cela s'est produit et j'ai perdu la cause.
J'ai trouvé cette solution assez performante, utilisant des déclencheurs comme elle le fait. De plus, MyISAM est rapide aux insertions, ce que font les déclencheurs. Vous pouvez encore améliorer cela avec une indexation intelligente (ou l'absence de ...). L'insertion d'une seule ligne dans une table MyISAM avec une clé primaire ne devrait pas être une opération que vous devez optimiser, vraiment, à moins que vous ayez des problèmes importants ailleurs. Pendant tout le temps où j'exécutais la base de données MySQL sur laquelle était implémentée cette table d'historique, cela n'a jamais été la cause des (nombreux) problèmes de performances qui se sont posés.
si vous recevez des insertions répétées, vérifiez votre couche logicielle pour les requêtes de type INSERT IGNORE. Hrmm, je ne m'en souviens pas maintenant, mais je pense qu'il y a des problèmes avec ce schéma et les transactions qui échouent finalement après l'exécution de plusieurs actions DML. Quelque chose dont il faut être conscient au moins.
Il est important que les champs de la table d'historique et de la table de données correspondent. Ou, plutôt, que votre table de données n'a pas PLUS de colonnes que la table d'historique. Sinon, les requêtes d'insertion / mise à jour / suppression sur la table de données échoueront, lorsque les insertions dans les tables d'historique placent des colonnes dans la requête qui n'existent pas (en raison de d. * Dans les requêtes de déclencheur) et que le déclencheur échoue. Ce serait génial si MySQL avait quelque chose comme des déclencheurs de schéma, où vous pourriez modifier la table d'historique si des colonnes étaient ajoutées à la table de données. MySQL a-t-il cela maintenant? Je réagis ces jours-ci: P
la source
CREATE TABLE MyDB.data_history as select * from MyDB.data limit 0;
owner
champ, et pour la mise à jour, je pourrais ajouter unupdatedby
champ, mais pour supprimer, je ne sais pas comment je pourrais le faire via les déclencheurs. mettre à jour ladata_history
ligne avec l'identifiant de l'utilisateur dans se sent sale: PVous pouvez créer des déclencheurs pour résoudre ce problème. Voici un tutoriel pour le faire (lien archivé).
Une autre solution serait de conserver un champ de révision et de mettre à jour ce champ lors de l'enregistrement. Vous pouvez décider que le max est la dernière révision ou que 0 est la ligne la plus récente. C'est à toi de voir.
la source
Voici comment nous l'avons résolu
une table Utilisateurs ressemblait à ceci
Et les exigences commerciales ont changé et nous devions vérifier toutes les adresses et numéros de téléphone précédents qu'un utilisateur ait jamais eu. le nouveau schéma ressemble à ceci
Pour trouver l'adresse actuelle d'un utilisateur, nous recherchons UserData avec la révision DESC et LIMIT 1
Pour obtenir l'adresse d'un utilisateur entre une certaine période, nous pouvons utiliser created_on bewteen (date1, date 2)
la source
revision=1
l 'id_user=1
? D'abord, je pensais que votre comptage était0,2,3,...
mais ensuite j'ai vu que pourid_user=2
le comptage des révisions est0,1, ...
id
et lesid_user
colonnes. Just use a group ID of
id` (ID utilisateur) etrevision
.MariaDB prend en charge la gestion des versions du système depuis la version 10.3, qui est la fonction SQL standard qui fait exactement ce que vous voulez: elle stocke l'historique des enregistrements de table et y donne accès via des
SELECT
requêtes. MariaDB est un fork de MySQL à développement ouvert. Vous pouvez en savoir plus sur son système de versioning via ce lien:https://mariadb.com/kb/en/library/system-versioned-tables/
la source
Pourquoi ne pas simplement utiliser les fichiers journaux bin? Si la réplication est définie sur le serveur Mysql et que le format de fichier binlog est défini sur ROW, toutes les modifications peuvent être capturées.
Une bonne bibliothèque python appelée noplay peut être utilisée. Plus d'infos ici .
la source
Juste mes 2 cents. Je créerais une solution qui enregistre exactement ce qui a changé, très similaire à la solution de transitoire.
Ma table des modifications serait simple:
DateTime | WhoChanged | TableName | Action | ID |FieldName | OldValue
1) Lorsqu'une ligne entière est modifiée dans la table principale, beaucoup d'entrées iront dans cette table, MAIS c'est très peu probable, donc pas un gros problème (les gens ne changent généralement qu'une chose) 2) OldVaue (et NewValue si vous want) doit être une sorte de "anytype" épique car il peut s'agir de n'importe quelle donnée, il pourrait y avoir un moyen de le faire avec des types RAW ou simplement en utilisant des chaînes JSON pour convertir en entrée et en sortie.
Utilisation minimale des données, stocke tout ce dont vous avez besoin et peut être utilisé pour toutes les tables à la fois. Je fais des recherches moi-même en ce moment, mais cela pourrait finir par être la voie à suivre.
Pour créer et supprimer, juste l'ID de ligne, aucun champ nécessaire. Sur supprimer un drapeau sur la table principale (actif?) Serait bien.
la source
La manière directe de le faire est de créer des déclencheurs sur les tables. Définissez des conditions ou des méthodes de mappage. Lorsque la mise à jour ou la suppression se produit, elle s'insère automatiquement dans la table de «modification».
Mais la plus grande partie est que se passe-t-il si nous avons beaucoup de colonnes et beaucoup de tables. Nous devons taper le nom de chaque colonne de chaque table. De toute évidence, c'est une perte de temps.
Pour gérer cela plus magnifiquement, nous pouvons créer des procédures ou des fonctions pour récupérer le nom des colonnes.
Nous pouvons également utiliser un outil 3ème partie simplement pour ce faire. Ici, j'écris un programme java Mysql Tracker
la source
create table like table
Je pense qu'il reproduit facilement toutes les colonnes