Mysql
 sql >> Database >  >> RDS >> Mysql

Esiste un'opzione/funzione MySQL per tenere traccia della cronologia delle modifiche ai record?

Ecco un modo semplice per farlo:

Innanzitutto, crea una tabella della cronologia per ogni tabella di dati che desideri monitorare (interrogazione di esempio di seguito). Questa tabella conterrà una voce per ogni query di inserimento, aggiornamento ed eliminazione eseguita su ciascuna riga della tabella dati.

La struttura della tabella di cronologia sarà la stessa della tabella di dati di cui tiene traccia tranne che per tre colonne aggiuntive:una colonna per memorizzare l'operazione avvenuta (chiamiamola 'azione'), la data e l'ora dell'operazione e una colonna per memorizzare un numero di sequenza ("revisione"), che viene incrementato in base all'operazione ed è raggruppato in base alla colonna della chiave primaria della tabella di dati.

Per eseguire questo comportamento di sequenziamento, viene creato un indice a due colonne (composito) sulla colonna della chiave primaria e sulla colonna di revisione. Tieni presente che puoi eseguire il sequenziamento in questo modo solo se il motore utilizzato dalla tabella della cronologia è MyISAM (Vedi 'MyISAM Notes' in questa pagina)

La tabella della cronologia è abbastanza facile da creare. Nella query ALTER TABLE di seguito (e nelle query trigger di seguito), sostituisci "primary_key_column" con il nome effettivo di quella colonna nella tabella dei dati.

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);

E poi crei i trigger:

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;

E hai finito. Ora, tutti gli inserimenti, gli aggiornamenti e le eliminazioni in 'MyDb.data' verranno registrati in 'MyDb.data_history', fornendoti una tabella della cronologia come questa (meno la colonna artificiosa '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 

Per visualizzare le modifiche per una determinata colonna o colonne da un aggiornamento all'altro, dovrai unire la tabella della cronologia a se stessa nella chiave primaria e nelle colonne della sequenza. Puoi creare una vista per questo scopo, ad esempio:

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

Modifica:Oh wow, alla gente piace la mia tabella della cronologia di 6 anni fa :P

La mia implementazione sta ancora ronzando, diventando più grande e più ingombrante, suppongo. Ho scritto visualizzazioni e un'interfaccia utente piuttosto carina per guardare la cronologia in questo database, ma non penso che sia mai stato usato molto. Così va.

Per indirizzare alcuni commenti in ordine sparso:

  • Ho eseguito la mia implementazione in PHP che è stata un po' più complicata ed ho evitato alcuni dei problemi descritti nei commenti (è significativo il trasferimento degli indici. Se trasferisci gli indici univoci alla tabella della cronologia, le cose si romperanno. Ci sono soluzioni per questo nei commenti). Seguire alla lettera questo post potrebbe essere un'avventura, a seconda di quanto è consolidato il tuo database.

  • Se la relazione tra la chiave primaria e la colonna di revisione sembra disattivata, di solito significa che la chiave composta è stata bloccata in qualche modo. In alcune rare occasioni mi è successo ed ero in perdita per la causa.

  • Ho trovato questa soluzione piuttosto performante, usando i trigger come fa. Inoltre, MyISAM è veloce negli inserimenti, il che è tutto ciò che fanno i trigger. Puoi migliorarlo ulteriormente con l'indicizzazione intelligente (o la mancanza di...). L'inserimento di una singola riga in una tabella MyISAM con una chiave primaria non dovrebbe essere un'operazione da ottimizzare, in realtà, a meno che non si verifichino problemi significativi altrove. Per tutto il tempo in cui ho eseguito il database MySQL, questa implementazione della tabella della cronologia era attiva, non è mai stata la causa di nessuno dei (molti) problemi di prestazioni che si sono presentati.

  • se ricevi inserimenti ripetuti, controlla il livello del tuo software per le query di tipo INSERT IGNORE. Hrmm, non ricordo ora, ma penso che ci siano problemi con questo schema e transazioni che alla fine falliscono dopo aver eseguito più azioni DML. Qualcosa di cui essere consapevoli, almeno.

  • È importante che i campi nella tabella della cronologia e nella tabella dei dati corrispondano. O meglio, che la tua tabella di dati non ha PIÙ colonne rispetto alla tabella della cronologia. In caso contrario, le query di inserimento/aggiornamento/cancellazione sulla tabella di dati non riusciranno, quando gli inserimenti nelle tabelle di cronologia inseriscono colonne nella query che non esistono (a causa di d.* nelle query di trigger) e il trigger ha esito negativo. Sarebbe fantastico se MySQL avesse qualcosa come i trigger di schema, in cui potresti alterare la tabella della cronologia se le colonne fossero aggiunte alla tabella dei dati. MySQL ce l'ha adesso? Reagisco in questi giorni :P