Hier is een eenvoudige manier om dit te doen:
Maak eerst een geschiedenistabel voor elke gegevenstabel die u wilt bijhouden (voorbeeldquery hieronder). Deze tabel heeft een vermelding voor elke invoeg-, update- en verwijderquery die op elke rij in de gegevenstabel wordt uitgevoerd.
De structuur van de geschiedenistabel zal hetzelfde zijn als de gegevenstabel die wordt bijgehouden, behalve drie extra kolommen:een kolom om de uitgevoerde bewerking op te slaan (laten we het 'actie' noemen), de datum en tijd van de bewerking en een kolom om een volgnummer ('revisie') op te slaan, dat per bewerking oploopt en is gegroepeerd op de primaire sleutelkolom van de gegevenstabel.
Om dit sequencing-gedrag te doen, wordt een tweekoloms (samengestelde) index gemaakt op de primaire sleutelkolom en de revisiekolom. Merk op dat u alleen op deze manier sequencing kunt uitvoeren als de engine die door de geschiedenistabel wordt gebruikt MyISAM is (Zie 'MijnISAM-opmerkingen' op deze pagina)
De geschiedenistabel is vrij eenvoudig te maken. Vervang in de ALTER TABLE-query hieronder (en in de triggerquery's daaronder) 'primary_key_column' door de werkelijke naam van die kolom in uw gegevenstabel.
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);
En dan creëer je de triggers:
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;
En je bent klaar. Nu worden alle toevoegingen, updates en verwijderingen in 'MyDb.data' opgenomen in 'MyDb.data_history', waardoor u een geschiedenistabel krijgt zoals deze (minus de gekunstelde kolom '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
Om de wijzigingen voor een bepaalde kolom of kolommen van update tot update weer te geven, moet u de geschiedenistabel samenvoegen met zichzelf op de primaire sleutel- en reekskolommen. U kunt hiervoor een weergave maken, bijvoorbeeld:
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
Bewerken:Oh wauw, mensen houden van mijn geschiedenistafelding van 6 jaar geleden :P
Mijn implementatie ervan neuriet nog steeds voort, wordt groter en onpraktischer, neem ik aan. Ik heb views en een mooie gebruikersinterface geschreven om naar de geschiedenis in deze database te kijken, maar ik denk niet dat het ooit veel is gebruikt. Zo gaat het.
Om enkele opmerkingen in willekeurige volgorde te behandelen:
-
Ik deed mijn eigen implementatie in PHP die wat ingewikkelder was, en vermeed een aantal van de problemen die in opmerkingen worden beschreven (het overdragen van indexen is aanzienlijk. Als je unieke indexen overzet naar de geschiedenistabel, gaan dingen kapot. Er zijn oplossingen voor dit in de reacties). Het tot op de letter volgen van dit bericht kan een avontuur zijn, afhankelijk van hoe goed je database is.
-
Als de relatie tussen de primaire sleutel en de revisiekolom niet klopt, betekent dit meestal dat de samengestelde sleutel op de een of andere manier defect is. In een paar zeldzame gevallen heb ik dit meegemaakt en wist ik de oorzaak niet.
-
Ik vond deze oplossing behoorlijk performant, met behulp van triggers zoals het doet. MyISAM is ook snel met inserts, en dat is alles wat de triggers doen. Je kunt dit verder verbeteren met slimme indexering (of het ontbreken van...). Het invoegen van een enkele rij in een MyISAM-tabel met een primaire sleutel zou eigenlijk geen bewerking moeten zijn die u moet optimaliseren, tenzij u ergens anders grote problemen ondervindt. Gedurende de hele tijd dat ik de MySQL-database draaide, was deze geschiedenistabel-implementatie aan, het was nooit de oorzaak van een van de (vele) prestatieproblemen die naar voren kwamen.
-
als je herhaalde invoegingen krijgt, controleer dan je softwarelaag op INSERT IGNORE type queries. Hmm, ik weet het nu niet meer, maar ik denk dat er problemen zijn met dit schema en transacties die uiteindelijk mislukken na het uitvoeren van meerdere DML-acties. Iets om op te letten, tenminste.
-
Het is belangrijk dat de velden in de geschiedenistabel en de gegevenstabel overeenkomen. Of liever gezegd, dat uw gegevenstabel niet MEER kolommen heeft dan de geschiedenistabel. Anders mislukt het invoegen/bijwerken/verwijderen van query's op de gegevenstabel, wanneer de invoegingen in de geschiedenistabellen kolommen in de query plaatsen die niet bestaan (vanwege d.* in de triggerquery's), en de trigger mislukt. Het zou geweldig zijn als MySQL zoiets had als schema-triggers, waar je de geschiedenistabel zou kunnen wijzigen als er kolommen aan de gegevenstabel werden toegevoegd. Heeft MySQL dat nu? Ik reageer tegenwoordig wel :P