Поддержание ссылочной целостности - хорошо или плохо?

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

Например, рассмотрим таблицу 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
)

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

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

Любые подробности по ключевым аргументам (например, производительность, передовой опыт, гибкость дизайна и т. Д.) Были бы весьма признательны.

Для всех, кто ищет конкретную цель и нашу среду:

Цель:

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

Окружающая обстановка:

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

person YetAnotherUser    schedule 26.04.2011    source источник


Ответы (9)


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

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

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

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

person IAmTimCorey    schedule 14.05.2011
comment
Да - этот вопрос игнорирует различие между реляционными данными и файлом журнала. Нет смысла применять структуры и требования RDB - но файл журнала имеет свои собственные структуры и требования. - person dkretz; 17.05.2011
comment
Согласитесь с вашими пунктами, кроме «возможно связывание двух таблиц истории» - не видел, как мы можем это реализовать. В таблице CourseHistory может быть несколько CourseID: 15 - что бы мы снова поддерживали FK. Кроме того, любые предложения о возможных недостатках (производительность и т. Д.) В случае, если мы действительно поддерживаем FK между таблицей StudentHistory и Course. Спасибо за ваше время! - person YetAnotherUser; 19.05.2011

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

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

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

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

person Imraan    schedule 16.05.2011
comment
Курс - это больше, чем просто имя, он может иметь дополнительные соответствующие поля, включая FK (InstructorID) для другой таблицы. Не уверен, возможно ли расширить все данные. Я отредактирую свой вопрос, чтобы включить это. - person YetAnotherUser; 17.05.2011
comment
@YetAnotherUser Если вам нужно вернуться к справочной таблице для получения другой информации, вам придется сохранить внешние ключи в таблице аудита. Чтобы выполнить эту работу, вам нужно будет реализовать мягкое удаление и убедиться, что справочные данные не изменяются, чтобы сделать таблицу аудита недействительной, то есть создать новую запись для курса HTML5, а не редактировать запись HTML4. - person Imraan; 17.05.2011
comment
Я бы сохранил «CourseID» в таблице аудита, не уверен, что понимаю, зачем мне нужно поддерживать FK. Я всегда могу вернуться и посмотреть аудит связанных таблиц на случай, если мне когда-нибудь понадобится отменить действие. - person YetAnotherUser; 19.05.2011
comment
@YetAnotherUser Если вы хотите сохранить ссылочную целостность, я бы предложил добавить ограничение внешнего ключа в таблицу аудита. Вы можете выполнить поиск в связанной таблице, а данных там больше нет. - person Imraan; 19.05.2011
comment
Мне нравится идея аудита всех таблиц, чтобы мы всегда могли ссылаться на всю запись, на которую указывает идентификатор, а не только на ее имя или описание. Я, вероятно, храню гораздо больше данных, чем мне когда-либо понадобится, но лучше перестраховаться, чем сожалеть, а в конце концов, дисковое пространство стоит дешево. Я предложил модель аудита на основе триггеров: codeproject.com/Articles/1112660/ - person drizin; 24.10.2016

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

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

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

person Brett Rossier    schedule 17.05.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_id) в score_logs. Это объект; если он будет жестко удален, можно также удалить историю.

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

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

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

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

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

person Denis de Bernardy    schedule 14.05.2011

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

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

person Joel Brown    schedule 27.04.2011
comment
Да - в моем случае это база данных транзакций, но интересный подход. Я расскажу об этом подробнее. Если вы собираетесь вернуться к тому, что произошло, в исходном примере все должно быть просто до тех пор, пока мы не будем вести контрольный журнал всех критических таблиц / сущностей. Мы можем вернуться к таблице StudentHistory и посмотреть, как звали миссис Marriedlady в определенный момент времени. - person YetAnotherUser; 28.04.2011

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

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

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

person APC    schedule 27.04.2011
comment
Я согласен, что моя действующая схема должна обеспечивать реляционную целостность. Меня больше беспокоит полезность использования FK StudentScoreHistory -> Student и StudentScoreHistory -> Course - от этого (SSH -> S) пахнет нечистым дизайном. - person YetAnotherUser; 28.04.2011

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

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

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

person G. Stoynev    schedule 19.05.2011

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

person Paul Creasey    schedule 26.04.2011
comment
Разве это не сделало бы конструкцию более жесткой без видимой выгоды? Кроме того, концептуальный аудит не должен мешать работе остальной системы. Без внешних ключей в таблице аудита мы сделали бы на одно предположение меньше (мягкое удаление) о системе. - person YetAnotherUser; 27.04.2011
comment
Не думайте о своем аудите / истории отдельно от системы: это часть системы. Если вы хотите вести точную историю, тогда полезны FK в таблицах истории, так же как полезны FK в таблицах живых данных. - person Vince Bowdren; 16.05.2011

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

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

person Beth    schedule 17.05.2011
comment
Независимо от того, является ли аудит функцией приложения или просто дополнительным спокойствием для администраторов баз данных / безопасности, действительно зависит от приложения ... Похоже, что это просто дополнительная активность на стороне базы данных, которую любые компоненты внешнего приложения не видны. С учетом всего сказанного, стоит подумать, должно ли это быть функцией приложения! - person Tao; 18.05.2011
comment
@Tao, внешние приложения будут иметь ограниченную видимость таблиц аудита - в виде отчетов журнала аудита и т. Д. Хотя это определенно не будет функцией приложения (если вы хотите рассматривать аудит как отдельный независимый модуль). - person YetAnotherUser; 19.05.2011
comment
@Beth - мы бы хранили таблицы аудита в той же базе данных, что и основные таблицы. Кроме того, любые изменения схемы в основной таблице также будут реплицированы в таблицы истории. Не возражаете ли вы подробнее остановиться на «интеграции ваших функций аудита в существующую производственную схему» - вы имеете в виду, что производственные и исторические данные должны храниться в одних и тех же таблицах? Как вы поддерживаете правильную индексацию и pks? Разве это не накладные расходы на основное приложение? - person YetAnotherUser; 19.05.2011
comment
Это может вызвать накладные расходы на основное приложение, если вы ожидаете частого резервного копирования. Да, идея заключалась бы в том, чтобы сохранить детали аудита в тех же таблицах, что и основные, с полями маркеров, чтобы различать их, как вы это делаете для мягких удалений. Индексирование и PK должны быть в порядке, если вы не хотите повторно использовать PK после его аудита? В этом случае ваш контрольный маркер должен быть частью ПК. - person Beth; 19.05.2011
comment
Если вам нужно восстановить проверенные данные, как если бы они никогда не удалялись, будет проще, если вы сохраните их все вместе. Если можно, чтобы ИТ-специалисты совершили какое-то волшебство для восстановления, или если объем аудита заметно влияет на производительность, то, во что бы то ни стало, держите его отдельно. Вы можете делать все, что хотите, пока пользователи не смотрят. - person Beth; 19.05.2011