Voici une méthode simple :
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 contiendra 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 ce faire, un index à deux colonnes (composite) est créé sur la colonne de clé primaire et la colonne de révision. Notez que vous ne pouvez faire le 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.
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);
Et puis vous créez les déclencheurs :
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;
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 un tableau d'historique comme celui-ci (moins la colonne artificielle '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
Pour afficher les modifications d'une ou plusieurs colonnes données d'une mise à jour à l'autre, vous devez 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 :
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
Edit : Oh wow, les gens aiment ma table d'histoire d'il y a 6 ans :P
Ma mise en œuvre continue de bourdonner, devenant plus grande et plus lourde, je suppose. J'ai écrit des vues et une interface utilisateur assez agréable pour consulter l'historique de cette base de données, mais je ne pense pas qu'elle ait jamais été beaucoup utilisée. Alors ça marche.
Pour répondre à certains commentaires sans ordre particulier :
-
J'ai fait ma propre implémentation en PHP qui était un peu plus impliquée et j'ai évité certains des problèmes décrits dans les commentaires (avoir des index transférés, de manière significative. Si vous transférez des index uniques vers la table d'historique, les choses vont se casser. Il existe des solutions pour ceci dans les commentaires). Suivre cet article à la lettre pourrait être une aventure, selon le degré d'établissement de votre base de données.
-
Si la relation entre la clé primaire et la colonne de révision semble désactivée, cela signifie généralement que la clé composite est interrompue d'une manière ou d'une autre. En quelques rares occasions, cela m'est arrivé et j'étais perdu pour la cause.
-
J'ai trouvé cette solution assez performante, en utilisant des déclencheurs comme elle le fait. De plus, MyISAM est rapide pour les insertions, ce que font tous 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 vraiment optimiser, à moins que vous n'ayez des problèmes importants ailleurs. Pendant tout le temps où j'ai exécuté la base de données MySQL, cette implémentation de la table d'historique n'a jamais été la cause des (nombreux) problèmes de performances qui se sont produits.
-
si vous obtenez 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 plus 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 insert/update/del 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éclenchement), et 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. Est-ce que MySQL a ça maintenant ? Je réagis ces temps-ci :P