我们目前正在做一个Web应用程序,其中一个功能是由用户创建事件。用户或管理员稍后可以删除这些事件。但是,客户端要求事件不是从数据库中实际删除,而是标记为已删除。用户应该只能看到未删除的事件,但管理员也应该能够浏览已删除的事件。这就是真正的功能。
现在我建议我们只需添加一个名为“status”的额外列,它将有几个有效值:ACTIVE和DELETED。通过这种方式,我们可以区分正常(活动)和已删除事件,并创建非常简单的查询(SELECT * FROM EVENTS WHERE STATUS ='ACTIVE')。 然而,我的同事不同意。他指出,无论现在活动事件和已删除事件是否共享相同信息(因此它们可以存储在同一个表中),以后的需求,我的更改和客户端将需要存储有关已删除事件的一些其他信息(比如删除日期,谁删除了它,为什么要删除它 - 有点评论)。他说,为了在将来满足这些要求,我们必须在EVENTS表中添加其他列,这些列将保存特定于已删除事件的数据,而不是活动事件。他提出了一个解决方案,其中创建了与EVENTS表具有相同模式的附加表(如DELETED_EVENTS)。每个已删除的事件都将从EVENTS表中进行物理删除,并移至DELETED_EVENTS表。
我强烈反对他的想法。它不仅会使SQL查询更复杂,效率更低,而且完全违背了YAGNI。我也不同意他的观点,如果未来需求发生变化,我的想法将使我们在EVENTS表中创建额外的(不可为空)列。在我的场景中,我只需创建一个新的表,如DELETED_EVENTS_DATA(它将保存那些额外的存档数据),并在EVENTS表中添加引用键,以维护EVETNS和DELETED_EVENTS_DATA表之间的一对一关系。
然而,由于两个通常对软件和数据库设计有着相似看法的开发人员对于如何在数据库级别设计这些需求有如此根本不同的看法,我感到很困惑。我认为我们可能都朝着错误的方向前进,还有另一个(第三个)解决方案?或者只有一种替代方案? 你如何设计这种要求?有关如何正确完成的任何模式或指导方针吗?任何帮助将深表赞赏
答案 0 :(得分:3)
不要使用状态列。
至少应该有datedleted和deletedby列。只是知道删除了某些东西是没有用的,即使客户第一次看到他们想要知道的已删除事件时,他们现在没有要求它,以便辨别出原因。
如果事件表的大小可能会变得非常大,通常会将已删除/存档的数据完全移动到另一个表中。通常,您将这些表分配给不同的数据库文件。该文件通常位于不同的驱动器上以保持性能。我不是说一个全新的数据库,只是一个不同的数据库文件。
如果将它保存在同一个表中,则所有查询都应该有一个where子句(DateDeleted为null)。如果将信息移到另一个表格,显然你没有这个要求。这就是我推荐这种做法的原因。
答案 1 :(得分:2)
我发现在每个事件(创建,更新等)中拍摄对象的快照,并将这些快照(以及日期和用户信息)存储在另一个表中,这样您就可以在生命周期内满足各种历史跟踪需求一个申请。然后,您可以向用户显示快照,向用户显示按时间顺序的更改,在给定日期推断出对象的状态等。
我确信那里有正式的设计模式 - 这只是我随着时间的推移而改进而且运作良好的模式。但是,磁盘空间效率不高。
编辑:此外,当用户删除对象时,我会将记录标记为已删除,并为历史记录表创建最终快照。您可以无限期地从界面隐藏对象,或者您可以选择显示它 - 取决于使用需求。
答案 2 :(得分:0)
在这种情况下,这通常是一种判断。不知道比你告诉我的更多,我倾向于使用你的解决方案,这只是虚拟删除。我相信你对YAGNI的应用是好的。如果用户将来会在事件生活中提供日志记录阶段的要求,那么此时您可能无法正确猜出这些要求是什么。如果用于处理数据库中事件的逻辑被很好地封装(以后容易更改),则尤其如此。
但是,如果你很了解这个客户端,并且你知道它们具有相似类型的历史类型要求,并且功能将无法很好地封装,那么你的同事可能会做出很好的猜测。这里的关键是,无论你们中哪一个是正确的,它都不是太多。双方都有优点。
顺便说一下,最好有一个布尔(是/否)IsDeleted列,索引以该列开头。这会更快,尽管它可能不会产生足够大的差异。
答案 3 :(得分:0)
我现在要添加标记字段,只有在你积极知道你将要做什么的时候才打算计划其余部分,系统还积累了真实的数据和用户体验,所以你有一些数据要基于您的性能/复杂性设计决策。
答案 4 :(得分:0)
确定我们处理它的方式如下。
我们在每个名为'Deleted'的表上都有一个额外的列,这是一个位字段。然后你正确地说你的查询非常简单,因为它只是一个where子句来过滤它们或让它们进入。只有你需要确保你生成的任何报告或统计信息过滤掉已删除的记录。
然后,对于您正在谈论想要捕获的额外信息,这些额外的信息将进入单独的“审计”表中。在我们的例子中,我们使这个额外的表非常通用,它可以保存任何表的审计信息...请参阅下面的工作原理......
Event
EventId EventName ... Deleted
1 Dinner 0
2 Supper 1
3 Lunch 0
4 Lunch 1
Audit
AuditId EntityTypeId EntityId ActionTypeId ActionDateTime ... etc
1 1 (Event) 2 (EventId) 1 (Deleted) 2/1/2010 12:00:00
1 1 (Event) 4 (EventId) 1 (Deleted) 3/1/2010 12:00:00
现在,如果你想要捕获其他实体(比如位置 - 其中Location是一个表),它看起来就像这样......
Audit
AuditId EntityTypeId EntityId ActionTypeId ActionDateTime ... etc
1 1 (Event) 2 (EventId) 1 (Deleted) 1/1/2010 12:00:00
1 1 (Event) 4 (EventId) 1 (Deleted) 2/1/2010 12:00:00
1 2 (Event) 2 (LocationId) 1 (Deleted) 3/1/2010 12:00:00
1 2 (Event) 8 (LocationId) 1 (Deleted) 4/1/2010 12:00:00
1 2 (Event) 9 (LocationId) 1 (Deleted) 5/1/2010 12:00:00
然后当你想要获得额外的审计数据时,你所说的非常简单。查询看起来像这样
SELECT *
FROM Event E
INNER JOIN Audit A
ON E.EventId = A.EntityId
WHERE E.Deleted = 1
AND A.EntityTypeId = 1 -- Where 1 stands for events
此审计表也可以捕获其他事件,而不仅仅是删除...这是通过使用ActionTypeId列完成的。目前它只有1(这是删除),但你也可以有其他人。
希望这有帮助
编辑:
除此之外,如果我们有强大的审计要求,我们会执行以下操作...以上所有更改都没有,但我们创建了第二个名为“xyz_Audit”的数据库,该数据库捕获数据库中发生的每个操作的前置和后置。第二个数据库与第一个数据库具有相同的模式(没有Audit表),除了每个表有2个额外的列。
第一个额外的列是PrePostFlag,第二个列是AuditId。因此,主键现在跨越3列,'xyzId','PrePostFlag'和'AuditId'。
通过这样做,我们可以让管理员有充分的权力知道谁做了什么,更改了数据以及如何更改,并取消删除记录,我们只需要更改主数据库中的已删除标志。
此外,通过将这些数据放在不同的数据库中,它允许我们对主跨国数据库进行不同的优化,存储和管理计划。
答案 5 :(得分:0)
这很大程度上取决于表格的大小以及您是否真的需要有关删除的附加信息。
在大多数情况下,只需删除标记字段即可。然后创建一个视图,选择尚未删除记录的记录。将视图用于用户的所有查询,而不是直接访问表。
如果您有审核,则您已经知道谁将该记录标记为已删除以及何时删除。
如果没有,您应该将这些字段添加到您的表格中。
我可能会定期将已删除的记录删除到存档表中,以便在主表上提高查询性能。假设删除已删除超过6个月的所有已删除记录。然后有一个视图,它结合了普通表和存档表,供管理员查询。
这两种方法结合使用视图可以让你获得两全其美的效果,你的表格在查询时保持小而且每个人只能看到他们需要查看的记录,并且相对容易取消删除意外删除的内容,归档旧记录可能发生在当天的低使用期,而不是记录标记为删除时。
答案 6 :(得分:0)
当用户创建,修改或删除事件时,请创建新的transaction
对象。在事务中存储有关事件更改的所有内容,并将其添加到表中,并引用该事件。这样,您就可以获得用户所做的所有事情的审核日志。这增加了最小的复杂性,但也允许扩展。您甚至可以在以后添加撤消功能,只需对数据模型进行最小的更改(如果有)。
因此,如果用户正在查看日志,您可以在没有与之关联的DELETE事务的情况下检索每个日志,但管理员可以查看所有内容。