Как контролировать версию записи в базе данных - PullRequest
151 голосов
/ 27 ноября 2008

Допустим, у меня есть запись в базе данных и что как администраторы, так и обычные пользователи могут делать обновления.

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

Ответы [ 11 ]

149 голосов
/ 27 ноября 2008

Допустим, у вас есть таблица FOO, которую администраторы и пользователи могут обновить. Большую часть времени вы можете писать запросы к таблице FOO. Счастливые дни.

Тогда я бы создал FOO_HISTORY таблицу. Здесь есть все столбцы таблицы FOO. Первичный ключ такой же, как FOO плюс столбец RevisionNumber. Существует внешний ключ от FOO_HISTORY до FOO. Вы также можете добавить столбцы, связанные с ревизией, такие как UserId и RevisionDate. Заполняйте номера RevisionNumbers постоянно увеличивающимися во всех таблицах *_HISTORY (т.е. из последовательности Oracle или ее эквивалента). Не полагайтесь только на одно изменение в секунду (т.е. не вводите RevisionDate в первичный ключ).

Теперь, каждый раз, когда вы обновляете FOO, непосредственно перед обновлением вы вставляете старые значения в FOO_HISTORY. Вы делаете это на каком-то фундаментальном уровне в своем дизайне, чтобы программисты не могли случайно пропустить этот шаг.

Если вы хотите удалить строку из FOO, у вас есть несколько вариантов. Либо каскадно удалите всю историю, либо выполните логическое удаление, отметив FOO как удаленное.

Это решение хорошо, когда вы в значительной степени интересуетесь текущими ценностями и только изредка в истории. Если вам всегда нужна история, вы можете указать эффективные даты начала и окончания и сохранить все записи в самом FOO. Каждый запрос должен проверить эти даты.

40 голосов
/ 27 ноября 2008

Я думаю, что вы ищете версионирование содержимого записей базы данных (как это делает StackOverflow, когда кто-то редактирует вопрос / ответ). Хорошей отправной точкой может служить модель базы данных, в которой используется revision tracking.

Лучший пример, который приходит на ум, это MediaWiki, движок Википедии. Сравните схему базы данных здесь , в частности таблицу ревизий .

В зависимости от того, какие технологии вы используете, вам придется найти несколько хороших алгоритмов сравнения / слияния.

Отметьте этот вопрос , если это для .NET.

25 голосов
/ 27 ноября 2008

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

Когда вы хотите просмотреть текущую запись, вы выбираете ту, в которой endDate равен нулю.

Это иногда называют типом 2 Медленно изменяющееся измерение . Смотри также TupleVersioning

8 голосов
/ 28 ноября 2008

Обновление до SQL 2008.

Попробуйте использовать отслеживание изменений SQL в SQL 2008. Вместо взлома меток времени и захоронения столбцов вы можете использовать эту новую функцию для отслеживания изменений данных в вашей базе данных.

MSDN SQL 2008 Отслеживание изменений

5 голосов
/ 10 декабря 2015

Просто хотел добавить, что одним хорошим решением этой проблемы является использование Временная база данных . Многие поставщики баз данных предлагают эту функцию либо из коробки, либо через расширение. Я успешно использовал расширение temporal table с PostgreSQL, но у других оно тоже есть. Всякий раз, когда вы обновляете запись в базе данных, база данных также сохраняет предыдущую версию этой записи.

5 голосов
/ 27 ноября 2008

Два варианта:

  1. Иметь таблицу истории - вставляйте старые данные в эту таблицу истории при каждом обновлении оригинала.
  2. Таблица аудита - сохраните значения до и после - только для измененных столбцов в таблице аудита вместе с другой информацией, например, кто обновил и когда.
3 голосов
/ 27 ноября 2008

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

Дополнительный бонус этого подхода заключается в том, что аудит будет выполняться независимо от того, была ли операция sql выполнена через ваши DLL-библиотеки доступа к данным или через SQL-запрос вручную; (так как аудит выполняется на самом сервере).

3 голосов
/ 27 ноября 2008

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

То, как это работает, в случае, если вы хотите скопировать его в другую БД или, может быть, если вы просто хотите это понять, заключается в том, что для таблицы также создается теневая таблица, просто обычная таблица базы данных, с те же спецификации полей, а также некоторые дополнительные поля: например, какое действие было выполнено последним (строка, типичные значения «INS» для вставки, «UPD» для обновления и «DEL» для удаления), дата и время, когда действие имело место, и идентификатор пользователя для того, кто это сделал.

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

В Oracle все, что вам нужно, генерируется автоматически как код SQL, все, что вам нужно сделать, это скомпилировать / запустить его; и он поставляется с базовым приложением CRUD (фактически только «R») для его проверки.

2 голосов
/ 03 августа 2018

Алок предложил Audit table выше, я хотел бы объяснить это в своем посте.

Я принял этот проект без единой таблицы в своем проекте.

Схема:

  • id - INTEGER AUTO INCREMENT
  • имя пользователя - STRING
  • имя таблицы - STRING
  • oldvalue - TEXT / JSON
  • новое значение - TEXT / JSON
  • созданный на - DATETIME

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

Плюсы с этим дизайном:

  • Меньшее количество таблиц для управления историей.
  • Сохраняет полный снимок каждой строки старого и нового состояния.
  • Легко искать по каждой таблице.
  • Может создать раздел по таблице.
  • Может определять политику хранения данных для каждой таблицы.

Минусы с этим дизайном:

  • Размер данных может быть большим, если система часто меняется.
2 голосов
/ 28 ноября 2012

Пока @WW. ответ - хороший ответ. Другой способ - создать столбец версий и сохранить все версии в одной таблице.

Для одного подхода к столу вы либо:

  • Используйте флажок для обозначения последней буквы Word Press
  • ИЛИ сделать противную версию outer join.

Пример SQL метода outer join с использованием номеров ревизий:

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

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

Примером новой редакции для '/stuff' может быть:

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

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

Подход с использованием флагов и таблицы истории требует две строки для вставки / обновления.

Другое преимущество подхода outer join с номером ревизии заключается в том, что вы всегда можете рефакторинг для подхода с несколькими таблицами позже с помощью триггеров, потому что ваш триггер должен, по сути, делать что-то подобное выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...