Як версія контролює запис у базі даних


176

Скажімо, у мене є запис у базі даних і що і адміністратор, і звичайні користувачі можуть робити оновлення.

Хто-небудь може запропонувати хороший підхід / архітектуру, як версії контролювати всі зміни в цій таблиці, щоб можна було відкати запис до попередньої версії.

Відповіді:


164

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

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

Тепер щоразу, коли ви оновлюєтесь FOO, безпосередньо перед оновленням, ви вставляєте в нього старі значення FOO_HISTORY. Ви робите це на якомусь фундаментальному рівні у своєму дизайні, щоб програмісти не могли випадково пропустити цей крок.

Якщо ви хочете видалити рядок, у FOOвас є вибір. Або каскадуйте та видаляйте всю історію, або виконайте логічне видалення, позначивши FOOяк видалене.

Це рішення добре, коли вас значною мірою цікавлять поточні цінності та лише зрідка історія. Якщо вам завжди потрібна історія, то ви можете поставити ефективні дати початку і кінця і зберегти всі записи в FOOсобі. Кожен запит повинен перевірити ці дати.


1
Можна виконати оновлення таблиці аудиту за допомогою тригерів бази даних, якщо рівень доступу до даних безпосередньо не підтримує його. Крім того, не важко побудувати генератор коду, щоб зробити тригери, які використовують самоаналіз із словника системних даних.
Занепокоєння

44
Я рекомендую фактично вставити нові дані, а не попередні, тому в таблиці історії є всі дані. Незважаючи на те, що він зберігає перероблені дані, він усуває особливі випадки, необхідні для пошуку в обох таблицях, коли потрібні історичні дані.
Нердфест

6
Особисто я рекомендую нічого не видаляти (відкладіть це під конкретну діяльність з ведення господарства) та мати стовпець "Тип дії", щоб вказати, чи є це вставка / оновлення / видалення. Для видалення ви копіюєте рядок як звичайний, але ставите "delete" у стовпчик типу дій.
Ніл Барнвелл

3
@Hydrargyrum Таблиця, що містить поточні значення, буде краще, ніж перегляд історичної таблиці. Ви також можете визначити зовнішні ключі, що посилаються на поточні значення.
ВВ.

2
There is a foreign key from FOO_HISTORY to FOO': погана ідея, я хотів би видалити записи з foo, не змінюючи історію. Таблиця історії повинна бути звичайною лише вставкою.
Ясен

46

Я думаю, ви шукаєте версію вмісту записів баз даних (як це робить StackOverflow, коли хтось редагує питання / відповідь). Гарною відправною точкою може бути розгляд деякої моделі бази даних, яка використовує відстеження ревізії .

Найкращий приклад, який спадає на думку, - MediaWiki, двигун Wikipedia. Порівняйте схему бази даних тут , зокрема таблицю ревізії .

Залежно від того, які технології ви використовуєте, вам доведеться знайти кілька хороших алгоритмів розходження / злиття.

Перевірте це питання, якщо воно стосується .NET.


30

У світі BI ви можете це досягти, додавши startDate та endDate до таблиці, до якої потрібно версії. Коли ви вставляєте перший запис у таблицю, заповнюється startDate, але endDate є нульовим. Коли ви вставляєте другий запис, ви також оновлюєте endDate першої записи разом з startDate другого запису.

Коли ви хочете переглянути поточну запис, ви вибираєте ту, де endDate є нульовою.

Іноді це називають тип 2 Повільно змінюється розмір . Дивіться також TupleVersioning


Чи не зросте мій стіл досить великим за допомогою такого підходу?
Нільс Босма

1
Так, але ви можете вирішити це шляхом індексації та / або розділення таблиці. Також буде лише невелика жменька великих столів. Більшість буде набагато меншою.
ЗанепокоєнийOfTunbridgeWells

Якщо я не помиляюсь, єдине падіння тут полягає в тому, що воно обмежує зміни один раз на секунду правильно?
pimbrouwers

@pimbrouwers так, це в кінцевому рахунку залежить від точності полів і функції, яка їх заповнює.
Дейв Нілі

9

Оновлення до SQL 2008.

Спробуйте скористатись відстеженням змін SQL у SQL 2008. Замість того, щоб позначити часові позначки та злому надгробних плит, ви можете використовувати цю нову функцію для відстеження змін даних у вашій базі даних.

Відстеження змін MSDN SQL 2008


7

Просто хотів додати, що одне хороше рішення цієї проблеми - це використання бази даних Temporal . Багато виробників баз даних пропонують цю функцію або поза коробкою, або через розширення. Я успішно використовував розширення тимчасової таблиці з PostgreSQL, але й інші. Щоразу, коли ви оновлюєте запис у базі даних, база даних також тримається на попередній версії цього запису.


6

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

  1. Майте таблицю історії - вставляйте старі дані в цю історію, коли оригінал оновлюється.
  2. Таблиця аудиту - зберігайте значення до та після - лише для модифікованих стовпців таблиці аудиту разом з іншою інформацією, як, наприклад, хто оновлювався та коли.

5

Ви можете проводити аудит у таблиці SQL через тригери SQL. З тригера ви можете отримати доступ до 2 спеціальних таблиць ( вставлених і видалених ). Ці таблиці містять точні рядки, які були вставлені чи видалені щоразу, коли таблиця оновлюється. У триггерному SQL ви можете взяти ці змінені рядки та вставити їх у таблицю аудиту. Такий підхід означає, що аудит прозорий для програміста; не вимагаючи від них жодних зусиль чи будь-яких знань щодо впровадження.

Додатковим бонусом цього підходу є те, що аудит відбуватиметься незалежно від того, чи була операція sql через DLL для доступу до ваших даних або через запит SQL вручну; (оскільки аудит проводиться на самому сервері).


3

Ви не кажете, яка база даних, і я не бачу її в тегах публікацій. Якщо це стосується Oracle, я можу порекомендувати підхід, вбудований в конструктор: використовувати журнальні таблиці . Якщо це стосується будь-якої іншої бази даних, то я в основному рекомендую так само ...

Як він працює, якщо ви хочете його повторити в іншій БД, або, можливо, ви просто хочете його зрозуміти, це те, що для таблиці існує також тіньова таблиця, просто звичайна таблиця бази даних, з тими ж специфікаціями поля , а також кілька додаткових полів: наприклад, яку дію востаннє виконували (рядок, типові значення "INS" для вставки, "UPD" для оновлення та "DEL" для видалення), дата дати, коли дія відбулася, та ідентифікатор користувача для того, хто робив це.

Через тригери кожна дія до будь-якого рядка таблиці вставляє в таблицю журналу новий рядок із новими значеннями, яку дію було зроблено, коли та яким користувачем. Ви ніколи не видаляєте жодних рядків (принаймні, не за останні кілька місяців). Так, вона зросте великою, легко мільйонами рядків, але ви можете легко відстежувати значення для будь-якого запису в будь-який момент часу з моменту початку журналу чи останнього очищення старих рядків журналу, і хто вніс останню зміну.

В Oracle все, що вам потрібно, генерується автоматично у вигляді SQL-коду, все, що вам потрібно зробити, - це компілювати / запустити його; і він постачається з базовим додатком CRUD (фактично лише "R") для його огляду.


2

Я теж роблю те саме. Я роблю базу даних для планів уроків. Ці плани потребують гнучкості в модифікації атомних змін. Іншими словами, кожну зміну, як би не була невелика, до планів уроків потрібно дозволити, але стару версію потрібно також зберегти недоторканою. Таким чином, творці уроків можуть редагувати плани уроків, поки учні їх використовують.

Як би це працювало, що після того, як учень зробив урок, їх результати додаються до версії, яку вони закінчили. Якщо зміна буде внесена, результат їх результату завжди буде вказувати на їх версію.

Таким чином, якщо критерії уроку буде видалено або переміщено, їх результати не зміняться.

Я зараз це роблю, обробляючи всі дані в одній таблиці. Зазвичай у мене буде тільки одне поле id, але в цій системі я використовую id та sub_id. Sub_id завжди залишається з рядком, через оновлення та видалення. Ідентифікатор автоматично збільшується. Програмне забезпечення плану уроків буде посилатися на найновіший суб -ідід. Результати студентів посилаються на ідентифікатор. Я також включив позначку часу для відстеження, коли відбулися зміни, але не потрібно обробляти версію.

Одне, що я можу змінити, як тільки я тестую його, - це я можу використати згадану раніше нульову ідею endDate. У моїй системі, щоб знайти найновішу версію, я повинен був би знайти max (id). Інша система просто шукає endDate = null. Не впевнений, чи є переваги, які мають інше поле дати.

Мої два центи.


2

Поки @WW. відповідь є гарною відповіддю. Ще один спосіб - зробити стовпець версій і зберегти всі ваші версії в одній таблиці.

Для одного столу ви підходите :

  • Використовуйте прапор для позначення останнього ala 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підходу до редагування номерів полягає в тому, що ви завжди зможете пізніше повернутись до підходу до декількох таблиць за допомогою тригерів, оскільки ваш тригер повинен по суті робити щось подібне до вище.


2

Алок запропонував Audit tableвище, я хотів би пояснити це у своєму дописі.

Я прийняв цю схему без єдиної схеми для свого проекту.

Схема:

  • id - ІНТЕГРОВАННЯ АВТОМАТИЧНОГО ВПРОВАДЖЕННЯ
  • ім’я користувача - STRING
  • назва таблиці - STRING
  • старе значення - ТЕКСТ / JSON
  • нове значення - ТЕКСТ / JSON
  • createdon - ДАТЕТИМ

Ця таблиця може містити історичні записи для кожної таблиці одночасно, із повною історією об'єктів в одному записі. Цю таблицю можна заповнити, використовуючи тригери / гачки, де змінюються дані, зберігаючи старий та новий знімок значення цільового рядка.

Плюси цього дизайну:

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

Мінуси з цим дизайном:

  • Розмір даних може бути великим, якщо система має часті зміни.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.