跳到主要内容
约 6 分钟4 张卡

长事务

长事务延长数据库资源的占用时间。本文负责 MySQL 8.0 InnoDB 的事务拆分与热点更新。

1. 面试核心​

先记住这几条,再读正文
  • 锁与版本保留。 长写事务持续持锁,长时间存活的 Read View 延缓相关 undo 清理。卡 · 事务持续时间与资源
  • 只读长事务。 RR 只读事务也能保留旧快照,延长历史版本的存活时间。卡 · 事务持续时间与资源
  • 批处理恢复。 数据变更与 checkpoint 在同一事务提交,可避免批处理进度先行。卡 · 批量任务拆分
  • 热点行竞争。 同一热点行的并发写入串行取得记录锁,条件 UPDATE 可减少往返。
  • 元数据锁。 事务访问表取得的元数据锁保留到事务结束,会阻塞冲突 DDL。卡 · 元数据锁与 DDL

2. 事务持续时间与资源​

应用事务从开启到提交或回滚之间保持活动。InnoDB 的事务状态取决于已执行的 SQL。BEGIN 只开启边界,普通读的快照时机见 MVCC。

事务期间会占用连接。执行更新后,还需持有记录锁和未提交版本。查询建立的 Read View 则要求保留可见历史。

SQL 之间的计算、远程调用和等待也算事务时间。因此,单条 SQL 很快,整个事务仍能持续很久。

3. 长事务与 purge​

活跃 Read View 需要的旧版本会继续保留。清理线程 purge 必须等待相关可见性需求结束。并发写入持续产生历史版本,undo 占用随之增长。更长的版本链也会增加旧版本查找成本。

长写事务持有记录锁,让冲突更新排队。等待请求持续占用连接,压力会扩散到连接池。

大事务产生较大的日志与复制工作单元。回滚也需撤销已完成的修改,因此会耗费时间。

4. 元数据锁与 DDL​

事务查询表时,也会取得元数据锁 MDL。锁保留到事务结束,因此只读事务也能阻塞 ALTER TABLE。

-- 会话 A
START TRANSACTION;
SELECT * FROM orders WHERE id = 1;
-- 保持事务打开。

-- 会话 B
SET SESSION lock_wait_timeout = 1;
ALTER TABLE orders ADD COLUMN audit_note VARCHAR(20);

在有 orders 表的练习库执行时,B 等待冲突的 MDL。等待超过一秒后,ALTER 报锁等待超时。A 执行 ROLLBACK 后释放该锁。

通过 performance_schema.metadata_locks 查看 MDL。它与 data_locks 的行锁证据分开观察。KILL QUERY 只终止当前语句,事务仍需提交或回滚。空闲事务要定位会话及应用的事务结束路径。

5. 事务中的远程调用​

下面的事务把网络延迟计入了持锁时间。

开启事务 → 更新订单 → 调用支付服务 → 写流水 → 提交

支付成功后,数据库仍可提交失败。本地回滚无法撤销已经完成的支付。

可用短事务记录待支付状态和待发送事件。任务处理器读取事件,再调用支付接口。结果通过幂等状态转换写回数据库。重复投递由稳定业务请求 ID 去重。

6. 批量任务拆分​

回填与清理按稳定游标拆成小事务。每批独立提交,并持久化 checkpoint。批量大小需按耗时、日志量和锁等待校准。

SELECT id FROM orders WHERE id > ? ORDER BY id LIMIT ?;

持续变化的数据集使用 OFFSET 时,位置会移动。稳定键游标可避免逐页增加的跳过成本。

本批更新和 checkpoint 应在同一数据库事务提交。先提交进度再提交数据,会在失败时漏处理。恢复后重放还需幂等条件。

任务启动时应约定数据截止范围。按递增主键回填时,可记录本轮最大主键。高于上界的新增记录留给后续轮次。已分配但晚提交的较小主键,仍需补扫处理。

7. 热点行​

库存、余额和全局计数器容易集中写入同一行。每个事务依次取得记录锁,因此写入会串行执行。减少从加锁到提交的时间,才能缩短排队。

UPDATE inventory SET available = available - 1
WHERE sku_id = ? AND available > 0;

影响 1 行表示扣减成功,影响 0 行表示条件未满足。单条语句完成条件检查与扣减,减少客户端往返。同一行仍由记录锁协调并发。

8. 热点拆分​

热点拆分需要保持业务不变量。

  • 分片计数把可合并的统计量写入多个子行。
  • 按商品、账户或租户分区,让不同键独立处理。
  • 串行消费者控制同一键的排队长度与执行顺序。
  • 额度预分配将总资源分给多个执行单元。
  • 低冲突更新可用版本号比较,并限制重试次数。

分片计数需要聚合读取,额度预分配需要回收协议。库存和余额场景还需明确超额与故障恢复边界。

9. 监控​

诊断应同时观察事务寿命和资源积压。

  • 用 information_schema.innodb_trx 查看事务起始时间。
  • 用 data_lock_waits 还原当前锁等待链。
  • 观察 undo 空间、purge 进度和 history list length。
  • 观察连接池 active、pending 和获取超时。
  • 按业务键统计冲突,记录批任务耗时与进度。

history list length 统计待清理的历史 undo 日志数量。它与修改行数、字节数存在不同的计量单位。

发现长事务后,定位完整调用链和修改量。终止大事务会触发回滚,连接解除阻塞需等待清理完成。

10. 常见问题​

10.1 只读事务的资源影响​

RR 只读事务建立的旧快照会延缓相关版本清理。普通一致性读无需取得记录锁,仍会占用连接。事务访问表的元数据锁还会影响 DDL。

10.2 Redis 热点更新的边界​

11 · 面试题卡片​

11.1 如何治理长事务和热点行更新​

出现公司:腾讯、招银科技

考察重点

  • 锁、连接、undo 历史与复制成本。
  • 事务内远程调用和大批量工作的拆分。
  • 条件更新、分片、串行化与业务不变量。

相关内容:事务持续时间与资源至监控。

参考回答

缩短事务可减少连接与锁占用,热点拆分可降低单行竞争。

先对照事务年龄、锁等待链和 undo 积压。再移出远程调用,将批任务拆成可恢复的小事务。

热点行先使用条件 UPDATE 减少往返。达到单行上限后,按业务约束选择分片或排队。额度、余额等状态还需设计故障恢复与核对。

11.2 没有更新数据的长事务为什么也可能拖慢数据库?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点

  • RR 与 RC 快照寿命
  • 历史版本与 purge

相关内容:事务持续时间与资源、长事务与 purge。

参考回答

RR 只读事务可维持旧 Read View,延缓相关 undo 清理。

RC 普通读按语句建立快照,单次长查询仍会保留旧版本。实际影响需看查询与快照寿命。只读事务还会占用连接和表的元数据锁。

11.3 分批回填时数据提交成功但进度未保存,如何保证恢复正确?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点

  • 批次原子性
  • 稳定游标与幂等

相关内容:批量任务拆分。

参考回答

同库的数据修改和 checkpoint 应在同一事务提交。

旧实现若已分开提交,恢复时从持久化进度重放。重放使用幂等更新,避免再次产生副作用。扫描还需稳定键及明确的截止范围。跨系统副作用另用可恢复事件和幂等键协调。

11.4 只执行 SELECT 的长事务为什么能阻塞 ALTER TABLE?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:元数据锁寿命、诊断表与事务结束。

相关内容:元数据锁与 DDL。

参考回答

SELECT 会取得表的元数据锁,并保留到事务结束。

ALTER TABLE 需要冲突的锁,因此等待。可用 performance_schema.metadata_locks 查看状态。该等待由 lock_wait_timeout 控制。结束原事务后,相关 MDL 才释放。

12. 参考资料​