Workerman 4.0.10 中数据库事务长时间挂起会导致连接阻塞、锁表、内存泄漏甚至进程僵死,必须在事务生命周期内主动控制执行时长与回滚边界;事务绑定于常驻 Worker 进程的 PDO 连接,不会随请求结束自动关闭,未捕获异常或阻塞 I/O 会使事务长期 hold 连接和行锁;可通过 SHOW ENGINE INNODB STATUS 查看 RUNNING 状态且启动时间过久的事务确认卡住;推荐在事务外用 PHP 定时器超时强制 rollback,需显式保存并取消 timer;catch 必须捕获 Throwable 以覆盖 Exception 和 Error;严禁跨 onConnect/onMessage/onWorkerStart 开启事务,应确保单次 onMessage 内完成全链路;建议配置 innodb_rollback_on_timeout=ON 和 wait_timeout=60 加强安全边界。

Workerman 4.0.10 中数据库事务长时间挂起会导致连接阻塞、锁表、内存泄漏甚至进程僵死,必须在事务生命周期内主动控制执行时长与回滚边界。
事务超时的根源定位
Workerman 的 Worker 进程是常驻内存的,【事务一旦开启,就绑定在当前 Worker 进程的 PDO 连接上,不会随请求结束自动关闭】。若业务逻辑中存在未捕获异常、阻塞 I/O(如同步 HTTP 请求)、或死循环,事务将一直 hold 着数据库连接和行锁,直到进程被主进程强制 kill 或 MySQL 自动超时中断(默认 wait_timeout=28800 秒),此时已无法保证数据一致性。
确认是否发生事务卡住:登录 MySQL 执行 SHOW ENGINE INNODB STATUS\G,查看 TRANSACTIONS 部分是否有 TRX_STATE: RUNNING 且 TRX_STARTED 时间远早于当前时间的事务。
手动设置事务最大执行时间
方法一:利用 PDO 的 ATTR_TIMEOUT 属性(仅对连接建立阶段生效,不控事务)→ 不适用。
方法二:MySQL 8.0+ 支持 SET SESSION innodb_lock_wait_timeout = N,但该值只影响锁等待,不终止已运行事务 → 不解决根本问题。
方法三(推荐):在事务块外启动 PHP 原生定时器,超时即 throw Exception 触发 rollback:
① 在 onMessage 回调开头记录 start_time:$start = microtime(true);
② 开启事务后,立即注册一个单次定时器:Timer::add(8, function() use ($pdo) { $pdo->rollback(); throw new RuntimeException('Transaction timeout'); }, [], false);
③ 所有业务逻辑完成后,先 cancel 定时器再 commit:Timer::del($timerId); $pdo->commit();。注意:$timerId 必须在 add 后显式保存,否则无法取消。
使用 try-catch 包裹事务并强制超时兜底
这一步操作起来很简单,直接把事务逻辑放进 try 块里就行。
关键点在于:catch 必须捕获 Throwable,而不仅是 Exception —— 因为超时触发的 throw 是 RuntimeException,但某些致命错误(如内存耗尽)会抛出 Error,只有 Throwable 能覆盖全部。
示例代码结构:
try {
$pdo->beginTransaction();
// 注册 8 秒超时定时器,保存 $timerId
// 执行查询、更新、外部 API 调用等
Timer::del($timerId);
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
【$pdo->rollback();】
}
throw $e;
}
避免事务跨回调生命周期
绝对禁止在 onConnect 中开启事务,然后等到 onMessage 才 commit —— 这种跨事件的事务会持续数小时甚至数天,彻底失控。
也禁止在 onWorkerStart 中初始化 PDO 并开启事务,因为 Worker 进程生命周期长达数日,事务状态会污染整个进程上下文。
正确做法:每个 onMessage 回调内完成“connect → begin → work → commit/rollback → close(可选)”全链路,确保事务严格限定在单次消息处理周期内。
配合 MySQL 配置收紧事务安全边界
在 my.cnf 中调整两项关键参数:
innodb_rollback_on_timeout = ON —— 当 lock wait 超时,自动回滚整个事务,而非仅回滚最后一条语句。
wait_timeout = 60 —— 将空闲连接断开阈值从默认 8 小时压缩至 60 秒,迫使 Workerman 在连接空闲时快速释放,避免僵尸连接残留。
修改后需重启 MySQL 生效。注意:此设置会影响所有客户端,确认其他服务能容忍 60 秒空闲断连。

















