数据库软删除选型:新增isDeleted列还是新建关联表更优
两种软删除实现方案的性能对比与选型
先对齐业务前提:单表当前量级数千条,删除逻辑为按type整类批量删除,核心查询需求有两个:一是可查询已删除、未删除及全量数据,二是快速获取所有未删除的唯一type值。两种方案的性能差异可以从写、读两个维度直接对比:
写操作(删除动作)性能
- 方案1(主表新增
isDeleted字段):删除某类type时需要批量更新该类下的数千条记录,写开销和该type下的记录数正相关。以InnoDB引擎为例,批量更新数千行不仅会产生更多行锁占用、更大的binlog日志量,操作耗时也会随单类下的数据量上涨线性升高,等到单类数据涨到几万、几十万条时,一次删除操作可能耗时数秒甚至触发锁等待。 - 方案2(独立存储已删除
type映射表):删除动作仅需要在映射表插入1条对应type的记录,写开销是常量级,不管单type下挂几千还是几十万条记录,删除操作都是单次单行写入,耗时极短,锁粒度仅覆盖映射表的单行记录,完全不会阻塞主表的正常读写。
读操作性能
针对两个核心查询场景分别对比:
场景1:获取所有未删除的唯一type值
- 方案1:需要执行
SELECT DISTINCT type FROM 主表 WHERE isDeleted = false。如果没有为isDeleted和type建立联合索引,会触发全表扫描;即使建了联合索引,随着主表数据量持续增长,索引扫描的行数也会不断增加,查询性能会逐步下降。 - 方案2:映射表的行数等于已删除的
type总数,通常最多几十到上百行,体量极小。查询时既可以通过LEFT JOIN关联两张表过滤已删除type,也可以先查出所有已删除type的小集合再做内存计算,不管哪种方式开销都几乎可以忽略,查询速度远高于方案1。
场景2:查询全量/已删除/未删除的实体记录
- 方案1:单表查询即可,只需要通过
isDeleted字段过滤状态,语法简单,不需要关联表。但要注意isDeleted是区分度极低的字段(通常绝大多数数据都是未删除状态),单独给这个字段建索引几乎没有效果,必须和常用查询条件一起建联合索引才能避免慢查询。 - 方案2:查询未删除/已删除数据时需要关联映射表做过滤,因为映射表体量极小,数据库执行关联查询时会自动把它作为驱动表,关联计算的开销低到几乎感知不到,实际查询速度和单表查询没有明显差距;查询全量数据时直接查主表即可,完全不需要关联。
额外注意事项
- 方案1的隐性成本:除了删除操作开销大,后续新增同
type记录时必须严格给isDeleted赋默认值false,一旦逻辑漏写就会出现新插入数据被误标记为已删除的问题;后续所有业务查询都必须带上isDeleted的过滤条件,很容易因为漏写条件查出已删除数据,引发业务bug。 - 方案2的适配边界:这个方案仅适用于按整类
type做删除的场景,如果后续需要支持单条记录维度的软删除,这个方案就无法覆盖,需要回到行级标记的实现逻辑;另外要给映射表的type字段加唯一索引,避免同一个type被重复插入导致过滤逻辑异常。
最终选型建议
如果业务长期保持「按type整类删除」的逻辑,没有单条记录软删除的需求,方案2的读写性能全面优于方案1,且数据量越大优势越明显;如果后续确定会新增单条记录软删除的需求,再选择方案1,同时一定要提前建好(isDeleted, type)的联合索引,保障核心查询的性能。
内容的提问来源于stack exchange,提问作者Tushar Vardhan
相关产品推荐
相关产品推荐

