Oto prosty sposób, aby to zrobić:
Najpierw utwórz tabelę historii dla każdej tabeli danych, którą chcesz śledzić (przykładowe zapytanie poniżej). Ta tabela będzie zawierała wpis dla każdego zapytania wstawiania, aktualizowania i usuwania wykonywanego w każdym wierszu w tabeli danych.
Struktura tabeli historii będzie taka sama, jak śledzonej przez nią tabeli danych, z wyjątkiem trzech dodatkowych kolumn:kolumny do przechowywania operacji, która wystąpiła (nazwijmy ją „działaniem”), daty i godziny operacji oraz kolumny do przechowywania numeru sekwencyjnego („weryfikacja”), który zwiększa się na operację i jest pogrupowany według kolumny klucza podstawowego tabeli danych.
Aby wykonać to zachowanie sekwencjonowania, tworzony jest dwukolumnowy (złożony) indeks w kolumnie klucza podstawowego i kolumnie wersji. Pamiętaj, że sekwencjonowanie w ten sposób można wykonać tylko wtedy, gdy silnikiem używanym przez tabelę historii jest MyISAM (Patrz „Notatki MyISAM” na tej stronie)
Tablica historii jest dość łatwa do stworzenia. W poniższym zapytaniu ALTER TABLE (oraz w poniższych zapytaniach wyzwalających) zastąp „primary_key_column” rzeczywistą nazwą tej kolumny w tabeli danych.
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);
A potem tworzysz wyzwalacze:
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;
I jesteś skończony. Teraz wszystkie wstawienia, aktualizacje i usunięcia w „MyDb.data” będą rejestrowane w „MyDb.data_history”, co daje taką tabelę historii (bez wymyślonej kolumny „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
Aby wyświetlić zmiany dla danej kolumny lub kolumn od aktualizacji do aktualizacji, musisz połączyć tabelę historii z samą sobą na kluczu podstawowym i kolumnach sekwencji. W tym celu możesz utworzyć widok, na przykład:
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
Edycja:Och wow, ludzie lubią mój stół do historii sprzed 6 lat :P
Przypuszczam, że moja implementacja wciąż się nuci, staje się coraz większa i bardziej nieporęczna. Napisałem widoki i całkiem fajny interfejs użytkownika, aby spojrzeć na historię w tej bazie danych, ale nie sądzę, aby był kiedykolwiek często używany. Tak to idzie.
Aby odnieść się do niektórych komentarzy w przypadkowej kolejności:
-
Zrobiłem własną implementację w PHP, która była trochę bardziej zaangażowana i uniknęłam niektórych problemów opisanych w komentarzach (z przenoszeniem indeksów, znacznie. Jeśli przeniesiesz unikalne indeksy do tabeli historii, wszystko się zepsuje. Istnieją rozwiązania dla to w komentarzach). Podążanie za tym postem co do joty może być przygodą, w zależności od tego, jak ustanowiona jest Twoja baza danych.
-
Jeśli relacja między kluczem podstawowym a kolumną wersji wydaje się niedostępna, zwykle oznacza to, że klucz złożony został w jakiś sposób przerwany. W kilku rzadkich przypadkach zdarzyło mi się to i nie mogłem znaleźć przyczyny.
-
Uważam, że to rozwiązanie jest dość wydajne, używając wyzwalaczy, tak jak to robi. Ponadto MyISAM jest szybki przy wstawianiu, co robią wszystkie wyzwalacze. Możesz to jeszcze poprawić za pomocą inteligentnego indeksowania (lub braku...). Wstawianie pojedynczego wiersza do tabeli MyISAM z kluczem podstawowym nie powinno być operacją, którą musisz zoptymalizować, chyba że masz poważne problemy, które mają miejsce gdzie indziej. Przez cały czas, gdy uruchamiałem bazę danych MySQL, ta implementacja tabeli historii była włączona, nigdy nie było to przyczyną żadnego z (wielu) problemów z wydajnością, które się pojawiły.
-
jeśli otrzymujesz powtarzające się wstawki, sprawdź warstwę oprogramowania pod kątem zapytań typu INSERT IGNORE. Hrmm, nie pamiętam teraz, ale myślę, że są problemy z tym schematem i transakcjami, które ostatecznie kończą się niepowodzeniem po uruchomieniu wielu działań DML. Przynajmniej coś, o czym należy pamiętać.
-
Ważne jest, aby pola w tabeli historii i tabeli danych były takie same. Lub raczej, że tabela danych nie zawiera WIĘCEJ kolumn niż tabela historii. W przeciwnym razie zapytania typu insert/update/del w tabeli danych zakończą się niepowodzeniem, gdy operacje wstawiania do tabel historii umieszczają w zapytaniu kolumny, które nie istnieją (z powodu d.* w zapytaniach wyzwalacza), a wyzwalacz nie powiedzie się. Byłoby wspaniale, gdyby MySQL miał coś w rodzaju wyzwalaczy schematów, w których można by zmienić tabelę historii, jeśli do tabeli danych dodano kolumny. Czy MySQL ma to teraz? Reaguję w tych dniach :P