保持参考完整性 - 好还是坏?

时间:2011-04-26 17:54:11

标签: sql database-design referential-integrity audit-trail

我们计划在数据库中引入简单的审计跟踪,使用触发器和每个需要审计的表的单独历史表。

例如,考虑表StudentScore,它几乎没有外键(例如,StudentID,CourseID)将其链接到相应的父表(学生和课程)。

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。

在设计讨论期间提出的一点是在StudentHistory表中将StudentID和CourseID设为FK,以保持参照完整性。支持这一点的论据是因为我们总是主要做一个软(逻辑布尔标志)删除而不是硬删除,它有利于保持参照完整性以确保我们在审计表中没有任何孤儿ID

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

这对我来说似乎有点奇怪。我同意@Jonathan Leffler's comment审计记录不应该停止删除父数据。相反,如果需要,则应通过主表中的外键处理,而不是在审计表中处理。我想得到你的意见,以确保我没有错过将外键扩展到审计表的一些价值。

现在我的问题是: 在历史记录表中使用这些外键是一个好设计吗?

关键参数(e.x.性能,最佳实践,设计灵活性等)的任何细节都将受到高度赞赏。

为了寻求特定目的和环境的人的利益:

目的:

  1. 维护关键数据历史记录
  2. 允许审核用户活动并支持重新创建方案
  3. 在有限范围内允许回滚用户活动
  4. 环境:

    • 交易数据库
    • 并非每个表都需要审核
    • 尽可能使用软删除,特别是静态/参考数据
    • 很少有高度交易的表格使用硬删除

9 个答案:

答案 0 :(得分:25)

在讨论审计时,我会回到它背后的目的。它不是真正的备份,而是历史。例如,对于StudentScore,您可能希望确保不会失去这样一个事实:当学生现在拥有95%时,他们最初有65%。此审计跟踪将允许您返回更改以查看发生了什么以及由谁完成。由此,您可以确定特定用户滥用系统的行为。在某些方面,这可能是一种备份类型,因为您可以将这些更改回滚到以前的状态而不会回滚整个表。

考虑到这一点(如果我对你使用它的假设是正确的),你想要FK / PK关系的唯一地方是历史表和它的“实时”对应物。您的审计(历史)表不应引用任何其他表,因为它不再是该系统的一部分。相反,它只是一个表中发生的事情的记录。期。您可能要考虑的唯一参照完整性是历史表和实时表(因此可能的FK / PK关系)。如果允许从实时表中删除记录,请不要在历史记录表中包含FK。然后,历史记录表可以包含已删除的记录(如果允许删除,则为您所需的记录)。

不要与主历史记录表中的主数据库中的关系完整性混淆。历史表都是独立的。它们仅用作一个表的历史(不是一组表)。

两个历史表的关联是可能的,甚至更高级的现场和历史表之间的关系(例如,有生活和历史的学生和课程),所以你甚至可以处理学生被删除的可能性(颤抖)因为记录仍然在历史表中。这里唯一的问题是如果你没有保留特定表的历史记录,在这种情况下你选择丢失这些数据(如果你允许删除)。

答案 1 :(得分:6)

我建议不要将外键扩展到审计表。我的建议是将审计中的数据扩展到外键值。

不是将CourseID存储为“1”,而是“HTML4”。这样,如果删除了外键值,则审计表仍然有效。如果外键值在将来的任何时间从“HTML4”更改为“HTML5”,这也将成立。如果您只存储了外键,那么您将告诉审核员以前的学生做了“HTML5”,这是不正确的。

另一个很大的好处是能够将审计跟踪发送到另一台服务器进行数据挖掘而没有任何问题。

我已经使用了上述设置一段时间了,它对我有用。

答案 2 :(得分:6)

如果您需要重新创建场景,那么我会说是的,您需要FK,并且拥有它们我认为这将是一种更容易的方式来跟踪相关的相关详细记录。但是,这会导致删除问题,以及可能在主键表中更改的信息。在这种情况下,我会说你想删除其他表中有FK的记录,而是使用你已经指出的软删除。

就PK表中的信息变化而言,请注意。设置FK将是一种获得某些追溯能力的简单方法,但它并不完美。有权衡。要获得绝对完美的历史记录,您基本上需要创建所有相关记录的备份副本,只要审计候选记录在其上发生了某些事情。你需要弄清楚适当的粒度级别并与它一起使用,因为完整的事件记录可能很复杂,并且在此过程中占用了大量空间。

此外,这可能是也可能不是您的选择,但我强烈考虑使用ApexSQL Audit + ApexSQL Log等工具的组合,而不是本土的审核解决方案。根据您的需求,这两个工具与定期归档您的事务日志相结合将涵盖您需要执行的操作。审计工具可以将数据存储在同一个数据库或其他地方,日志工具可以有选择地恢复数据。只是一个想法。

答案 3 :(得分:4)

你的milage显然会随着情况而变化,但根据我的经验,使用原始表的主键保持参照完整性,而不是更多。这样可以避免历史记录中的孤立ID,同时允许与相关表进行流畅的交互。

假设您有类似这样的事情:

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

在这种情况下,在score_logs上引用删除级联fkey引用分数(score_id)是有意义的。这是对象;如果它被硬删除,也可以丢弃历史记录。

相比之下,student_id和course_id上​​的外键在我的经验中没有多少意义。它们意味着您不能对学生和课程进行(硬)删除 - 即使没有引用它们的实时行也存在。这可能是您想要实现的,在这种情况下忽略提示。就我而言,我发现自己需要修剪用户,评论,产品,订单等等;历史日志中的外键使这很不方便。

另外,请注意,有一种情况是fkeys会对你不利。如果您在订单上有订单行,并且订单行被删除,您仍然需要该订单行上的历史记录。在这种情况下使用的正确pkey是order_id,而不是order_line_id。

最后一点,如果你最终选择保留fkeys:考虑他们应该指向什么。通过分离的数据(例如学生和课程),可以合理地假设实时行是好的。然而,对于强耦合的数据片段(例如产品和促销),您真正想要的是引用fkey及其版本。

回到前两点,你可能会发现这个相关的主题并回答有趣:

How do you create an audit trail for aggregate roots?

答案 4 :(得分:3)

如果您的系统真的专注于事务处理,那么我的答案可能不适合您,但在数据仓库/ BI世界中,这个问题通常通过使用“星型模式”来解决。在这种方法中,您可以将链接表中的重要指示信息与审计记录进行非规范化。这可能包括父表的PK值(即审计表上的FK值)。但是,您不会保留实际的参照完整性约束本身。

因此,对于您的示例,您的StudentScoreHistory表可以保留其StudentID列,不包含FK约束,也可以保留StudentName(或您认为可能需要学生的任何内容)。通过这种方式,您可以返回审计跟踪,将已发生的事情和时间拼凑在一起,而无需担心您是硬还是软删除父记录。这具有跟踪可更改父表属性的进一步优势(或缺点,取决于您的观点),就像最初记录子记录时一样。例如,知道学生123456,现在是Marriedlady夫人,在她获得生物学学位时曾经是Singlegirl女士,这可能是有用的。

答案 5 :(得分:2)

您的实时架构强制实施关系完整性,因此您不需要历史架构中的外键。或者换一种说法:在历史模式中的表之间强制执行外键的唯一原因是,除了从实时模式中的更改填充DML之外,还有一些机制可以对历史模式执行DML。在这种情况下,您的历史模式作为审计跟踪非常无用。

您提出了软删除的问题,这会使问题混乱。如果您考虑在两个模式之间使用外键,那只会是相关的,例如StudentScoreHistory引用StudentScore。这可能是一个有效的设计,但同样,它表明你不相信你的审计机制。我个人更喜欢在实时表中进行硬删除,并在History表中记录删除的事实。软删除只是悲伤的另一个来源。

但无论如何这是一个不同的问题。完全可以在每个表的实时版本和历史版本之间具有外键,例如StudentScoreHistory -> StudentScore还没有在历史架构中强制执行关系完整性,例如StudentScoreHistory -> StudentHistory

答案 6 :(得分:2)

首次实施非常相似的审计系统,我目前正面临着同样的担忧。我的观点与BiggsTRC的观点相呼应 - 你的"生活" table维护与课程记录的FK关系,并且您的历史记录表仅保持与其" live"的关系。对应(StudentScore)。我认为,这在审计表中没有孤儿。

现在,在答案中还有一些我没有看到的东西:在我们当前的项目中,我们看到了在历史表中将FK维护到CourseHistory表的价值,因此我们知道,&#34是什么;状态" StudentScoreHistory审核条目时的课程记录。当然,根据您的系统逻辑,这可能对您有所影响,也可能无关紧要。

我们解决您的问题(在您对BiggsTRC的回答中),您可能会多次使用相同的CourseId,而不是引用实际的CourseId,而是引用CourseHistory表的PK列。我们仍然没有做出如何做到这一点的坚定决定 - 我们是否想要创建课程记录的审核条目,即使没有更改,或者尝试引入一些逻辑来查找匹配的CourseHistory记录StudentScoreHistory录入时的相关课程状态。

答案 7 :(得分:1)

如果您只打算按照描述进行软删除,那么我认为没有理由不使用外键。

答案 8 :(得分:0)

我不会为“审计”行创建第二组表,只需将审计功能集成到现有的生产模式中。听起来您的目的不是在给定日期/灾难时备份和恢复,而是跟踪每个用户或学生的更改历史记录,这是您的应用程序的功能。我认为您的其他字段很好,它们不需要添加到另一组表中。

备份和还原过程的一个问题是架构更改。模式往往会随着时间的推移而发生变化,这意味着您可能无法直接从备份中恢复。如果您将审计功能保留在生产模式中,那么当您需要支持其他功能时,您不必担心会破坏任何内容。