webman中db::transaction()仅作用于默认连接,无法保障跨库事务一致性;多库操作需手动管理各pdo实例的begintransaction/commit/rollback,或采用补偿事务+最终一致性方案。

Webman 里 Db::transaction() 只能保单库多表一致性,跨库事务根本无效;多表关联写入若涉及多个连接,必须手动控制 PDO 事务或改用补偿机制。
Db::transaction() 为什么只对默认连接生效
Webman 的 Db::transaction() 底层调用的是 Illuminate\Database\Connection 的事务方法,它只作用于配置中 'default' 指定的那个连接(比如 'mysql'),不会识别你代码里写的 Db::connection('mysql2')。
- 你在闭包里执行
Db::connection('mysql2')->table('log')->insert(...),这句完全游离在事务之外,提交或回滚都不管它 - 错误现象:主库插入成功、日志库插入失败,但事务仍显示“成功”,数据已不一致
- 根本原因:每个
Db::connection('xxx')返回的是独立 PDO 实例,autocommit 状态和事务上下文互不感知 - 验证方式:打印
Db::connection('mysql')->getPdo()->inTransaction()和Db::connection('mysql2')->getPdo()->inTransaction(),结果一定不同
多库写入时如何手动管理 PDO 事务
当业务要求“主库 + 日志库”必须原子性写入,就得绕过 Db::transaction(),直接操作底层 PDO。
- 先获取两个连接的 PDO 实例:
$pdo1 = Db::connection('mysql')->getPdo();、$pdo2 = Db::connection('mysql2')->getPdo(); - 分别开启事务:
$pdo1->beginTransaction();、$pdo2->beginTransaction(); - 执行各自 SQL,全部成功后按顺序
$pdo1->commit();、$pdo2->commit(); - 任一环节出错,必须反向回滚:
$pdo2->rollback();、$pdo1->rollback();(顺序不能错) - 注意:MySQL 不支持跨实例两阶段提交(2PC),commit 第一个成功、第二个网络超时会导致最终不一致
一对多写入时怎么避免 N+1 和事务失效
Webman 的 Model 不支持 with() 预加载,手动关联写入又容易破坏事务边界——尤其当你在循环里反复调用 OrderItem::create(),而它默认走的是 default 连接。
- 确保所有关联模型都显式绑定连接:
protected $connection = 'mysql';,否则可能混库 - 别在 foreach 里逐条 insert,改用批量:
OrderItem::insert($items);,且必须确认该模型的$connection和主表一致 - 如果主表和子表不在同一库,放弃强事务,改用补偿:先写主库,再把子表数据发到 Redis 队列,由 Worker 异步重试插入
- 字段类型必须严格一致:比如主表
orders.id是BIGINT UNSIGNED,子表order_items.order_id也必须是同类型,否则whereIn()查不到但不报错
最容易被忽略的常驻进程陷阱
Webman 是常驻进程模型,PDO 连接一旦开启事务却没正常结束(比如异常未捕获、忘记 commit/rollback),这个连接会一直卡在 inTransaction 状态,后续请求复用该连接时直接报错或行为异常。
- 务必在 try/catch 外层包住整个事务块,且
finally或catch中确保rollback()被调用 - 不要在
onRequest或控制器方法里 new PDO,所有连接初始化应放在onWorkerStart或服务提供者中 - 禁用 PDO 持久化:
PDO::ATTR_PERSISTENT => false,否则事务状态可能跨请求污染 - 定时心跳不是万能的:
select 1不会清除未完成的事务,只能防“server has gone away”
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











