Як зберігати історичні дані


162

Деякі співробітники та я вступили в дискусію щодо найкращого способу зберігання історичних даних. В даний час для деяких систем я використовую окрему таблицю для зберігання історичних даних і зберігаю оригінальну таблицю для поточного активного запису. Отже, скажімо, у мене таблиця FOO. У моїй системі всі активні записи будуть надходити у FOO, а всі історичні записи - у FOO_Hist. Користувач може оновлювати багато різних полів у FOO, тому я хочу постійно вести точний облік оновлених даних. FOO_Hist містить точно такі ж поля, як і FOO, за винятком автоматичного збільшення HIST_ID. Кожен раз , коли FOO оновлюється, я виконую оператор вставки в FOO_Hist подібне: insert into FOO_HIST select * from FOO where id = @id.

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

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

Як ви чи ваша компанія справляєтесь з цим?

Я використовую MS SQL Server 2008, але я хотів би зберегти відповідь загальною та довільною для будь-якої СУБД.

Відповіді:


80

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

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

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

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

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

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


Я думаю, я бачу, що ти кажеш. Тож, що я зробив зі своєю таблицею FOO_Hist, це справді створити таблицю аудиту. Замість того, щоб використовувати тригер для вставки в таблицю аудиту при оновленні, я просто запустив оператор у програмі. Це правильно?
Аарон

6
Досить. Краще робити такий вид журналу аудиту за допомогою тригерів; тригери переконайтеся, що будь-які зміни (включаючи виправлення даних вручну) записуються в журнали аудиту. Якщо у вас є більше 10-20 таблиць для аудиту, швидше за все, швидше створити інструмент генератора тригерів. Якщо трафік диска для журналів аудиту є проблемою, ви можете помістити таблиці журналів аудиту на окремий набір дисків.
ConcernedOfTunbridgeWells

Так, я з цим на 100% згоден. Дякую.
Аарон

40

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

Ми використовуємо те, що називається моделлю Master - Detail, яка найпростіше складається з:

Наприклад, таблиця Master називається Widgetsчасто просто містить ідентифікатор. Часто містять дані, які не змінюватимуться з часом / не є історичними.

Наприклад, таблиця деталей / історії,Widget_Details що містить щонайменше:

  • ID - первинний ключ. Деталі / історичний ідентифікатор
  • MASTER_ID - наприклад, у цьому випадку під назвою "WIDGET_ID", це FK в основний запис
  • START_DATETIME - часова мітка, яка вказує на початок цього рядка бази даних
  • END_DATETIME - часова мітка, яка вказує кінець цього ряду бази даних
  • STATUS_CONTROL - одинарний стовпчик символів, який вказує статус рядка. "C" вказує на поточний, NULL або "A" буде історичним / заархівованим. Ми використовуємо це лише тому, що не можемо індексувати END_DATETIME як NULL
  • CREATED_BY_WUA_ID - зберігає ідентифікатор облікового запису, який спричинив створення рядка
  • XMLDATA - зберігає фактичні дані

Отже, по суті, сутність починається з 1 рядка в головному і 1 ряд у деталі. Деталі, що мають NULL дату закінчення та STATUS_CONTROL "C". Коли відбувається оновлення, поточний рядок оновлюється таким, що має END_DATETIME поточного часу, а status_control встановлюється на NULL (або "A", якщо бажано). Новий рядок створюється в таблиці деталей, все ще пов'язаної з тим самим головним, зі статусом_control 'C', ідентифікатором особи, яка здійснює оновлення, та новими даними, що зберігаються у стовпці XMLDATA.

Це основа нашої історичної моделі. Логіка створення / оновлення обробляється в пакеті Oracle PL / SQL, так що ви просто передаєте функції поточний ідентифікатор, ваш ідентифікатор користувача та нові XML-дані, і всередині він робить все оновлення / вставлення рядків, щоб представити це в історичній моделі . Час початку та кінця вказує, коли цей рядок у таблиці активний.

Зберігання є дешевим, ми, як правило, ВИДАЛЯЄМО дані та вважаємо за краще зберігати аудиторський слід. Це дозволяє нам бачити, як виглядали наші дані в будь-який момент часу. Якщо індексувати status_control = 'C' або використовувати перегляд, захаращення не є точно проблемою. Очевидно, що ваші запити повинні враховувати, ви завжди повинні використовувати поточну (NULL end_datetime та status_control = 'C') версію запису.


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

@projo ID на вашій головній таблиці - це ПК та концептуально "ПК" для будь-якої концепції, з якою ви маєте справу. Ідентифікатор в таблиці деталей - ПК, щоб визначити історичну версію для ведучого (це ще один стовпець на деталі). Створюючи відносини, ви часто посилаєтесь на справжній ПК своєї концепції (тобто ідентифікатор у вашій головній таблиці або стовпець MASTER_ID у вашій деталі) та використовуйте STATUS_CONTROL = 'C', щоб переконатися, що ви отримуєте поточну версію. Крім того, ви можете посилатись на ідентифікатор деталей, щоб відновити щось до певного моменту часу.
Кріс Камерон-Міллс

+1 Я реалізував цю модель з великим успіхом у кількох великих проектах.
Логіка трьох значень

Ми використовуємо той самий підхід. Але зараз мені цікаво, чи краще зберігати лише START_DATETIME і не зберігати END_DATETIME
bat_ventzi

Пара варіацій мого досвіду. Якщо ваш об'єкт "закінчено", тобто архівується або видаляється, то ви фактично не можете мати детальних записів із контролем статусу "С", тобто немає поточного рядка, хоча ви б не знали, коли це сталося. Крім того, ви можете встановити end_datetime на останньому рядку, і наявність рядка 'закінчено' C 'може вказувати на те, що сутність тепер видалена / заархівована. Нарешті, ви можете представити це через інший стовпець, СТАТУС, який, можливо, вже є.
Кріс Кемерон-Міллс

15

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

Якщо ви спробуєте інший підхід досить скоро, ви зіткнетесь з проблемами:

  • технічне обслуговування накладних витрат
  • більше прапорів у виділених
  • сповільнення запитів
  • зростання таблиць, індексів

7

У SQL Server 2016 і вище існує нова функція під назвою Temporal Tables, яка має на меті вирішити цю проблему мінімальними зусиллями розробника . Концепція тимчасової таблиці схожа на зміну захоплення даних (CDC), з тією різницею, що тимчасова таблиця абстрагувала більшість речей, які вам довелося робити вручну, якщо ви використовували CDC.


2

Змінити захоплення даних: https://docs.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-data-capture-sql-server?view=sql-server-2017

Він підтримується в SQL Server 2008 R2, він, можливо, підтримувався в SQL Server 2008.


Зауважте, що Change Data Capture призначений лише для короткого зберігання історії даних. Див. Розділ Темпоральні таблиці SQL Server vs Змінення збору даних та відстеження змін .
Едвард Брей


1

Просто хотів додати параметр, який я почав використовувати, тому що я використовую Azure SQL, і річ з декількома таблицями була для мене занадто громіздкою. Я додав на свій стіл тригер вставки / оновлення / видалення, а потім перетворив перехід до / після зміни в json за допомогою функції "ЗА JSON AUTO".

 SET @beforeJson = (SELECT * FROM DELETED FOR JSON AUTO)
SET @afterJson = (SELECT * FROM INSERTED FOR JSON AUTO)

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

Про це я дізнався за цим посиланням тут


0

Ви можете просто розділити таблиці, ні?

"Стратегії розподілених таблиць і індексів за допомогою SQL Server 2008 Коли таблиця баз даних збільшується до сотень гігабайт або більше, може ускладнюватися завантаження нових даних, видалення старих даних та підтримка індексів. Просто розмір таблиці спричиняє такі операції значно довше. Навіть дані, які потрібно завантажити або видалити, можуть бути дуже помітними, що робить операції INSERT і DELETE на столі непрактичними. Програмне забезпечення бази даних Microsoft SQL Server 2008 забезпечує розподіл таблиць, щоб зробити такі операції більш керованими ".


Так, я можу розділити таблиці, але чи є це стандарт при роботі з історичними даними? Чи слід включати історичні дані до тієї ж таблиці, що й активні дані? Це питання, які я хотів обговорити. Це також не є довільним, оскільки воно стосується SQL Server 2008.
Аарон

0

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


2
Чи складніше JOINдві таблиці в парі історичних звітів або складніше змінювати кожну вставлену / оновлену / видалену таблицю, щоб знати про історичні проблеми? Насправді журнал аудиту міститиме навіть поточні дані в таблиці історії, тому поточна таблиця навіть не повинна бути потрібною у звіті.

0

Іншим варіантом є архівування оперативних даних на [щоденній | щогодини | що завгодно] основі. Більшість двигунів баз даних підтримують вилучення даних в архів .

В основному, ідея полягає у створенні запланованого завдання для Windows або CRON, яке

  1. визначає поточні таблиці в операційній базі даних
  2. вибирає всі дані з кожної таблиці у файл CSV або XML
  3. стискає експортовані дані до ZIP-файлу, переважно із позначкою часу генерації у назві файла для легшого архівування.

Багато двигунів баз даних SQL поставляються з інструментом, який можна використовувати для цієї мети. Наприклад, при використанні MySQL в Linux для завдання планування вилучення може бути використана наступна команда:

mysqldump --all-databases --xml --lock-tables=false -ppassword | gzip -c | cat > /media/bak/servername-$(date +%Y-%m-%d)-mysql.xml.gz

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

0

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

Аудит (ціль безпеки) : використовуйте загальну таблицю для всіх таблиць, що підлягають аудиту. визначити структуру для зберігання назви стовпців перед полями значення та після значення.

Архівний / Історичний : для випадків, таких як відстеження попередньої адреси, номера телефону тощо. Створення окремої таблиці FOO_HIST краще, якщо ваша схема активних таблиць транзакцій істотно не зміниться в майбутньому (якщо ваша таблиця історії повинна мати ту саму структуру). якщо ви очікуєте нормалізації таблиці, додавання / видалення стовпців змін типу даних, зберігайте свої історичні дані у форматі xml. визначте таблицю з наступними стовпцями (ID, дата, версія схеми, XMLData). це легко впорається зі змінами схеми. але вам доведеться мати справу з xml, і це може спричинити складність у пошуку даних.



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