Храните все изменения данных с каждой детали (например, Stackru)
У меня есть система, написанная с использованием Codeigniter и в качестве базы данных с использованием MySQL. В системе есть пользователь, группы пользователей с различными привилегиями и т. Д. Имеют множество таблиц mysql, которые имеют множество отношений.
Некоторые из таблиц у меня есть:
- Предметы
- контракты
- клиенты
- товары
- product_features
- заказы
- order_features
- order_products
- так далее...
В настоящее время я регистрирую каждое изменение данных для этих таблиц, сделанное пользователями. Пользователи могут изменять эти данные из-за своих привилегий. Хранение смены логов только в простой форме вроде
A user changed product features with id of A8767
B user added new customer with id 56
C user edited content of orderlist
A user added new product (id: A8767) to order (id: or67)
...
Я хочу сохранить все изменения, которые сделаны с каждой деталью, например, историю редактирования вопроса Stackru. Я могу думать о log_table
дизайн, чтобы сохранить все изменения данных из различных таблиц. Есть ли способ, учебник, движок, плагин, чтобы сделать это? Только я могу думать, делать дубликаты из каждой таблицы и хранить изменения в них, но я не думаю, что это хороший способ.
2 ответа
Я думал об этом некоторое время и могу думать только о двух способах сделать это. Оба могут работать полностью прозрачно, когда встроены в абстрактный слой данных / модель.
Кстати, в доктрине ORM Mapper есть реализация "версионных" данных таблиц. Посмотрите этот пример в их документах. Может быть, это соответствует вашим потребностям, но это не подходит моим. Кажется, что удаляются все исторические данные при удалении исходной записи, что делает ее не очень безопасной для редактирования.
Вариант A: иметь копию каждой таблицы для хранения данных редакции
Допустим, у вас есть простая таблица контактов:
CREATE TABLE contact (
id INT NOT NULL auto_increment,
name VARCHAR(255),
firstname VARCHAR(255),
lastname VARCHAR(255),
PRIMARY KEY (id)
)
Вы должны создать копию этой таблицы и добавить данные ревизии:
CREATE TABLE contact_revisions (
id INT NOT NULL,
name VARCHAR(255),
firstname VARCHAR(255),
lastname VARCHAR(255),
revision_id INT auto_increment,
type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
change_time DEFAULT current_timestamp,
PRIMARY KEY(revision_id)
)
Следить за INSERT
а также UPDATE
с помощью AFTER
триггеры. Для каждой новой редакции данных в оригинале вставьте копию новых данных в таблицу редакций и установите модификацию type
должным образом.
Чтобы войти DELETE
безопасная версия, вы также должны вставить новую строку в таблицу истории! Для этого вы должны использовать BEFORE DELETE
вызвать и сохранить последние значения, прежде чем они будут удалены. В противном случае вам придется удалить каждый NOT NULL
ограничение в таблице истории, а также.
Некоторые важные замечания относительно этой реализации
- Для таблицы истории вы должны отбросить каждый
UNIQUE KEY
(здесь:PRIMARY KEY
) из таблицы редакций, поскольку у вас будет один и тот же ключ несколько раз для каждой редакции данных. - Когда ты
ALTER
Схема и данные в исходной таблице через обновление (например, обновление программного обеспечения). Вы должны убедиться, что те же данные или исправления схемы применяются к таблице истории и ее данным. В противном случае у вас возникнут проблемы при возврате к более старой версии набора записей. - В реальной реализации вы захотите узнать, какой пользователь изменил данные. Чтобы обеспечить ревизионную безопасность, пользовательскую запись никогда не следует удалять из таблицы users. Вы должны просто отключить учетную запись с флагом.
- Обычно одно действие пользователя включает в себя более одной таблицы. В реальной реализации вы также должны отслеживать, какие изменения в нескольких таблицах относятся к одной пользовательской транзакции, а также в каком порядке. В реальном случае вы бы хотели отменить все изменения одной транзакции вместе в обратном порядке. Для этого потребуется дополнительная таблица ревизий, которая отслеживает пользователей и транзакции и имеет слабую связь со всеми этими отдельными ревизиями в таблицах истории.
Выгоды:
- полностью в базе данных, независимо от кода приложения. (ну, не тогда, когда важно отслеживать пользовательские транзакции. Это потребовало бы некоторой логики за пределами одного запроса)
- все данные в их исходном формате, без явных преобразований типов.
- хорошие показатели по поиску в ревизиях
- легкий откат. Просто сделай простое
INSERT .. ON DUPLICATE KEY UPDATE ..
заявление на исходную таблицу, используя данные из ревизии, которую вы хотите откатить.
Существо дела:
- Трудно реализовать вручную.
- Трудно (но не невозможно) автоматизировать процесс миграции баз данных / обновления приложений.
Как уже говорилось выше, доктриныversionable
делает что-то похожее.
Вариант B: иметь центральную таблицу журнала изменений
Предисловие: плохая практика, показана только для иллюстрации альтернативы.
Этот подход сильно зависит от логики приложения, которая должна быть скрыта в уровне данных / модели.
У вас есть центральная таблица истории, которая отслеживает
- Кто сделал
- когда
- изменить, вставить или удалить
- какие данные
- в какой области
- из какого стола
Как и в другом подходе, вы также можете отслеживать, какие отдельные изменения данных принадлежат одному действию / транзакции пользователя и в каком порядке.
Выгоды:
- нет необходимости синхронизировать с исходной таблицей при добавлении полей в таблицу или создании новой таблицы. он масштабируется прозрачно.
Существо дела:
- плохая практика с использованием простого значения = хранилище ключей в базе данных
- плохая производительность поиска из-за неявных преобразований типов
- может замедлить общую производительность приложения / базы данных, когда центральная таблица истории становится узким местом из-за блокировок записи (это относится только к конкретным механизмам с блокировками таблиц, т.е. MyISAM)
- Реализовать откаты намного сложнее
- возможные ошибки преобразования данных / потеря точности из-за неявного преобразования типов
- не отслеживает изменения, когда вы напрямую обращаетесь к базе данных где-то в вашем коде, а не используете уровень модели / уровень данных, и забываете, что в этом случае вы должны записывать в журнал изменений вручную. Может быть большой проблемой при работе в команде с другими программистами.
Заключение:
- Вариант B может быть очень удобен для небольших приложений, так как просто "заглядывает", когда он просто для регистрации изменений.
- Если вы хотите вернуться назад во времени и иметь возможность легко сравнить различия между исторической ревизией 123 и ревизией 125 и / или вернуться к старым данным, то вариант А - трудный путь.
Как насчет использования общей таблицы обновлений универа. Поля таблицы должны содержать эти значения:
user,event,date,table,field,new value
- пользователь - кто внес изменения
- событие - как код предварительно определенных событий (обновить, сохранить, вставить)
- дата - когда было сделано изменение
- таблица и поле - могут быть автоматически локализованы из глобального запроса
- значение - вставленное значение
Значения и вставка могут быть созданы с помощью некоторой функции из общего запроса.