警示:软删除引发泼天大祸!

原文 The Day Soft Deletes Caused Chaos.

在我作为软件工程师的生涯中,最大的失误莫过于 5 年前合并了一个表面看起来毫无问题的 Pull Request。

简而言之:在生产级系统中,我们不应该使用软删除 ------ ---这是一个我通过一次严重的失误痛苦学到的教训。那次失误导致同一场音乐会的座位能被无限次地售卖给不同的买家。

所谓软删除,是一种在不真正执行删除操作的情况下保留数据的简便方法,仅通过标记一个「DELETE」标志来实现。

你在表格中增加了一个新的列,这样在进行「删除」操作时,实际上是更新了这条数据的状态,并在进行数据查询时自动排除那些标记为已删除的数据。

尽管这种做法初看起来简单易行,但它隐藏着潜在的风险 ------ 如果不小心让一些本不应该被展示的数据泄露出去,后果可能非常严重。

重大事件警示

我在一家活动票务公司工作时,创建了一个与此类似的拉取请求:

在座位预订流程中,顾客可以在结账过程中暂时锁定座位 5 分钟。这个过程是通过一个后台任务实现的,它会在时间到后删除锁定记录,使得座位重新可被预订。我当时的任务是将这种基于「软删除」的锁定机制迁移到一个新的数据库集合中,这个集合专门用来存放被删除的记录。

问题就出在了一行代码上:

这一操作移除了模型上负责处理软删除逻辑的 Paranoia 库,即通过设置一个 deleted_at 字段为当前时间来标记记录为已删除。我当时没有意识到的是,这个库还自动地从 ORM 查询中过滤掉了所有标记为软删除的记录。

由于自动排除软删除记录的机制失效,且数据迁移尚未完成,后台任务开始错误地处理那些已经被标记为「已删除」的座位锁定记录 ------ 这导致一些已经成功售出的座位被错误释放,再次对外开放预订!

我永远也忘不了,当我意识到发生了什么事时,那种心沉如石、惶恐不安的感觉。

这导致了在如 Shawn Mendes 音乐会这样的事件中,同一座位被多次出售的情况。放大到多个座位、多个活动,影响简直糟透了。

诚然,软删除机制并非唯一的问题所在。对于这次改动,我本应采取更多的预防措施,比如分步骤进行。CI/CD 流程中的自动化测试本应该能够发现这个错误,但它却还是漏了。万幸的是,这个领域的监控做得很好,问题几乎立刻就被发现并解决了。但是,由此造成的影响和后果还是非常严重的,包括数百个需要退款的重复预订、取消的订单、发送给受影响顾客的道歉邮件,还有一份深夜编写的事故总结报告。

不要使用软删除!

即便在像 GDPR 这样的法规监管下,我们想要保留被删除数据的倾向是可以理解的。开发者可能出于合规、报告、分析的需要,或者仅仅是希望有一个后备方案 ------ 以防万一误删数据或需要排查已删除记录的错误。想象这样的场景:一个顾客不慎删除了一张至关重要的发票,或者一个社交媒体用户删去了一条违反规则的评论。在某个宽限期内保留这些被删除的数据看似有其价值。然而,软删除策略实际上带来的问题要比它解决的更多。

复杂性增加

软删除像病毒一样扩散,让数据查询变得异常复杂。虽然应用程序的 ORM 层通常会自动排除被标记为「已删除」的记录,这种看似方便的做法却可能在手动编写复杂 SQL 查询时造成重大疏漏。正如我所经历的那样,你可能会得到不精确的结果,甚至可能暴露敏感信息,或基于片面信息做出错误判断。诚然,创建数据库视图似乎是一种更安全的做法,但这仍然增加了不必要的复杂性和额外的负担。

墨菲定律:任何可能出错的事情都会出错

索引、唯一约束和外键关系也都需要考虑「删除」状态,这使得它们的创建和维护更加复杂。

为 active users 的 email 字段创建唯一索引

即便引入了部分索引,软删除还是可能引起表的膨胀,不利地影响表的大小和性能。在高流量的环境下,这个问题可能更加突出,可能需要进行性能调整或数据分区来保持效率。

数据完整性

通过软删除来处理应用层中的删除操作会失去数据库的一个优势,即数据库会尽力为您保持数据的有效性。

vbnet 复制代码
ERROR: delete on table "users" violates foreign key constraint "orders_user_id_fkey" on table "orders"
DETAIL: Key (id)=(456) is still referenced from table "orders".

数据库外键违规错误

自行执行参照完整性可能容易出错,并会增加大量的开发和维护开销。

软删除的替代方案

软删除的另一种替代方法是将删除的数据存档到历史表中。这样做仍然很简单,而且可以消除软删除带来的长期责任和维护负担。在删除之前,可以将删除的记录插入到一个单独的表中。

删除前归档数据的事务

如果不想在整个代码库中手动归档数据,最好的办法就是在数据库层建立审计跟踪。《PostgreSQL 数据更改跟踪终极指南》概述了 PostgreSQL 的不同策略。我还推荐大家查看我参与的一个名为 Bemi 的开源项目,该项目旨在通过插入数据库和应用程序(支持大量不同的 ORM,如 Bemi-rails )来简化这一过程,从而自动提供上下文数据变更记录。

底线

远离软删除。虽然它们看上去似乎是处理已删除数据的便捷方法,但实际上,它们就像一个即将爆炸的定时炸弹。这是我几年前吃过的苦头,绝对是你不愿意重蹈覆辙的教训。相比之下,使用历史记录或审计表是一个更为明智的选择。这种做法不仅更整洁、更安全,而且长远来看能够避免无数的麻烦。


💡 更多资讯,请关注 Bytebase 公号:Bytebase

相关推荐
桀桀桀桀桀桀30 分钟前
数据库中的用户管理和权限管理
数据库·mysql
superman超哥2 小时前
04 深入 Oracle 并发世界:MVCC、锁、闩锁、事务隔离与并发性能优化的探索
数据库·oracle·性能优化·dba
用户8007165452002 小时前
HTAP数据库国产化改造技术可行性方案分析
数据库
engchina2 小时前
Neo4j 和 Python 初学者指南:如何使用可选关系匹配优化 Cypher 查询
数据库·python·neo4j
engchina2 小时前
使用 Cypher 查询语言在 Neo4j 中查找最短路径
数据库·neo4j
尘浮生2 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
威哥爱编程3 小时前
SQL Server 数据太多如何优化
数据库·sql·sqlserver
小华同学ai3 小时前
AJ-Report:一款开源且非常强大的数据可视化大屏和报表工具
数据库·信息可视化·开源
Acrelhuang3 小时前
安科瑞5G基站直流叠光监控系统-安科瑞黄安南
大数据·数据库·数据仓库·物联网
十叶知秋4 小时前
【jmeter】jmeter的线程组功能的详细介绍
数据库·jmeter·性能测试