thinkphp 8 无事务超时配置,依赖 mysql 的 wait_timeout、interactive_timeout 和 innodb_lock_wait_timeout;需调低 mysql 参数并手动实现带超时与重试的事务逻辑。

ThinkPHP 8 默认不设事务执行超时,事务卡住会一直等下去,直到 MySQL 的 wait_timeout 或 interactive_timeout 主动断开连接——这时才抛出 SQLSTATE[HY000] [2006] MySQL server has gone away,但已无法回滚,数据可能处于中间状态。
事务内查询超时由 MySQL 控制,不是 TP8 配置项
TP8 没有类似 transaction_timeout 的全局配置。事务生命周期内的所有 SQL 执行,超时完全交由 MySQL 服务端决定:
-
wait_timeout(默认 28800 秒 = 8 小时):控制非交互式连接空闲多久后断开,适用于 FPM 场景 -
interactive_timeout(默认也是 28800):控制交互式连接(如 CLI、MySQL 客户端)空闲超时 - 这两个值必须在 MySQL 配置中显式调低(例如设为
300),否则 TP8 启动的事务可能挂起数小时不报错 - 改完需重启 MySQL 或执行
SET GLOBAL wait_timeout = 300(注意权限和持久化)
手动加锁或长事务必须配 lock_wait_timeout
如果你在事务里用了 SELECT ... FOR UPDATE 或 LOCK IN SHARE MODE,等待行锁释放的时间由 MySQL 的 innodb_lock_wait_timeout 控制,默认 50 秒。超时后 MySQL 直接报错 ERROR 1205 (40001): Deadlock found when trying to get lock 或 Lock wait timeout exceeded,TP8 不会自动重试或回滚——它只响应 PDO 异常,而这类锁超时是原生 MySQL 错误码,PDO 默认不转成异常。
- 确认是否启用异常模式:
'params' => [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION],否则锁超时不触发break_reconnect或事务自动 rollback - 生产环境建议将
innodb_lock_wait_timeout设为10~30秒,避免阻塞雪崩 - TP8 中无法在
Db::transaction()闭包里动态改这个值,只能全局配置 MySQL
break_reconnect 在事务中基本无效
很多人以为开了 'break_reconnect' => true 就能兜底事务中断,实际不行。这个配置只对「单条查询执行前发现连接已断」生效;一旦事务已开启,后续 SQL 报错(包括连接断开、锁超时、server gone away)都不会触发重连——因为事务上下文已丢失,重连后无法继续原事务。
- 事务中遇到连接断开,TP8 会直接抛
think\db\exception\PDOException,且不会 rollback(除非异常穿透到Db::transaction()顶层) - 正确做法是:不在事务中依赖
break_reconnect,而是缩短 MySQL 端超时 + 加监控告警 + 关键路径手动重试(见下一条) - 若真要重试整个事务,必须自己用
for循环包裹Db::transaction(),每次失败后重新 startTrans,不能复用旧连接
高风险操作必须自己实现带超时的事务重试
TP8 不提供事务级超时中断或自动重试封装。对支付、库存扣减等强一致性场景,得自己控制时间窗和重试逻辑:
- 用
microtime(true)记录事务开始时间,每步操作后检查是否超限(比如总耗时 > 8 秒),超了就主动throw new \Exception('Transaction timeout') - 重试最多 2~3 次,间隔用
usleep(100000)(100ms),避免压垮 DB - 每次重试都调用全新
Db::transaction(),不要在 catch 里调Db::rollback()后再 commit —— 已失效的事务对象不能复用 - 示例关键片段:
for ($i = 0; $i 8) { throw new \Exception('Transaction timeout'); } Db::table('stock')->where('id', 1)->dec('quantity'); }); break; } catch (\think\db\exception\PDOException $e) { if ($i === 2) throw $e; usleep(100000); } }
真正难的不是写几行重试代码,而是判断哪些操作值得重试、哪些错误类型必须立刻终止——比如主键冲突、余额不足,重试只会让问题更糟。超时配置只是兜底,核心还得靠 MySQL 层收敛、业务逻辑前置校验、以及链路可观测性补全。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











