长事务
长事务延长数据库资源的占用时间。本文负责 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 热点更新的边界
Redis 可承接计数更新,但会改变持久化与故障恢复方式。库存和余额应先明确权威状态及失败核对协议。相关边界见 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 才释放。