Версія, що контролює вміст бази даних


16

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

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

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

  • Збережіть весь рядок при кожній зміні, відновіть рядок назад до ідентифікатора джерела за допомогою первинного ключа (до чого я схиляюся в даний час, це найпростіше). Однак багато невеликих змін можуть призвести до великої кількості набряку таблиці.
  • зберегти перед / після / користувачем / часовою позначкою для кожної зміни з назвою стовпця, щоб відновити зміни до відповідного стовпця.
  • зберегти до / після / користувача / часової позначки з таблицею для кожного стовпця (це призведе до занадто багато таблиць).
  • збережіть diff / user / timetamp для кожної зміни стовпцем (це означатиме, що вам потрібно буде пройти всю історію змін, що втручаються, щоб повернутися до певної дати).

Який тут найкращий підхід? Прокрутка моєї власної здається, що я, мабуть, винаходжу чужу (кращу) базу коду.


Бонусні бали за PostgreSQL.


Це питання вже обговорювалося в SO: stackoverflow.com/questions/3874199/… . Google для "історії записів бази даних", і ви знайдете ще кілька статей.
Док Браун

1
Здається, ідеальний кандидат для проведення подій
Джеймс,

Чому б не скористатися журналом транзакцій SQL-сервера, щоб виконати трюк?
Thomas Junk

Відповіді:


11

Метод, який я зазвичай використовую, - це збереження повного запису з полем end_timestamp. Існує ділове правило, що лише один рядок може мати нульову кінцеву позначку end_timestamp, і це, звичайно, поточний активний вміст.

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

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


Зауважте, що інші двигуни бази даних можуть вести себе по-різному, наприклад, MySQL дозволяє кілька значень NULL у стовпці з унікальним індексом. Це ускладнює виконання цього обмеження.
qbd

Використання фактичної позначки часу небезпечно, але деякі бази даних MVCC працюють внутрішньо, зберігаючи мінімальні та максимальні серійні номери транзакцій разом із кортежами.
user2313838

"Це легко за допомогою Oracle, оскільки унікальний індекс може містити одну і лише одну нуль". Неправильно. Oracle взагалі не включає нульові значення в індекси. Не існує обмеження кількості нулів у стовпці з унікальним індексом.
Геррат

@Gerrat Пройшло кілька років, як я створив базу даних, яка мала цю вимогу, і я більше не маю доступу до цієї бази даних. Ви вірні, що стандартний унікальний індекс може підтримувати кілька нулів, але я думаю, що ми використовували або унікальне обмеження, або, можливо, функціональний індекс.
ківірон

8

Зауважте, що якщо ви використовуєте Microsoft SQL Server, то вже є функція, що називається Змінити захоплення даних . Вам все одно потрібно буде написати код, щоб пізніше отримати доступ до попередніх версій (CDC створює для цього конкретні представлення), але принаймні вам не потрібно змінювати схему ваших таблиць, а також не реалізовувати відстеження змін.

Під кришкою відбувається те, що:

  • CDC створює додаткову таблицю, що містить версії,

  • Ваша оригінальна таблиця використовується як раніше, тобто будь-яке оновлення відображається безпосередньо в цій таблиці,

  • У таблиці CDC зберігаються лише змінені значення, тобто дублювання даних зводиться до мінімуму.

Те, що зміни зберігаються в іншій таблиці, має два основні наслідки:

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

  • Деякі зміни схеми вихідної таблиці призводять до видалення CDC. Наприклад, якщо ви додаєте стовпець, CDC не знає, як з цим впоратися. З іншого боку, додавання індексу або обмеження має бути нормальним. Це швидко стає проблемою, якщо ввімкнути CDC на таблиці, яка піддається частим змінам. Можливо, є рішення, яке дозволяє змінити схему, не втрачаючи CDC, але я не шукав її.


6

Розв’яжіть задачу «по-філософськи» та в коді спочатку. А потім "домовляйтеся" з кодом і базою даних, щоб це відбулося.

Наприклад , якщо ви маєте справу із загальними статтями, початкова концепція статті може виглядати приблизно так:

class Article {
  public Int32 Id;
  public String Body;
}

І на наступному найосновнішому рівні я хочу зберегти список змін:

class Article {
  public Int32 Id;
  public String Body;
  public List<String> Revisions;
}

І мені може світатися, що нинішній орган - це лише остання редакція. А це означає дві речі: мені потрібно, щоб кожна редакція була датована або пронумерована:

class Revision {
  public Int32 Id;
  public Article ParentArticle;
  public DateTime Created;
  public String Body;
}

І ... поточний орган статті не повинен відрізнятися від останньої редакції:

class Article {
  public Int32 Id;
  public String Body {
    get {
      return (Revisions.OrderByDesc(r => r.Created))[0];
    }
    set {
      Revisions.Add(new Revision(value));
    }
  }
  public List<Revision> Revisions;
}

Деякі деталі відсутні; але це ілюструє, що ви, мабуть, хочете дві сутності . Один представляє статтю (або інший тип заголовка), а другий - перелік редакцій (групування будь-яких полів має добрий «філософський» сенс для групування). Спочатку вам не потрібні спеціальні обмеження для бази даних, оскільки ваш код не переймається жодною з самих ревізій - вони є властивостями статті, яка знає про зміни.

Отже, вам не потрібно турбуватися про те, щоб позначити редакції спеціальним чином або спиратися на обмеження бази даних, щоб позначити "поточну" статтю. Ви просто позначте їх часом (навіть ідентифікатор з автоматичним включенням буде добре), зробіть їх пов’язаними з батьківською статтею, і нехай стаття відповідає за те, щоб "найновіша" була найрелевантнішою.

І ви дозволяєте ORM обробляти менш філософські деталі - або ви ховаєте їх у користувальницькому класі утиліт, якщо ви не використовуєте нестандартну ORM.

Набагато пізніше, після того, як ви зробили кілька стрес-тестувань, ви можете подумати про те, щоб зробити цю властивість редагування ледачою навантаженням, або щоб ваш атрибут Body lazy-load був лише найвищим переглядом. Але ваша структура даних у цьому випадку не повинна змінюватися, щоб відповідати цим оптимізаціям.


2

Існує вікі-сторінка PostgreSQL для тригера відстеження аудиту, який допоможе вам налаштувати журнал аудиту, який буде робити все, що вам потрібно.

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

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

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


1

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

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

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

У будь-якому випадку, це все на GitHub тут .

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