35

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

例如考虑表 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 的评论,即审计记录不应停止删除父数据。相反,如果需要,应该通过主表中的外键而不是审计表中的外键来处理。我想听听你的意见,以确保我不会错过将外键扩展到审计表的一些价值。

现在我的问题是: 在历史表中包含这些外键是一个好的设计吗?

任何关于关键论点的细节(例如性能、最佳实践、设计灵活性等)都将受到高度赞赏。

为了任何寻求特定目的和我们环境的人的利益:

目的:

  1. 维护关键数据历史
  2. 允许审核用户活动并支持重新创建场景
  3. 在有限的范围内允许回滚用户活动

环境:

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

9 回答 9

30

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

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

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

将两个历史表关联在一起是可能的,并且实时表和历史表之间的关系甚至更高级(例如,同时具有实时和历史的学生和课程),因此您甚至可以处理学生被删除的可能性(不寒而栗)因为该记录仍将在历史记录表中。这里唯一的问题是如果您不保留特定表的历史记录,在这种情况下您选择丢失该数据(如果您允许删除)。

于 2011-05-14T04:38:05.973 回答
6

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

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

另一个巨大的好处是能够将审计跟踪发送到另一台服务器进行数据挖掘,而不会出现任何问题。

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

于 2011-05-16T12:00:28.433 回答
6

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

至于 PK 表中的信息发生变化,请自告奋勇。设置 FK 是获得一些追溯能力的简单方法,但它并不完美。有取舍。为了获得绝对完美的历史记录,您基本上需要创建所有相关记录的备份副本,只要审计候选记录发生了什么事。您需要确定合适的粒度级别并与之配合,因为完美的事件记录可能很难设置,并且在此过程中会占用大量空间。

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

于 2011-05-17T18:17:16.923 回答
4

您的里程显然会因情况而异,但根据我的经验,请保持原始表的主键的引用完整性,仅此而已。这允许避免历史记录中的孤立 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 上有一个 on delete 级联 fkey 引用 score(score_id) 是有意义的。这是对象;如果它被硬删除,还不如丢弃历史。

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

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

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

关于前两点,您可能会发现这个相关的线程并回答有趣:

如何为聚合根创建审计跟踪?

于 2011-05-14T14:08:49.437 回答
3

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

因此,对于您的示例,您的 StudentScoreHistory 表可以保留其 StudentID 列,而没有 FK 约束,以及 StudentName(或您认为可能需要 Student 的任何内容)。通过这种方式,您可以返回您的审计跟踪,将发生的事情和时间拼凑起来,而不必担心您是硬删除还是软删除父记录。这具有进一步的优势(或劣势,取决于您的观点),即在最初记录子记录时跟踪可更改的父表属性。例如,知道学生 123456(现在是 Mrs. Marriedlady)在获得生物学学位时曾经是单身女郎小姐,这可能会很有用。

于 2011-04-27T01:04:29.023 回答
3

您的实时模式强制执行关系完整性,因此您不需要历史模式中的外键。或者换一种说法:在 History 模式中的表之间强制执行外键的唯一原因是,是否有某种机制可以针对 History 模式执行 DML,而不是从实时模式中的更改中填充它。在这种情况下,您的 History 模式作为审计跟踪毫无用处。

您提出了软删除的问题,这使问题变得混乱。仅当您考虑在两个模式之间使用外键时才有意义,例如StudentScoreHistoryreferences StudentScore。这可能是一个有效的设计,但同样,它表明您不信任您的审计机制。就我个人而言,我更喜欢在实时表中进行硬删除,并在历史表中记录删除的事实。软删除只是另一个悲伤的来源。

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

于 2011-04-27T06:31:02.737 回答
2

作为第一次实施非常相似的审计系统,我目前面临同样的担忧。我的观点与 BiggsTRC 的观点相呼应——您的“实时”表保持与课程记录的 FK 关系,而您的历史表仅保持与其“实时”对应项 (StudentScore) 的关系。我认为,这实现了审计表中没有孤儿。

现在,还有一些我在答案中没有看到的东西:在我们当前的项目中,我们看到了在历史表中维护一个 FK 到 CourseHistory 表的价值,这样我们就知道了 Course 的“状态”是什么StudentScoreHistory 审计条目时的记录。当然,这对您来说可能或可能无关紧要,具体取决于您的系统逻辑。

Our solution to your concern (in your answer to BiggsTRC), that you might have the same CourseId several times was to reference not the actual CourseId, but the PK column of CourseHistory table. We still don't have a firm decision how to accomplish this - whether we want to create audit entry of the Course record even if there was not a change, or try to introduce some logic to look-up the CourseHistory record that matches relevant Course state at time of StudentScoreHistory entry.

于 2011-05-19T04:53:03.513 回答
1

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

于 2011-04-26T18:07:42.623 回答
0

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

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

于 2011-05-17T14:55:12.367 回答