逻辑删除还是物理删除?数据库删除策略选型与工程实践指南
发布时间:2026/10/18 1:18:12 锦皓数字建站

我刚工作那会儿接过一个线上数据维护的任务。运营同事误操作把一张用户标签表里近三个月的记录全删了。那张表用的物理删除DELETE 语句一执行事务一提交数据直接消失。当时备份策略虽然做了每日全备但恢复时才发现要恢复到误删前那几分钟得靠 binlog 回放折腾了大半夜最后还丢了一小部分数据。从那以后我对删除这两个字变得格外敏感。后来再设计系统凡是核心业务数据我几乎默认走逻辑删除。但这并不代表物理删除就该被全盘否定。这两者之间的选择本质上是业务需求、数据规模、合规要求、恢复成本之间的权衡今天想把这个决策过程完整地拆开聊一聊。这篇文章不打算讲那种一定要用逻辑删除或者物理删除才是正道的极端观点而是从数据库设计者的实操视角把两种删除方式的底层机制、适用场景、隐藏代价和组合策略说清楚。不管你是刚入行的后端开发还是已经在维护线上系统的架构师只要你每天要跟 DELETE 和 UPDATE 打交道这篇文章应该能给你提供一套可以直接落地的选型思路。1. 删除这件事的底层差异DELETE、UPDATE 与数据落盘的真实命运1.1 物理删除的真删你以为删掉了其实只是标记不可见很多人对物理删除的理解停留在DELETE FROM 表 WHERE id ?以为执行完之后数据就彻底没了。从业务查询的视角看确实如此但从数据库存储引擎的视角看事情没那么简单。以 InnoDB 为例执行 DELETE 时记录并没有立刻从磁盘文件里物理抹掉。InnoDB 在数据页中给这条记录打上一个删除标记这个标记叫 delete-mark。记录仍然占据原来的槽位只是对普通查询不可见。真正把空间释放掉要等 purge 线程后续做清理。如果表上还有二级索引索引记录也会被标记删除。所以物理删除这个叫法其实是相对于业务逻辑层的逻辑删除而言的它把记录从 B 树的正常索引路径中摘除查询看不到它了但底层磁盘空间并不一定马上归还。这就带来两个直接后果。第一如果你频繁地小批量删除数据又没做 OPTIMIZE TABLE表文件的大小可能只增不减因为那些被标记删除的页可能被后续插入复用也可能长时间留存。第二如果删除操作发生在事务里并且事务还没提交其他事务在隔离级别下是看不到这些删除的一旦回滚删除标记会撤销。所以物理删除的本质是从当前数据版本中移除记录让它不再参与任何一致性读、范围查询、唯一约束校验。它删的是逻辑可见性而非磁盘上的物理字节。这一点和逻辑删除放在一起看会发现二者在存储层的处理路径完全不同。1.2 逻辑删除的假删用一列状态位改写数据的生命周期逻辑删除就是在表里增加一个标记字段常见的命名有is_deleted、deleted_at、status。删除操作变成 UPDATE 语句比如UPDATE user_tag SET deleted_at NOW() WHERE id 123;执行完之后这条记录在数据库里依然存在只是业务查询里统一加一个deleted_at IS NULL的条件把它过滤掉。这种方案的存储开销极小没有索引页的删除标记和 purge 清理过程数据还静静地躺在原表里。与之对应的是所有与这张表关联的外键、统计查询、后台任务都要理解被逻辑删除的记录本质上还活着只是业务上不展示这个潜规则。逻辑删除的表通常还会保留一个非空的删除时间字段。相比布尔位时间戳能提供更多信息什么时候删的、删除操作是否可追溯、数据还能否按时间窗口恢复。有些团队会再加一个deleted_by字段记录操作人这在审计场景里非常有用。这里要特别强调一点逻辑删除的下游影响范围极广。一旦你决定对某张表启用逻辑删除就意味着这张表上的每一个 SELECT 都要带上过滤条件每一个 JOIN 都要考虑是否要排除已删除记录每一个唯一索引都要重新设计。它不是一个简单的字段添加而是一整套查询规范的改造。1.3 两者的时间线对比删除后 1 秒、1 天后、1 年后的数据状态为了更直观地理解差异我列了一张对比表假设业务是电商订单表对比维度物理删除逻辑删除删除后 1 秒业务查询查不到需加条件才查不到删除后 1 小时能否恢复依赖 binlog 或备份有风险直接 UPDATE 还原 deleted_at 即可删除后 30 天存储占用视 purge 情况可能已释放记录仍在占用存储删除后 1 年审计查询若备份过期彻底丢失随时可查删除痕迹对唯一约束的影响无残留可插入同键数据残留记录导致唯一键冲突对统计报表的影响不参与计数的自动排除忘了过滤就统计错误从这张表能看出来逻辑删除最大的优势在后悔药物理删除最大的优势在不残留。删得干净还是留得下来本身就是两种极端诉求没有绝对的好坏只有哪个更贴合你的业务。2. 为什么我默认给核心业务表加逻辑删除审计、恢复与关联数据的三重考量2.1 审计需求谁在什么时间删了什么必须能回答很多业务系统尤其是后台管理系统、订单系统、客服工单系统都会面临一个灵魂拷问这条数据是谁删的什么时候删的删除前它的值是什么如果表里只有 DELETE 操作而你没有额外的审计日志表这个问题几乎无法回答。即使开了 binlog要从 binlog 里解析出具体某个时间点的变更那也不是普通业务查询能做到的事。物理删除意味着原始记录直接消失后续任何形式的对账、追溯、纠纷处理都会变得非常被动。我见过一个真实的案例某后台管理系统的操作员误删了客户合同记录合同没有独立备份导致月底对账时发现数据对不上。因为没有删除时间、没有删除人、没有原始快照最后只能靠翻中间件日志慢慢拼效率极低。如果那张表用的是逻辑删除一条 UPDATE 就能让合同复活所有审计信息都清清楚楚。所以我的原则是只要数据的生命周期里存在用户可见的状态或者数据可能被外部监管、财务对账、客户投诉回溯就必须能回答删除前是什么样。逻辑删除是实现这个能力的最简单手段没有之一。2.2 恢复成本逻辑删除的反悔是一条 UPDATE物理删除则是一段灾难恢复流程误删数据这件事几乎每个团队都会遇到。区别只在于遇到之后你是花 5 分钟恢复还是花 5 个小时恢复。逻辑删除的恢复逻辑非常简单UPDATE user_tag SET deleted_at NULL WHERE id 123;只要记录还在一条 UPDATE 就能把数据拉回正常状态。配合事务和合理的索引整个过程对线上无感知。而物理删除的恢复通常要经历找到备份文件、确认备份时间点、搭临时实例、回放 binlog 到目标时间、导出数据、再导入线上库。中间任何一步出错都可能导致数据不完整或者二次污染。这里我想说一个更隐蔽的风险物理删除后的恢复过程本身在时间轴上就是有损的。如果误删之后又有其他业务对这个表做了写入那么单纯恢复删除记录很可能会覆盖掉后续的合法数据。逻辑删除则没有这个冲突因为记录一直在原位你只需要翻转一个字段的值其他列都是删除时的快照。这也是我在设计用户标签、订单快照、配置版本这类数据时坚定选择逻辑删除的原因。数据一旦被删除几乎不可能通过外部手段重新获取与其指望备份机制不如让数据本身具备复活能力。2.3 关联数据的完整性子表记录还在父表却查无此人这种幽灵数据最恶心逻辑删除还有一层容易被忽略的作用维持主外键关系在查询上的连续性。举个例子用户表里有一条用户记录删除后关联的订单表里还有几百条订单引用这个用户 ID。如果用物理删除用户 ID 在用户表里不再存在订单查询要显示用户昵称就会得到 NULL 或者需要 LEFT JOIN 后做空值处理。虽然外键约束不会报错但业务逻辑上就出现了订单属于一个不存在的人这种幽灵数据。逻辑删除让用户记录始终存在只是标记为删除。订单查询 JOIN 用户表时依然能取到历史快照。特别是对于一些需要保留订单历史、合同历史、操作日志的场景这种连续性价值极高。你可以把逻辑删除理解成一个软锚点它不阻断数据的关联关系只是在展示层或者统计层做一个过滤。我习惯在涉及历史快照型数据时把逻辑删除和快照字段冗余配合使用。比如订单表本身冗余了下单时的用户昵称、用户等级这样就算用户后续被物理删除订单展示也不受影响。但在没有冗余的情况下逻辑删除几乎是唯一的选择。3. 物理删除的真需求数据合规、空间膨胀与查询性能的硬约束3.1 合规要求你不能留着不该留的数据哪怕它被打上删除标记逻辑删除不是万能的最典型的一个反例就是数据合规。在很多业务场景下用户有权要求平台删除自己的个人信息。如果只是把deleted_at置为当前时间数据本身还完整保存在磁盘上从合规审查的视角看这依然属于未删除。因为只要数据库被拖库、备份被泄露这些已标记删除的数据依然能被还原出来造成隐私泄露风险。GDPR 的被遗忘权、个人隐私保护法的相关规定都对数据删除提出了明确要求。这时候物理删除是唯一的选择因为逻辑删除本质上只是在业务层面隐藏数据存储层面数据依然存在。我记得之前参与过一个用户注销功能的改造最初方案是把用户表逻辑删除结果法务给了个非常明确的答复数据必须不可逆地删除包括备份副本。于是我们不得不设计一套严格的物理删除流程同时对相关关联表做级联处理或匿名化处理。在这种场景下正确的做法是区分数据的业务删除和合规删除两条链路。业务删除可以走逻辑删除避免误操作合规删除则要触发真正的物理删除或者匿名化处理。具体而言可以为每张表增加一个retention_date或者purge_date字段由定时任务扫描并执行物理清理。3.2 表膨胀逻辑删除的数据留痕会让 InnoDB 表越来越大直到你无法忍受逻辑删除的表数据只增不减。对某些表来说这没问题比如操作日志、订单流水。但对另一些表来说这是灾难。典型例子是购物车、临时会话、消息通知表。这些表的特点是数据具有极强的时效性一旦过期就几乎不会被查询。如果所有删除操作都变成 UPDATE 置标记那么表行数会持续增长二级索引变得臃肿查询性能逐渐劣化。也许你会说后台定时任务可以把早于 N 天的已删除数据物理清理掉这样既保留短期恢复能力又控制长期膨胀。这个思路没问题但不能忽略一个点清理任务本身可能成为数据库的新压力源。大批量 DELETE 会在 InnoDB 里产生大量的 undo log、redo log 和 purge 操作如果清理时间安排不当很可能拖垮主库性能。所以我的建议是对于生命周期短、数据量增长快、恢复价值低的表干脆别用逻辑删除。购物车里的过期商品删除就删了用户不会来找你恢复消息推送记录删了把未读数清零没人会关心三天前那条推送的具体内容。这种表用物理删除配合同一张表的按天分区删除某个分区就是一条ALTER TABLE ... DROP PARTITION速度极快空间立刻释放性能影响也极小。3.3 唯一约束的冲突逻辑删除的信号被唯一索引拦截这是逻辑删除最让人头疼的工程问题。假设用户表对user_name做了唯一约束。用户张三注销了你执行逻辑删除deleted_at被置上但user_name仍然是张三。过了一段时间新用户尝试注册张三数据库立刻报Duplicate entry因为唯一索引不允许出现两个值为张三的行。解决思路有几种但没有一种是完美的。最常见的是把唯一索引改成复合唯一索引例如UNIQUE KEY uk_user_name_deleted (user_name, deleted_at)。在 MySQL 里如果多列唯一索引包含允许为 NULL 的列那么 NULL 值是可以重复出现的。这样设计后多个未删除用户的名字不能重复但已删除用户和未删除用户之间不会冲突。这个方案有一个隐患如果一个用户被删除多次即在deleted_at列上不断更新删除时间那么最终唯一索引检查的是当前删除时间不会与之前冲突但你无法防止同一个user_name的两个已删除记录拥有相同的删除时间。更稳妥的做法是在逻辑删除时把用户名改成一个带时间戳的唯一值比如zhangsan_del_20250101120000但这种改造会让业务代码变得非常丑。遇到这种场景我一般会重新审视业务需求这个唯一性到底约束的是当前活跃数据还是约束全表历史数据如果是前者那么用user_name和deleted_at的复合唯一索引就够用如果是后者你就该考虑把已删除数据挪到归档表让主表只保留未删除数据从而让唯一索引回归简单。4. 逻辑删除的隐蔽陷阱查询穿透、统计口径与定时清理的连锁反应4.1 查询条件漏写 deleted_at 导致的穿越问题逻辑删除最大的工程债不是删除本身而是每一个读取数据的地方都要记得过滤。团队里只要有一个新人不知道这个约定写了一个不带过滤条件的查询已删除数据就复活了。更麻烦的是这种 bug 往往不会在测试环境暴露——测试环境里没有那么多的历史数据线上跑一段发现统计数字突然变多才开始排查是不是查询漏了状态条件。为了降低这种风险我强烈建议不要只依赖程序员自觉而是用技术手段把过滤条件内置。常见做法是引入一个数据访问层在 ORM 层统一处理逻辑删除条件。比如在 MyBatis Plus 里配置TableLogic在 Spring Data JPA 里自定义Where注解。这样业务代码里写查询时框架会自动拼接deleted_at IS NULL从源头上避免漏过滤。如果项目没用 ORM至少要在 Service 层封装一个统一的数据访问入口不允许业务 Controller 直接操作 Mapper。这本质上是一个团队规范问题但技术手段可以帮助规范落地。我在团队里还定了一条铁律所有逻辑删除表上的查询必须有 DBA 代码审查的环节重点看条件里是否带上了过滤字段。4.2 统计报表的分母争议已删除数据该不该计入总数逻辑删除还会引发一个业务层面的争端统计数据怎么算。比如后台用户总数如果你直接用SELECT COUNT(*) FROM user结果包含了所有已删除用户明显不对。但如果只是加上WHERE deleted_at IS NULL又可能无法回答历史累计注册用户数这类问题。我的处理方式是在报表查询里明确区分存量活跃规模和历史累计规模两种口径。前者必须过滤已删除数据后者则会把已删除数据也计入。如果要同时展示这两个指标最好在报表页面加上口径标注避免产品、运营、管理层之间产生理解偏差。数据库表的设计上也可以把是否已删除作为一个维度字段放到统计表里用物化视图或者定时汇总的方式定期刷新。如果你直接在线上业务表上跑大范围统计而且表里积累了大量的已删除数据那么 COUNT 扫描的性能会越来越差。这时候就该考虑把已删除数据迁出主表了否则逻辑删除会让统计数据越算越慢。我遇到过一张超过千万行的消息表因为逻辑删除导致COUNT(*)查询 3 秒都出不来后来把 90 天前的已删除数据全部迁到归档库主表查询回到了毫秒级。4.3 定时清理的时间差策略保留多久的删除标记最合适逻辑删除不代表永远不清理。一个成熟的系统必然会有删除标记的保留期限。这个保留期限应该怎么定我的经验是参考业务恢复窗口和数据合规要求的最小值。如果公司内部规定线上配置数据需要保留 180 天以便回溯那么deleted_at标记 180 天内的数据就不能清如果没有任何硬性要求我一般保留 30 天超过 30 天的已删除数据交给归档任务处理。清理任务要特别注意批大小和执行时间。我的习惯是每批只删除 5000 条每批之间停顿几百毫秒避免产生过大的事务和长时间锁占用。执行时间放在业务低峰期比如凌晨三点。清理前还要做一次 COUNT 评估如果某次清理量特别大应当先告警。清理过程本身也应该记录日志包含清理范围、删除条数、执行时间。这样如果清理后有人反馈数据找不到了还能从清理日志里确认这组数据是被归档还是被永久清除了。4.4 归档表的折中方案把已删除与活跃物理隔离兼顾性能与恢复如果你既想享受逻辑删除的恢复便利又不想让已删除数据拖垮主表那归档表是个非常实用的中间方案。主表只保存deleted_at IS NULL的活跃数据已删除数据在保留期过后被移动到结构一致的归档表里。归档表可以放在同一个库也可以放到单独的归档库。移动到归档表的操作可以用 INSERT INTO archive_table SELECT ... FROM main_table WHERE deleted_at cutoff_date然后 DELETE 原表。这个操作要在同一个事务里分步执行并且必须加足够的索引否则全表扫描会锁住大量行。从查询路由角度如果业务偶尔需要查询历史已删除数据可以通过一个视图或者统一入口去联合查询主表和归档表。从恢复角度归档表保留了完整的删除标记和历史快照必要时候把记录搬回主表也是一条 UPDATE。这个方案比单纯逻辑删除多一层物理隔离但多出来的成本是归档任务本身需要开发、监控和维护。表数量一多归档逻辑就会变得复杂所以我会在任务里加一个统一的状态机记录每张表的上一次归档水位。5. 选型决策矩阵我把删除策略拆成六个问题直接判断该走哪条路5.1 问题清单从业务价值、误删风险、数据规模、合规压力四个维度做判断题与其含糊地说看情况不如用一套量化的问题来辅助决策。我每次接到新表设计都会顺手过一遍这套判断题如果这条数据被误删了业务会因为无法恢复而遭受重大损失吗公司内部或外部有审计需求需要知道谁在什么时候删了什么吗这条数据删除之后还需要被历史查询、报表统计或对账流程引用吗这张表的数据增长速度是否很高如果保留已删除数据半年后表体积是否会让查询难以忍受是否受到个人信息保护相关法规约束要求删除行为必须不可逆该数据是否只是临时性状态数据删除后没有任何回味价值如果第 1、2、3 题的答案都是是无脑选逻辑删除并且做好查询过滤规范。如果第 1、2、3 题都是否而第 4 题答案是是那么物理删除更合适。如果第 5 题答案是是那么必须物理删除连逻辑删除作为中间态的时间都不能太长。这套判断题不是银弹但它能帮你把直觉转变成可讨论的依据。我在开方案评审会时会把这张列表贴出来让团队成员逐条回答往往不到十分钟就能达成一致。5.2 四类典型数据的选型参照表为了更直观我把实际项目里最常见的几类数据整理成一张表方便你直接对照数据类型推荐策略核心理由用户账户、合同、订单主表逻辑删除 长期保留审计、对账、法律纠纷需要完整历史购物车、临时会话、过期通知物理删除 分区表数据时效性短保留会增加无谓成本操作日志、访问日志物理删除 / 归档 冷存储数据量大超过留存期后无业务价值用户画像、标签、配置版本逻辑删除 定期归档误操作影响面大需要快速恢复能力用户个人隐私敏感信息合规物理删除 匿名化法规强制要求不可逆删除社交动态、评论、消息逻辑删除为主用户侧投诉、后台审核需查看删除前后上下文这几类不能覆盖所有场景但已经能帮大部分业务系统找到适合自己的方案。核心思路是根据数据恢复的价值、数据增长的速度、监管合规的要求三个因素决定删除标记的存留时间以及是否需要物理隔离。5.3 什么时候该把逻辑删除升级为真删除 回收站的双轨制在一次事故复盘之后我给系统设计过一种双轨制删除方案。主表始终采用逻辑删除保证误删可恢复同时后台提供一个回收站管理页面列出已删除记录操作人员可以一键恢复或者确认永久删除。确认永久删除时执行物理删除并在单独的purge_audit_log表里记录一条审计日志。这种双轨制的好处是日常删除操作不会直接造成不可逆后果操作人员有两次确认机会一旦数据进入永久删除阶段就代表业务已经认可这个删除意图后续审计也能追踪到具体的人和时间。需要提醒的是双轨制要求回收站的列表本身不能太大否则查询和恢复都会变得笨重。解决方案是回收站列表只保留最近 30 天的已删除记录更早的数据自动归档。如果你所在团队正在处理一个高价值核心表强烈建议引入这种回收站思路。成本不高收益却非常明显。我见过很多系统因为缺少这层缓冲一次误操作就造成几小时的线上故障事后复盘的情绪成本远大于最开始的一点点开发成本。6. 实操复盘一次数据误删事故如何验证了删除策略的工程价值6.1 事故始末运营在后台勾选了批量删除执行的是物理 DELETE事故发生的背景是运营后台需要一个批量清理临时活动配置的功能。最初落地这个功能时开发图省事直接在后端写了一个DELETE FROM activity_config WHERE id IN (...)的接口。后台页面也只是一个简单的表格勾选没有二次确认。运营同事在筛选条件选错的情况下全选并执行了删除一次性删掉了 248 条活动配置其中包括一个大促活动的主配置和关联规则。活动配置没有独立的配置版本管理依赖数据库的当前值。当运营发现活动页面异常时距离删除已经过去了 20 分钟。这 20 分钟里其他活动有新增和修改导致无法通过简单地回滚整个库来恢复数据。借助备份回放也只能恢复到某个备份时间点备份之外的新改动会丢失。从表面看事故原因是误操作和缺少二次确认但根因其实是删除策略没有分层。如果活动配置表用逻辑删除这个问题只需要一条UPDATE activity_config SET deleted_at NULL WHERE id IN (...)就能解决。当时的开发没有做这个选择因为他觉得活动配置不是核心数据删错了重建就行。事实说明所有被人在后台勾选的数据都可能成为核心数据。6.2 恢复方案的推演如果是逻辑删除五分钟内修复物理删除耗了一整晚事故当天第一反应是看看有没有 binlog 可以定位误删记录。确认 binlog 开启之后团队开始解析 binlog目标是把 248 条记录从 binlog 事件里解析出来。但问题是 binlog 里的记录是经过编码的必须用专门的工具按表结构解析再加上删除前后有其他 DML 语句混在一起筛选过程非常痛苦。最终花了大半夜终于提取到完整的删除前数据再通过 INSERT 重新灌回线上库。因为恢复过程中与新增数据存在潜在冲突还临时禁用了部分活动配置对线上其他业务造成影响。如果当初有逻辑删除场景会完全不同。运营误删后只需要由 DBA 执行一条 SQL 还原deleted_at字段再检查一下这些活动配置是否互相依赖、是否需要级联恢复整个操作不会超过五分钟。而且因为记录从未真正消失所有关联的快照数据都还在原地不会出现恢复后部分字段丢失的情况。这次事故对我的冲击很大。它让我意识到很多工程决策看上去是在选一个 SQL 语句实际上是在选一个团队面对故障时的应急余量。逻辑删除本质上是一种富余设计它牺牲了一部分存储和查询性能换来了极高的容错能力。对于配置类、关键业务类数据来说这笔交易非常划算。6.3 事故后的团队规范每条删除操作必须回答能不能恢复事故之后我在团队内部推行了一条非常简单的规范每一个删除接口在设计时必须回答三个问题——这条数据被删除后还能不能恢复如果不能恢复是否有替代的备份手段如果恢复需要超过 30 分钟是否要在页面上增加二次确认和操作人留痕配套的检查项还包含DELETE 语句是否带 WHERE事务是否显式控制是否有行数限制是否需要软删除标记所有删除操作必须走统一的 Service 方法不允许 Controller 里直接调用 Mapper 的 delete 方法。这一条看着简单实际上能挡住大部分低级错误。还加了一个删除防护机制所有高危表的 delete 操作在数据库层面设置触发器或者拦截器。比如禁止在非维护窗口对用户表执行不带deleted_at条件的 UPDATE 或 DELETE禁止对核心订单表执行物理 DELETE除非经过 DBA 授权并临时关闭触发器。这套规范运行几个月后团队里再没有发生过不可恢复的删除事故。倒不是说所有人都变细心了而是系统把容错点前移了即使有人粗心数据库层面还有一道防线。7. 一些藏在细节里的经验索引设计、备份策略与删除操作的性能边界7.1 逻辑删除字段的正确索引姿势很多人给deleted_at字段加了一个单独的索引但实际上这个索引的有效性很有限。因为绝大多数查询都带deleted_at IS NULL这个条件而 NULL 值在索引中的分布非常不均匀大量 NULL少量非空查询优化器很可能觉得反正都是 NULL 居多不如全表扫。所以更合理的做法是把deleted_at和一个高频查询字段组合成复合索引。举个例子用户表常用的查询是按邮箱查活跃用户索引应该设计成(email, deleted_at)这样才能同时满足查询条件和过滤条件。当然如果一张表绝大多数行都是活跃数据那么deleted_at IS NULL的过滤效果很差此时更需要考虑的是已删除数据归档而不是纠结单列索引该不该建。检查查询计划是必须的。我见过一个团队把is_deleted字段纳入索引后慢查询不仅没变快反而因为索引体积变大导致刷新成本上升。索引不是越多越好逻辑删除字段的索引设计尤其要谨慎。7.2 物理删除和 binlog 安全屋留给自己的最后一条退路即使决定用物理删除我也强烈建议开启 binlog并至少保留 7 天以上。binlog 就是数据库的安全屋在极端情况下可以用于 point-in-time 恢复。如果你用的是 MySQL 8.0binlog 默认开启但你要注意 binlog 的过期时间设置。我之前见不少团队把binlog_expire_logs_seconds设成 86400一天等于只留一天的安全窗口误删事故发生后第二天才发现binlog 已经清掉了。一个折中的做法是每天做一次全量备份保留 7 天 binlog这样就能把恢复窗口拉到 7 天。如果存储预算紧张可以只对核心库开启这个策略边缘系统的表物理删除时额外导出一份 CSV 到对象存储。要记住成本可以控制但完全没有任何恢复手段才是最贵的状态。7.3 大表删除的性能边界凌晨批量清理也需限流与分批最后说一说大批量物理清理的性能问题。很多人以为凌晨三点库的压力小就可以随意跑DELETE FROM big_table WHERE deleted_flag 1。实际不是这样。删除操作会拿行锁每次删除都记录 binlogInnoDB 还要维护二级索引整个操作产生的 IO 压力比同数量的 UPDATE 更大。一条不带 LIMIT 的 DELETE 一旦执行可能执行几十分钟期间主从延迟升高业务查询被阻塞。我习惯的写法是这样DELETE FROM activity_config WHERE deleted_flag 1 AND id IN ( SELECT id FROM activity_config WHERE deleted_flag 1 ORDER BY id LIMIT 1000 );这个方案每次删除固定数量的记录循环执行到没有剩余为止。在事务里每批删除都是一个独立的小事务锁范围小失败后重试也方便。如果表非常大建议先确认主键范围用主键分段循环的方式删除千万不要在一个事务里试图删除百万行。如果你用的是 TiDB 或者 OceanBase 这类分布式数据库删除逻辑的复杂度会更不一样但小批量、多循环的思路依然通用。删除不是越快越好而是越可控越好。8. 写在最后的个人选择基准删除策略不是一道非黑即白的题而是一组阶梯经过这些年踩坑、复盘、改造我对逻辑删还是物理删有了一个比较稳定的判断框架。我不会再对团队喊一律逻辑删除或者一律物理删除这类口号而是要求设计者对着具体的表和具体的业务场景做判断。所谓好方案就是在数据能恢复、查询不膨胀、合规不违规这三者之间找到一个可以被团队长期维护的平衡点。从操作频率上看90% 的删除操作都在业务后台发生操作者是内部人员存在误操作概率因此这些表我都建议保留逻辑删除作为默认。真正需要物理删除的通常是数据量巨大但价值密度低的流水表以及受合规要求约束的隐私数据。对于流水表我会用分区配合 DROP PARTITION 实现秒级清理对于违规表我会用任务触发不可逆清理并留审计日志。如果你还在犹豫一张表该怎么选我给你一条实用建议在表结构设计阶段就把deleted_at、deleted_by加上哪怕你觉得自己以后用不到。加上一个可空字段的成本极低后续不想要了可以直接忽略但如果你因为没有这个字段而在某次误删后焦头烂额那个代价会让你后悔当时少写了一行。同样任何 DELETE 接口上线前都先反问自己一句这段数据没了我能睡个好觉吗如果答案是否定的就乖乖改 UPDATE。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。