Есть ли опция/функция MySQL для отслеживания истории изменений записей?

Меня спросили, могу ли я отслеживать изменения записей в базе данных MySQL. Поэтому, когда поле было изменено, существует старый vs new и дата, когда это произошло. Есть ли способ или общая техника для этого?

Если это так, я подумывал сделать что-то подобное. Создайте TABLE с изменениями. Он будет содержать те же поля, что и основной TABLE, но префикс старого и нового, но только для тех полей, которые были фактически изменены, и TIMESTAMP для него. Он будет проиндексирован с идентификатором. Таким образом, отчет SELECT может быть запущен для отображения истории каждой записи. Это хороший метод? Спасибо!

Ответ 1

Это тонкое.

Если бизнес-требование "Я хочу проверить изменения данных - кто сделал то, что и когда?", вы обычно можете использовать таблицы аудита (в соответствии с примером триггера, опубликованным Keethanjan). Я не был бы большим поклонником триггеров, но для него очень выгодно быть относительно безболезненным - ваш существующий код не должен знать о триггерах и материалах аудита.

Если бизнес-требование "покажет мне, какое состояние данных было на данной дате в прошлом", это означает, что аспект изменения с течением времени вошел в ваше решение. Несмотря на то, что вы можете восстановить состояние базы данных, просмотрев таблицы аудита, трудно и подвержен ошибкам, и для любой сложной логики базы данных она становится громоздкой. Например, если бизнес хочет знать "найти адреса писем, которые мы должны были отправить клиентам, у которых были выдающиеся и неоплаченные счета в первый день месяца", вам, вероятно, придется тралить полдюжины аудиторских таблиц.

Вместо этого вы можете испечь концепцию изменения с течением времени в своем проекте схемы (это второй вариант, предложенный Кейтаняном). Это изменение вашего приложения, безусловно, на уровне бизнес-логики и настойчивости, поэтому оно не тривиально.

Например, если у вас есть таблица вроде этого:

CUSTOMER
---------
CUSTOMER_ID PK
CUSTOMER_NAME
CUSTOMER_ADDRESS

и вы хотели отслеживать с течением времени, вы должны изменить его следующим образом:

CUSTOMER
------------
CUSTOMER_ID            PK
CUSTOMER_VALID_FROM    PK
CUSTOMER_VALID_UNTIL   PK
CUSTOMER_STATUS
CUSTOMER_USER
CUSTOMER_NAME
CUSTOMER_ADDRESS

Каждый раз, когда вы хотите изменить запись клиента, вместо обновления записи вы устанавливаете для VALID_UNTIL в текущей записи значение NOW() и вставляете новую запись с VALID_FROM (сейчас) и нулевым VALID_UNTIL. Вы устанавливаете статус "CUSTOMER_USER" на идентификатор входа текущего пользователя (если вам нужно его сохранить). Если клиент нужно удалить, вы используете флаг CUSTOMER_STATUS, чтобы указать это: вы никогда не сможете удалить записи из этой таблицы.

Таким образом, вы всегда можете найти, какой статус таблицы клиентов был для заданной даты - каким был адрес? Они изменили имя? Присоединившись к другим таблицам с аналогичными датами valid_from и valid_until, вы можете исторически восстановить всю картину. Чтобы найти текущий статус, вы ищете записи с нулевой датой VALID_UNTIL.

Это громоздко (строго говоря, вам не нужен valid_from, но он делает запросы немного проще). Это усложняет ваш дизайн и доступ к базе данных. Но это облегчает восстановление мира.

Ответ 2

Вот простой способ сделать это:

Сначала создайте таблицу истории для каждой таблицы данных, которую вы хотите отслеживать (пример ниже). Эта таблица будет содержать запись для каждого запроса на вставку, обновление и удаление, выполняемую в каждой строке таблицы данных.

Структура таблицы истории будет такой же, как и таблица данных, которую она отслеживает, за исключением трех дополнительных столбцов: столбец для хранения выполняемой операции (позвольте называть это "действие" ), дату и время операции, и столбец для хранения номера последовательности ( "ревизия" ), который увеличивается на одну операцию и сгруппирован столбцом первичного ключа таблицы данных.

Чтобы сделать это поведение последовательности, создается столбец с двумя столбцами (составной) в столбце первичного ключа и в столбце ревизии. Обратите внимание, что вы можете выполнять последовательность только таким образом, если движок, используемый в таблице истории, - это MyISAM (См. "Заметки MyISAM" на этой странице)

Таблицу истории довольно легко создать. В запросе ALTER TABLE ниже (и в приведенных ниже триггерных запросах) замените "primary_key_column" на фактическое имя этого столбца в вашей таблице данных.

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

И затем вы создаете триггеры:

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;

И все готово. Теперь все вставки, обновления и удаления в "MyDb.data" будут записаны в "MyDb.data_history", давая вам таблицу истории, подобную этой (за вычетом столбца "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 

Чтобы отобразить изменения для данного столбца или столбцов от обновления до обновления, вам нужно будет присоединиться к таблице истории к себе в столбцах первичного ключа и последовательности. Вы можете создать представление для этой цели, например:

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

Ответ 3

Вы можете создать триггеры, чтобы решить эту проблему. Вот учебник для этого (архивная ссылка).

Установление ограничений и правил в базе данных лучше, чем запись специальный код для обработки одной и той же задачи, поскольку это предотвратит разработчик от написания другого запроса, который обходит все специальный код и может оставить вашу базу данных плохой целостностью данных.

В течение долгого времени я копировал информацию в другую таблицу, используя scriptпоскольку MySQL не поддерживал триггеры в то время. Я нашел это триггер, чтобы быть более эффективным для отслеживания всего.

Этот триггер копирует старое значение в таблицу истории, если оно изменено когда кто-то редактирует строку. Editor ID и last mod сохраняются в оригинальная таблица каждый раз, когда кто-то редактирует эту строку; время соответствует когда он был изменен на его текущую форму.

DROP TRIGGER IF EXISTS history_trigger $$

CREATE TRIGGER history_trigger
BEFORE UPDATE ON clients
    FOR EACH ROW
    BEGIN
        IF OLD.first_name != NEW.first_name
        THEN
                INSERT INTO history_clients
                    (
                        client_id    ,
                        col          ,
                        value        ,
                        user_id      ,
                        edit_time
                    )
                    VALUES
                    (
                        NEW.client_id,
                        'first_name',
                        NEW.first_name,
                        NEW.editor_id,
                        NEW.last_mod
                    );
        END IF;

        IF OLD.last_name != NEW.last_name
        THEN
                INSERT INTO history_clients
                    (
                        client_id    ,
                        col          ,
                        value        ,
                        user_id      ,
                        edit_time
                    )
                    VALUES
                    (
                        NEW.client_id,
                        'last_name',
                        NEW.last_name,
                        NEW.editor_id,
                        NEW.last_mod
                    );
        END IF;

    END;
$$

Другим решением было бы сохранить поле "Редакция" и обновить это поле при сохранении. Вы можете решить, что max является самой новой версией, или что 0 является самой последней строкой. Это вам.

Ответ 4

Вот как мы его решили

Таблица пользователей выглядела так:

Users
-------------------------------------------------
id | name | address | phone | email | created_on | updated_on

И бизнес-требование изменилось, и нам нужно было проверить все предыдущие адреса и номера телефонов, которые когда-либо были у пользователя. новая схема выглядит так:

Users (the data that won't change over time)
-------------
id | name

UserData (the data that can change over time and needs to be tracked)
-------------------------------------------------
id | id_user | revision | city | address | phone | email | created_on
 1 |   1     |    0     | NY   | lake st | 9809  | @long | 2015-10-24 10:24:20
 2 |   1     |    2     | Tokyo| lake st | 9809  | @long | 2015-10-24 10:24:20
 3 |   1     |    3     | Sdny | lake st | 9809  | @long | 2015-10-24 10:24:20
 4 |   2     |    0     | Ankr | lake st | 9809  | @long | 2015-10-24 10:24:20
 5 |   2     |    1     | Lond | lake st | 9809  | @long | 2015-10-24 10:24:20

Чтобы найти текущий адрес любого пользователя, мы ищем UserData с ревизией DESC и LIMIT 1

Чтобы получить адрес пользователя в течение определенного периода времени мы можем использовать created_on bewteen (date1, date 2)

Ответ 5

Только мои 2 цента. Я бы создал решение, которое записывает точно, что изменилось, очень похоже на переходное решение.

My ChangesTable прост:

DateTime | WhoChanged | TableName | Action | ID |FieldName | OldValue

1) Когда вся целая строка изменяется в основной таблице, в эту таблицу войдет много записей, но это очень маловероятно, поэтому не большая проблема (люди обычно меняют только одну вещь) 2) OldVaue (и NewValue, если вы хотите) должны быть своего рода эпическим "anytype", поскольку это могут быть любые данные, может быть способ сделать это с помощью типов RAW или просто использовать строки JSON для преобразования и выключения.

Минимальное использование данных, хранит все, что вам нужно, и может использоваться для всех таблиц одновременно. Я исследую это сам прямо сейчас, но это может закончиться тем, как я иду.

Для Create и Delete, просто идентификатор строки, никаких полей не требуется. При удалении флаг на главной таблице (active?) Был бы хорош.

Ответ 6

Почему бы просто не использовать файлы журналов bin? Если репликация установлена ​​на сервере Mysql, а формат файла binlog установлен на ROW, все изменения могут быть зафиксированы.

Можно использовать хорошую библиотеку python, называемую noplay. Подробнее здесь.

Ответ 7

Прямой способ сделать это - создать триггеры для таблиц. Установите некоторые условия или методы сопоставления. Когда происходит обновление или удаление, он автоматически добавляется в таблицу изменений.

Но самая большая часть - это то, что если бы мы получили много столбцов и много таблицы. Мы должны вводить каждое имя столбца для каждой таблицы. Очевидно, это трата времени.

Чтобы справиться с этим более великолепно, мы можем создать некоторые процедуры или функции для получения имени столбцов.

Мы также можем использовать инструмент 3-й части, чтобы это сделать. Здесь я пишу программу java Mysql Tracker