Поддержание ссылочной целостности - хорошо или плохо? - PullRequest
33 голосов
/ 26 апреля 2011

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

Например, рассмотрим таблицу StudentScore, у нее мало внешних ключей (например, StudentID, CourseID) привязав его к соответствующим родительским таблицам (Student & Course).

Table StudentScore (
    StudentScoreID, -- PK
    StudentID ref Student(StudentID),  -- FK to Student
    CourseID ref Course(CourseID),   -- FK to Course
)

Если для StudentScore требуется аудит, мы планируем создать таблицу аудита StudentScoreHistory -

Table StudentScoreHistory (
    StudentScoreHistoryID, -- PK
    StudentScoreID,
    StudentID,
    CourseID,
    AuditActionCode,
    AuditDateTime,
    AuditActionUserID
)

Если есть строка в StudentScoreПосле изменения мы переместим старую строку в StudentScoreHistory.

Одним из вопросов, поднятых в ходе обсуждения дизайна, было сделать StudentID и CourseID в таблице StudentHistory FK, чтобы сохранить ссылочную целостность.Аргумент в пользу этого состоял в том, что мы всегда в основном делаем мягкое (логический булевский флаг) удаление, а не полное удаление, и это хорошо для сохранения ссылочной целостности, чтобы гарантировать, что в таблице аудита нет бесхозных идентификаторов.

Table StudentScoreHistory (
    StudentScoreHistoryID, -- PK
    StudentScoreID,
    StudentID ref Student(StudentID), -- FK to Student
    CourseID ref Course(CourseID), -- FK to Course
    AuditActionCode,
    AuditDateTime,
    AuditActionUserID
)

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

Теперь мой вопрос: Это хороший дизайн?иметь эти внешние ключи в таблицах истории?

Любая информация о ключевых аргументах (бывшая производительность, лучшие практики, гибкость проектирования и т. д.) будет высоко оценена.

Для пользы любого, кто ищетдля конкретной цели и нашей среды:

Цель:

  1. Ведение истории критических данных
  2. Разрешить аудит активности пользователей с поддержкой воссоздания сценария
  3. В ограниченной степени разрешить откат пользовательской активности

Среда:

  • Транзакционная база данных
  • Не для каждой таблицы требуется аудит
  • Использованиемягкое удаление в максимально возможной степени, особенно для статических / справочных данных
  • Немногие таблицы с высокой степенью транзакций используют жесткое удаление

Ответы [ 9 ]

25 голосов
/ 14 мая 2011

При обсуждении одитинга я бы хотел вернуться к цели, стоящей за этим.На самом деле это не резервная копия, а история того, что было.Например, для StudentScore вы хотели бы быть уверенным, что не потеряете тот факт, что у ученика изначально было 65%, тогда как у него сейчас 95%.Этот контрольный журнал позволит вам вернуться к изменениям, чтобы увидеть, что произошло и кто это сделал.Исходя из этого, вы можете определить, что конкретный пользователь сделал, чтобы злоупотреблять системой.В некотором смысле это может быть типом резервного копирования, поскольку вы можете откатить эти изменения до их прежних состояний без отката целых таблиц.

Имея это в виду (если мои предположения о том, для чего вы используете это, верны), единственное место, где вы хотели бы видеть отношения FK / PK, это между таблицей истории и ее «живым» аналогом.Ваша таблица аудита (истории) не должна ссылаться на какую-либо другую таблицу, потому что она больше не является частью этой системы.Вместо этого это просто запись того, что произошло в одной таблице.Период.Единственная ссылочная целостность, которую вы, возможно, захотите рассмотреть, находится между таблицей истории и активной таблицей (таким образом, возможное отношение FK / PK).Если вы разрешаете удалять записи из оперативной таблицы, не включайте FK в таблицу истории.Тогда таблица истории может включать в себя удаленные записи (это то, что вы хотите, если вы разрешаете удаление).

Не путайте целостность реляционных данных в основной базе данных с этой таблицей истории.Таблицы истории все автономны.Они служат только историей одной таблицы (не набора таблиц).

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

6 голосов
/ 17 мая 2011

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

Поскольку информация в таблицах PK меняется, будьте осторожны с emptor. Настройка FKs была бы простым способом получить некоторую возможность отслеживания, но она не будет идеальной. Есть компромиссы. Чтобы получить абсолютно совершенную историю, вам, в основном, нужно создавать резервные копии всех связанных записей, каждый раз, когда в записи кандидата на аудиторию что-то происходит. Вам необходимо выяснить уровень детализации, который уместен, и идти с ним, потому что идеальная запись событий может быть сложна в настройке и потребляет много места в процессе.

Кроме того, это может или не может быть вариант для вас, но я бы настоятельно рекомендовал комбинацию инструментов, таких как ApexSQL Audit + ApexSQL Log , в отличие от собственного решения для аудита , Исходя из ваших потребностей, эти два инструмента в сочетании с периодическим архивированием журналов транзакций будут охватывать то, что вам нужно сделать. Средство аудита может хранить данные в той же базе данных или в другом месте, а средство ведения журнала может выборочно восстанавливать данные. Просто мысль.

6 голосов
/ 16 мая 2011

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

Вместо того, чтобы сохранять CourseID как «1», это будет «HTML4».Таким образом, если значение внешнего ключа будет удалено, таблица аудита по-прежнему будет действительной.Это также будет сохраняться, если значение Внешнего ключа будет изменено с «HTML4» на «HTML5» в любое время в будущем.Если вы сохранили только внешний ключ, вы бы сказали аудитору, что предыдущие студенты делали «HTML5», что неверно.

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

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

4 голосов
/ 14 мая 2011

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

Предположим, например, что у вас есть что-то вроде этого:

table scores (
 score_id,
 student_id ref students (student_id),
 course_id ref courses (course_id),
 score_date,
 score,
 pkey (score_id)
)

В этом случаеИмеет смысл использовать каскадный fkey для удаления, ссылающийся на Score (Score_id) на Score_logs.Это объект;если он будет удален жестко, он также может отказаться от истории.

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

Также обратите внимание, что есть случай, когда fkeys работает против вас.Если у вас есть строка заказа в заказе, и строка заказа удаляется, вам все еще нужна история в этой строке заказа.Правильный ключ для использования в этом случае - это order_id, а не order_line_id.

Последнее замечание на случай, если вы решите оставить ключи - подумайте, на что они должны указывать.Имея несвязанные фрагменты данных (например, студенты и курсы), разумно предположить, что живой ряд в порядке.Однако при наличии сильно связанных частей данных (например, о продуктах и ​​рекламных материалах) вам действительно нужно ссылаться как на fkey, так и на его версию.

В отношении двух предыдущих пунктов вы можете найти эту связанную темуи интересно ответить:

Как создать контрольный журнал для совокупных корней?

3 голосов
/ 27 апреля 2011

Если ваша система действительно ориентирована на обработку транзакций, тогда мой ответ может не подходить вам, но в мире хранилищ данных / BI эта проблема часто решается с использованием «звездной схемы». При таком подходе вы бы денормализовали важную ориентировочную информацию из связанных таблиц вместе с вашими записями аудита. Это может включать значения PK родительских таблиц (то есть значения FK в вашей проверенной таблице). Однако вы не сохраните сами фактические ограничения ссылочной целостности.

Таким образом, в вашем примере таблица StudentScoreHistory может сохранить свой столбец StudentID без ограничения FK, а также, возможно, StudentName (или то, что, по вашему мнению, вам может понадобиться от Student). Таким образом, вы можете вернуться к своему контрольному журналу, чтобы собрать воедино то, что произошло и когда, не беспокоясь о том, жестко ли вы удаляете родительские записи. Это имеет дополнительное преимущество (или недостаток, в зависимости от вашей перспективы) в отслеживании изменяемых атрибутов родительской таблицы, какими они были при первоначальной записи дочерней записи. Например, было бы полезно узнать, что студентка 123456, которая сейчас является миссис Marriedlady, была мисс Синглджирл, когда ей присваивалась степень по биологии.

2 голосов
/ 19 мая 2011

Находясь в середине внедрения очень похожей системы аудита в первый раз, я в настоящее время сталкиваюсь с той же проблемой.Мое мнение перекликается с мнением BiggsTRC: ваша «живая» таблица поддерживает отношение FK к записи курса, а ваша таблица истории поддерживает связь только с ее «живым» аналогом (StudentScore).Это, я думаю, позволяет добиться отсутствия детей-сирот в таблице аудита.

Теперь есть кое-что еще, что я не видел, упомянутое в ответах: в нашем текущем проекте мы увидели ценность сохранения FK в истории.от таблицы к таблице CourseHistory, чтобы мы знали, каково было «состояние» записи курса во время записи аудита StudentScoreHistory.Конечно, это может иметь или не иметь значения для вас, в зависимости от логики вашей системы.

Наше решение вашей проблемы (в вашем ответе на BiggsTRC) о том, что у вас может быть один и тот же CourseId несколько раз, заключалось в том, чтобы ссылаться не на фактический идентификатор CourseId, а на столбец PK таблицы CourseHistory.У нас до сих пор нет твердого решения, как это сделать - хотим ли мы создать контрольную запись в записи курса, даже если изменений не было, или попытаться ввести некоторую логику для поиска записи CourseHistory, соответствующей соответствующему курсусостояние во время записи StudentScoreHistory.

2 голосов
/ 27 апреля 2011

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

Вы ставите вопрос о программном удалении, что смущает проблему. Это будет актуально, только если вы рассматриваете возможность использования внешних ключей между двумя схемами, например, StudentScoreHistory ссылки StudentScore. Это может быть правильным решением, но опять же, оно предполагает, что вы не доверяете своему механизму аудита. Лично я предпочел бы иметь жесткие удаления в живых таблицах и записывать факт удаления в таблицу истории. Мягкие удаления - еще один источник горя.

Но в любом случае это другой вопрос. Совершенно возможно иметь внешние ключи между живой и исторической версиями каждой таблицы, например, StudentScoreHistory -> StudentScore без принудительного обеспечения реляционной целостности в схеме History, например, StudentScoreHistory -> StudentHistory.

1 голос
/ 26 апреля 2011

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

0 голосов
/ 17 мая 2011

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

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

...