为了正确回答这个问题,您首先需要确定:在此系统/应用程序的上下文中,“删除”是什么意思?
要回答这个问题,您需要回答另一个问题:为什么删除记录?
有许多充分的理由说明用户可能需要删除数据的原因。通常,我发现(每张表)有一个原因可能导致需要删除。一些例子是:
- 回收磁盘空间;
- 根据保留/隐私权政策要求硬删除;
- 损坏/绝望的错误数据,比修复更容易删除和重新生成。
- 在大多数行被删除,例如日志表仅限于X个记录/天。
硬删除还有一些很差的理由(稍后会详细说明):
- 更正小错误。这通常强调了开发人员的懒惰和敌意的UI。
- 为了“避免”交易(例如,本不应该开票的发票)。
- 因为可以。
您为什么问,这真的有什么大不了的?好孩子DELETE
怎么了?
- 在任何与金钱紧密联系的系统中,硬删除都违反了各种会计期望,即使移至档案/墓碑表也是如此。解决此问题的正确方法是追溯事件。
- 存档表倾向于偏离实时模式。如果您忘记了一个新添加的列或级联,那么您将永久丢失该数据。
- 硬删除可能是非常昂贵的操作,尤其是对于级联。许多人没有意识到级联不止一个级别(或者在某些情况下,任何级联,取决于DBMS)将导致记录级操作,而不是设置操作。
- 重复,频繁的硬删除可加快索引碎片的过程。
所以,软删除更好,对吗?不,不是真的:
- 设置级联变得非常困难。您几乎总是以客户机显示为孤立行的结果结束。
- 您只能跟踪一个删除。如果该行被多次删除和取消删除怎么办?
- 尽管可以通过分区,视图和/或过滤后的索引在某种程度上减轻读取性能,但读取性能会受到影响。
- 如前所述,在某些情况/管辖范围内,它实际上可能是非法的。
事实是这两种方法都是错误的。删除是错误的。 如果您实际上是在问这个问题,则意味着您正在建模当前状态而不是事务。在数据库领域,这是一种不好的做法。
乌迪·达汉(Udi Dahan)在“ 不要删除-就是不要”中写道。有总是某种任务,交易,活动,或(我的首选术语)事件实际上代表了“删除”。这是确定的,如果你随后又希望将非规范化的表现“当前状态”表,但做到这一点后,你已经确定下来的交易模式,而不是之前。
在这种情况下,您有“用户”。用户本质上是客户。客户与您有业务关系。这种关系不会简单地消失在空中,因为他们取消了帐户。真正发生的是:
- 客户创建帐户
- 客户取消帐户
- 客户续订帐户
- 客户取消帐户
- ...
在每种情况下,都是同一位客户,并且可能是同一帐户(即,每个帐户续订都是一项新的服务协议)。那为什么要删除行呢?这很容易建模:
+-----------+ +-------------+ +-----------------+
| Account | --->* | Agreement | --->* | AgreementStatus |
+-----------+ +-------------+ +----------------+
| Id | | Id | | AgreementId |
| Name | | AccountId | | EffectiveDate |
| Email | | ... | | StatusCode |
+-----------+ +-------------+ +-----------------+
而已。这里的所有都是它的。您无需删除任何内容。上面是一个相当普通的设计,具有很高的灵活性,但是您可以稍微简化一下;您可能会决定不需要“协议”级别,而只需将“帐户”转到“ AccountStatus”表即可。
如果您的应用程序中经常需要获取有效协议/帐户的列表,那么这是一个(稍微)棘手的查询,但这就是视图的用途:
CREATE VIEW ActiveAgreements AS
SELECT agg.Id, agg.AccountId, acc.Name, acc.Email, s.EffectiveDate, ...
FROM AgreementStatus s
INNER JOIN Agreement agg
ON agg.Id = s.AgreementId
INNER JOIN Account acc
ON acc.Id = agg.AccountId
WHERE s.StatusCode = 'ACTIVE'
AND NOT EXISTS
(
SELECT 1
FROM AgreementStatus so
WHERE so.AgreementId = s.AgreementId
AND so.EffectiveDate > s.EffectiveDate
)
这样就完成了。现在,您拥有了软删除的所有优点,但没有缺点:
- 孤立记录是非发行记录,因为所有记录始终可见。您仅在必要时从其他视图中进行选择。
- “删除”通常是一种非常便宜的操作-只需在事件表中插入一行即可。
- 无论您多么糟糕,都永远不会失去任何历史。
- 如果需要(例如出于隐私原因),您仍然可以硬删除帐户,并且对删除将干净进行且不会干扰应用程序/数据库的任何其他部分的知识感到满意。
剩下要解决的唯一问题是性能问题。在许多情况下,由于聚集索引的AgreementStatus (AgreementId, EffectiveDate)
存在,实际上实际上是没有问题的-很少有I / O寻求进行。但是,如果有问题,可以使用触发器,索引视图/物化视图,应用程序级事件等方法来解决。
不过,不要太早担心性能-正确进行设计更为重要,在这种情况下,“正确”意味着以一种将数据库用作事务处理系统的方式使用数据库。