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.
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 une table d'historique comme celle-ci (moins la colonne 'data_columns' artificielle)
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 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:
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 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