长事务导致行锁堆积,因InnoDB在事务未提交前持续持有X锁,阻塞其他事务更新同一行;常见于将耗时操作包入事务中,锁直到commit才释放。

长事务为什么会导致行锁堆积
事务开启后未提交,InnoDB 会一直持有已修改行的 X 锁(排他锁),其他事务想更新同一行就会卡在 Waiting for table metadata lock 或直接超时。常见于 PHP 中把耗时操作(如远程 API 调用、大循环计算、文件读写)包进 beginTransaction() 和 commit() 之间——锁不是等 SQL 执行完就释放,而是等到整个事务结束才释放。
怎么识别事务是否“过大”
看三类信号:
- MySQL 慢查询日志里出现带
SELECT ... FOR UPDATE或UPDATE的语句,执行时间 > 1s - 监控发现
Innodb_row_lock_time_avg持续升高,或SHOW ENGINE INNODB STATUS输出中频繁出现---TRANSACTION ... WAITING FOR THIS LOCK TO BE GRANTED - 用户反馈某接口偶发性卡顿或报错
Lock wait timeout exceeded拆分事务的实操边界在哪
核心原则:只把「必须原子性保证」的操作留在事务内,其余全移出去。例如一个订单创建流程:
- ✅ 必须在事务内:
INSERT INTO orders、UPDATE products SET stock = stock - 1 WHERE id = ?(库存扣减需和订单强一致) - ❌ 必须移出事务:
file_put_contents('log/order.log', ...)、curl_exec($notify_api)、generate_pdf() - ⚠️ 可选异步化:
INSERT INTO order_logs(若允许日志延迟写入,可改用消息队列或定时任务补)
拆分后结构示意:
立即学习“PHP免费学习笔记(深入)”;
try { $pdo->beginTransaction(); $pdo->prepare("INSERT INTO orders (...) VALUES (...)")->execute([...]); $pdo->prepare("UPDATE products SET stock = stock - 1 WHERE id = ?")->execute([$pid]); $pdo->commit(); <pre class="brush:php;toolbar:false;">// ✅ 事务已结束,锁已释放 notify_order_created($order_id); // 远程通知 write_order_log($order_id); // 文件写入} catch (Exception $e) { $pdo->rollback(); throw $e; }
拆分后还要防什么坑
拆事务不等于万事大吉,下面三点最容易被忽略:
- 业务幂等性没做:通知接口失败重试时,
notify_order_created()可能被调两次,得靠数据库唯一索引或 RedisSET order_notify_123 "done" NX PX 3600拦住重复 - 补偿逻辑缺失:事务成功但后续步骤失败(如通知超时),没有记录失败状态或触发重试队列,导致订单“半完成”
- 分离后的数据一致性假象:比如库存扣减成功,但通知失败,用户以为下单失败而重复点击——前端要禁用按钮 + 后端查订单状态接口兜底
真正的难点不在“怎么拆”,而在拆完之后,如何让各环节在失败时仍能收敛到一个确定状态。
- ✅ 必须在事务内:



















