长事务、热点更新与边界调整
长事务会延长锁和历史版本的持有时间,热点更新则让大量请求竞争同一小组记录。两者叠加时,连接池、undo、复制延迟和接口尾延迟都可能同时恶化。
1. 事务持续时间决定资源占用时间
事务从第一次需要一致性或修改数据开始,到提交或回滚为止。期间可能持有:
- 已取得的记录锁和区间锁。
- 数据库连接。
- 未提交修改对应的 undo 与 redo 状态。
- Read View 对旧版本的可见需求。
- 应用线程和上游请求预算。
一条 SQL 很快,不代表事务很短。SQL 之间的业务计算、网络调用、人工等待或错误的自动重试都可能让事务长时间保持打开。
2. 长事务会阻塞 purge 和并发写入
活跃快照仍可能读取旧版本时,InnoDB 不能清理相关 undo 历史。更新持续发生会让历史版本积累,增加存储和一致性读沿版本链查找的成本。
写事务持有的锁还会让其他更新排队。等待请求占用连接后,应用连接池可能耗尽,原本只影响一行的阻塞逐步扩散到整个服务。
大事务提交还会形成较大的日志和复制工作单元,回滚也需要按修改量执行,失败恢复时间不可忽略。
3. 事务中不要等待远程系统
开启事务 → 更新订单 → 调用支付服务 → 写流水 → 提交
支付调用的网络延迟会直接变成数据库持锁时间。远程结果不受本地数据库事务控制,即使数据库随后回滚,支付也可能已经成功。
更清晰的设计是先用短事务记录可恢复的状态和待发送事件,再由 Outbox 或任务处理器调用外部系统。回调或结果通知通过幂等状态转换推进下一步,而不是让数据库事务跨越网络。
4. 大批量工作要切成可恢复的小事务
回填、迁移和清理任务应按稳定游标分批处理,每批独立提交,并保存进度。批次大小要根据单批耗时、日志量、锁影响和失败重做成本校准。
使用 LIMIT ... OFFSET 扫描持续变化的大表,可能越来越慢并产生遗漏或重复。更适合使用主键或稳定业务键作为 seek 游标,例如 WHERE id > ? ORDER BY id LIMIT ?。
分批不等于忽略整体正确性。任务要明确可重复执行规则、完成标记和中途失败后的恢复方式。
5. 热点行把并发请求串行化
库存、余额、全局计数器和单一任务状态经常集中更新一行。即使每次更新只用几毫秒,所有请求仍要依次取得同一记录锁,吞吐上限由临界区耗时决定。
先确认业务不变量是否可以用单条条件更新表达:
UPDATE inventory
SET available = available - 1
WHERE sku_id = ? AND available > 0;
受影响行数可以判断扣减是否成功,避免“先查询、再计算、再更新”的竞争窗口。它减少往返与持锁时间,但同一行的写入依然串行。
6. 根据一致性要求拆分热点
当单行已达到瓶颈,可以考虑:
- 分片计数:把可合并的统计值写入多个分片,读取时聚合。
- 按业务键分区:让不同商品、账户或租户由不同执行单元处理。
- 串行消费者:把同一键的命令路由到同一分区,显式控制顺序。
- 预分配额度:把总资源拆给多个节点,再异步汇总剩余量。
- 乐观条件更新:低冲突时使用版本号比较并有限重试。
这些方案会改变实时可见性、故障恢复或资源超卖边界。选择前要写清必须保持的业务不变量,而不是只追求数据库 TPS。
7. 监控事务年龄与等待链
生产环境应观察:
- 活跃事务的最大和分位持续时间。
- history list、undo 空间和 purge 进度。
- 行锁等待时间、等待事务数和死锁率。
- 连接池 active、pending 与获取超时。
- 单一业务键或索引范围的冲突分布。
- 批任务单批耗时、进度和失败重试。
发现长事务后,先定位会话和完整调用链,再决定终止、等待或修复。贸然杀掉修改量很大的事务也会触发长时间回滚,需要评估业务影响。
8. 常见问题
8.1 只读事务是否不会造成影响
长时间只读事务仍可能持有旧快照,阻止相关历史版本被 purge。它通常不阻塞普通写入取得记录锁,但会增加 undo 保留与旧版本读取成本。
8.2 把热点更新放进 Redis 是否就解决了
Redis 可以提高某些计数操作吞吐,但会引入持久化、故障转移和数据库同步问题。若库存或余额要求严格不超额,需要先定义 Redis 故障时的权威状态、恢复协议和核对方式。
9. 面试题
9.1 如何治理长事务和热点行更新
出现公司:腾讯、招银科技、京东
考察重点
- 锁、连接、undo 历史与复制成本。
- 事务内远程调用和大批量工作的拆分。
- 条件更新、分片、串行化与业务不变量。
相关内容:第 1 节“事务持续时间决定资源占用时间”至第 7 节“监控事务年龄与等待链”。
参考回答
长事务会长时间占用连接和锁,旧 Read View 还会阻止相关 undo 版本清理;大事务的提交、复制和回滚成本也更高。先从活跃事务年龄、锁等待、连接池和 undo 历史定位完整调用链,再把远程调用移出事务,把批处理按稳定游标拆成可恢复的小事务。
热点行会让并发更新在同一记录锁上串行。可以先用单条条件 UPDATE 缩短临界区;达到单行上限后,根据业务一致性要求选择分片计数、按键分区、串行消费者或额度预分配。无论采用哪种方式,都要保留幂等、失败恢复和核对机制,不能只把状态换到另一个组件。