управление строками истории в базе данных - PullRequest
9 голосов
/ 04 апреля 2009

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

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

недостатки этого решения для меня:

  • ведение 2 таблиц вместо 1 (в случае необходимости изменения структуры таблицы)
  • приложение должно знать обе таблицы вместо одной
  • имена таблиц могут быть короткими, чтобы сохранить соглашение имени таблицы и имени таблицы истории (например, SOME_TABLE, SOME_TABLE_HIST)

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

  • когда строка вставляется в таблицу, она вставляется с IS_LAST = 1.
  • при обновлении строки копия исходной строки будет дублироваться в той же таблице с изменением IS_LAST = 0, а исходная строка будет обновляться по мере необходимости (при этом сохраняя IS_LAST = 1).

предположим, что в моем случае строки обновляются в среднем 10 раз. также предположим, что по крайней мере 90% действий, выполняемых приложением, происходит только в последней версии строк.

моя база данных - Oracle 10g, поэтому, чтобы сохранить «активную» таблицу тонкой, мы можем разделить таблицу на 2 раздела: раздел IS_LAST = 1 и раздел IS_LAST = 0.

Является ли разбиение хорошим способом решения проблемы хранения исторических данных?

Ограничивает ли это решение потенциал других разделов этими таблицами?

спасибо!

Ответы [ 10 ]

6 голосов
/ 04 апреля 2009

Первый вопрос должен быть: что бы вы сделали с этими данными? Если у вас нет четких бизнес-требований, не делайте этого.

Я сделал что-то похожее, и после 3 лет работы осталось около 20% «достоверных данных», а остальное - «предыдущие версии». И это 10 миллионов + 40 миллионов записей. За последние три года у нас было 2 (два) запроса на изучение истории изменений, и оба раза запросы были глупыми - мы записываем отметку времени изменения записи, и нас просили проверить, работают ли люди сверхурочно (после 17:00).

Теперь мы застряли в слишком большой базе данных, которая содержит 80% данных, которые никому не нужны.

EDIT:

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

  1. Все таблицы имеют суррогатный первичный ключ.
  2. Все первичные ключи генерируются из одной последовательности. Это прекрасно работает, потому что Oracle может генерировать и кэшировать числа, поэтому здесь нет проблем с производительностью. Мы используем ORM и хотели, чтобы каждый объект в памяти (и соответствующая запись в базе данных) имели уникальный идентификатор
  3. Мы используем ORM, и отображение информации между таблицей базы данных и классом осуществляется в форме атрибутов.

Мы записываем все изменения в одну архивную таблицу со следующими столбцами:

  • id (суррогатный первичный ключ)
  • отметка времени
  • оригинальный стол
  • идентификатор оригинальной записи
  • идентификатор пользователя
  • тип транзакции (вставка, обновление, удаление)
  • записать данные как поле varchar2
    • это фактические данные в виде пар имя-поле / значение.

Всё работает так:

  • ORM имеет команды вставки / обновления и удаления.
  • мы создали один базовый класс для всех наших бизнес-объектов, который переопределяет команды вставки / обновления и удаления
    • команды вставки / обновления / удаления создают строку в форме пар имя / значение с использованием отражения. Код ищет информацию отображения и считывает имя поля, связанное значение и тип поля. Затем мы создаем что-то похожее на JSON (мы добавили несколько модификаций). Когда создается строка, представляющая текущее состояние объекта, она вставляется в архивную таблицу.
  • когда новый или обновленный объект сохраняется в таблице базы данных, он сохраняется в его целевой таблице, и в то же время мы вставляем одну запись с текущим значением в архивную таблицу.
  • когда объект удаляется, мы удаляем его из его целевой таблицы и одновременно вставляем в архивную таблицу одну запись с типом транзакции = "DELETE"

Pro:

  • у нас нет архивных таблиц для каждой таблицы в базе данных. Нам также не нужно беспокоиться об обновлении архивной таблицы при изменении схемы.
  • полный архив отделен от «текущих данных», поэтому архив не влияет на производительность базы данных. Мы помещаем его в отдельное табличное пространство на отдельном диске, и он отлично работает.
  • мы создали 2 формы для просмотра архива:
    • общий просмотрщик, который может отображать архивную таблицу в соответствии с фильтром на архивной таблице. Данные фильтра пользователь может ввести в форму (промежуток времени, пользователь, ...). Мы показываем каждую запись в форме имя / значение поля, и каждое изменение имеет цветовую кодировку. Пользователи могут видеть все версии для каждой записи, и они могут видеть, кто и когда внес изменения.
    • просмотрщик счетов - этот был сложным, но мы создали форму, которая показывает счет, очень похожий на исходную форму ввода счета, но с некоторыми дополнительными кнопками, которые могут отображать разные поколения. Потребовалось немало усилий, чтобы создать эту форму. Форма использовалась несколько раз и затем забывалась, потому что она не была нужна в текущем рабочем процессе.
  • код для создания архивных записей находится в одном классе C #. Нет необходимости в триггерах для каждой таблицы в базе данных.
  • производительность очень хорошая. В часы пик системой пользуются около 700-800 пользователей. Это приложение ASP.Net. И ASP.Net, и Oracle работают на одном двойном XEON с 8 ГБ ОЗУ.

Минусы:

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

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

2 голосов
/ 04 апреля 2009

Я бы создал две таблицы: одну для значений типа IsLast и одну для исторических. Затем я установил бы триггер, который вставляет значение в историческую таблицу каждый раз, когда обновляется isLast.

1 голос
/ 06 апреля 2009

Поскольку вы используете Oracle, вы можете проверить Oracle Flashback Technology . Он записывает изменения всех изменений в базе данных, как данных, так и структуры. Он также записывает отметку времени и имя пользователя.

Я не использовал его, но он выглядит способным.

1 голос
/ 04 апреля 2009

Если бы у меня было 1 или 2 таблицы истории, я бы сделал это именно так, как предложил Туинстул. Но если бы у вас были десятки таблиц для этого, я бы больше подошел к решению, описанному Zendar. Причина в этом.

Как вы отвечаете на такие вопросы, как,

  • Что изменилось со вчерашнего дня, когда все было хорошо?

  • Изменил ли пользователь SMITHG какие-либо изменения?

Для этих вопросов требуется один запрос на таблицу, будь то отдельная таблица _hist или раздел внутри таблицы. Неважно, это огромный список запросов. Если у вас есть центральный стол, который выглядит так, то это кусок пирога.

table_name, Column_name, PK, Before_value, After_value, User, timestamp

Вставки имеют только после значений,

Удаляет только перед значениями,

Обновление имеет оба, но только для столбцов, которые изменились.

Некоторые вариации

Вы можете включить столбец для I / U / D, если вы предпочитаете Вы можете исключить значения столбцов для вставок и просто записать PK и I, поскольку правильные значения все еще находятся в таблице.

Поскольку это Oracle, вы можете разбить на имя_сталицы, так что, по сути, у вас действительно есть одна «таблица» исторических данных на реальную таблицу.

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

0 голосов
/ 20 августа 2009

Как и другие, я использую ORM (Propel) с базовым объектом, содержащим пользовательские методы сохранения и удаления. Эти методы переопределяют стандартное сохранение и удаление, которые идут с ORM. Они проверяют, какие столбцы изменились, и создают по 1 строке в таблице изменений для каждого измененного столбца.

Схема для change таблицы: change_pk, user_fk, user_name, session_id, ip_address, метод, table_name, row_fk, field_name, field_value, most_recent, date_time

Пример: 1, 4232, Gnarls Barkley, f2ff3f8822ff23, 234.432.324.694, UPDATE, User, 4232, first_name, Gnarles, Y, 2009-08-20 10:10 : 10' ;

0 голосов
/ 10 апреля 2009

Все зависит от того, что у вас есть:

  • Вы используете Standard или Enterprise Edition? Разметка включена только в качестве опции поверх Enterprise Edition. Подробнее об этом здесь .
  • Вы можете рассмотреть возможность использования Workspace Manager , если вы ищете простое решение, в котором вам не нужно поддерживать свой собственный код. Однако есть некоторые ограничения, которые я обнаружил (например, обслуживание индекса Oracle Text кажется трудным, если не невозможным, хотя я только посмотрел на него на 10gR2).
  • В противном случае я бы выбрал решение Зволкова (живая таблица с триггером записи в таблицу истории) или решение Марка Брейди (журнал изменений). Я использовал оба шаблона, и у каждого есть свои плюсы и минусы.
  • @ zendar: запрос Flashback работает только до тех пор, пока вы отменили. Это не долгосрочное решение, а всего лишь решение, позволяющее оглянуться назад на самое большее несколько часов (в зависимости от того, сколько времени вы отменили для хранения).
0 голосов
/ 06 апреля 2009

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

0 голосов
/ 04 апреля 2009

Я бы использовал раздел IS_LAST=1 и систему разделов IS_LAST=0. Поскольку он разделен на части, он будет быстрым (сокращение раздела), и вам никогда не придется запрашивать объединение обычной таблицы и таблицы истории.

Я бы использовал IS_LAST = 'Y' / 'N', а не 1/0. 1 и 0 не имеют смысла.

Существует специальная уловка, которая может помочь гарантировать, что для каждой сущности существует не более одной строки с IS_LAST='Y': вы можете создать уникальный индекс на основе функции с функцией, которая возвращает ноль при IS_LAST='N' и возвращает идентификатор при IS_LAST='Y'. Это объясняется здесь: http://www.akadia.com/services/ora_function_based_index_1.html

0 голосов
/ 04 апреля 2009

Как вы будете определять первичные ключи? Будет много строк с одним и тем же первичным ключом из-за сохранения строк истории в одной и той же таблице.

Кроме того, у вас, похоже, нет способа узнать порядок строк истории, когда одна «настоящая» строка изменяется более чем один раз.

(Один проект, над которым я работал, мы сгенерировали все таблицы истории и триггеры с помощью codemith, это сработало очень хорошо.)

0 голосов
/ 04 апреля 2009

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

Есть ли какая-то особая причина, по которой вы не хотите использовать то, что кажется обычным решением этой ситуации?

...