第 8 章:锁等待超时不一定回滚整个事务
收到 1205 后,应用若直接继续提交,可能提交事务里先前已经成功的语句。这与上一章的死锁受害事务不同。恢复策略必须知道具体错误与服务器配置。
8.1 先读实际配置
InnoDB 默认锁等待超时回滚当前失败语句;开启 innodb_rollback_on_timeout 时,行为会改变。随书实验先检查该配置为关闭,再观察默认路径,避免把一个部署配置当成所有实例的常量。
SELECT @@innodb_rollback_on_timeout;
SELECT @@innodb_lock_wait_timeout;锁等待超时还不同于网络读超时。前者是服务器明确返回数据库错误;后者可能意味着客户端根本没有收到语句的最终结果,不能由异常类名字相似就采用同一处理。
8.2 一个故意提交半完成的反例
连接 A 持有账户 1 的锁。连接 B 先把账户 2 增加 5,再尝试修改账户 1,等待后收到 1205。实验故意对 B 执行 COMMIT,最后 A 回滚。
实际余额为账户 1 的 100、账户 2 的 105。B 的第一条更新保留下来,说明失败的第二条语句没有自动撤销整个事务。这个有意制造的反例仅在专用随机数据库中运行,不是推荐的业务处理方式。
8.3 把应用恢复范围写清楚
对于要求整个业务一起完成的事务,一个清楚的应用策略是捕获相关异常后显式回滚整笔事务,再在允许的条件下重新开始业务过程。不要让连接带着部分状态回到池里,也不要在不了解前序变化的情况下重发最后一条语句。
某些应用确实希望使用保存点或重试语句,这需要明确证明局部恢复与业务约束相容。选择哪一种,应基于事务内容和配置,而不是套用一个捕获所有异常的通用重试装饰器。
8.4 超时时间不是吞吐容量
提高锁等待时间可能减少短暂竞争导致的报错,也可能让更多连接长时间挂起。等待者占用应用线程和连接池时,故障会传播到本来不相关的请求。
应先找出谁在持锁、持锁多久、为什么不提交,再决定是否调整期限。将队列堆在数据库连接里,无法替代请求准入、事务缩短或热点数据设计。
练习。 同一事务第一条语句已成功,第二条等待超时报错。应用 catch 后没有回滚,连接池归还连接时也没有处理。下一位使用该连接的请求可能继承什么?你会分别在业务代码和连接池边界检查哪项行为?