Webman事务不生效主因是模型连接与事务连接不匹配,如User模型指定'mysql_log'连接而事务在'default'开启;必须统一使用Db::connection('mysql_log')->transaction(),并用\Throwable捕获异常确保回滚。

Webman 事务不生效,八成是连接没对上
Webman 的 Db::transaction() 只作用于默认数据库连接,而模型(如 User)若显式声明了 $connection = 'mysql_log',那它的 save() 就跑在另一个 PDO 实例上——事务根本管不到它。
常见错误写法:
Db::transaction(function () use ($user) {
$user->name = 'new';
$user->save(); // 这句实际走的是 mysql_log 连接,不在事务里
});
- 必须显式指定连接:
Db::connection('mysql_log')->transaction(...) - 或者让模型不设
$connection,完全依赖Db::transaction()的默认连接 - 检查
config/database.php中'default' => 'mysql'是否与模型配置一致,大小写敏感 - 用
webman/log组件查日志,关键词Uncommitted transactions能快速暴露漏commit()或rollback()的地方
SELECT FOR UPDATE 在 Webman 里容易锁不住
Webman 基于 Swoole 长连接,默认 PDO 开启自动提交(PDO::ATTR_AUTOCOMMIT => true),导致 SELECT ... FOR UPDATE 执行完立刻释放锁,形同虚设。
- 必须手动关闭自动提交:
$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, false) - 整个“查-算-改”必须包裹在
$pdo->beginTransaction()和$pdo->commit()之间 -
WHERE条件必须命中索引,否则可能升级为表锁;例如WHERE id = ?要求id是主键或有索引 - 多个并发请求同时执行相同
SELECT ... FOR UPDATE会阻塞,超时由 MySQL 的innodb_lock_wait_timeout控制(默认 50 秒) - 避免死锁:所有服务端对同一组记录加锁时,必须保持一致的查询顺序(如按
id升序锁)
乐观锁更新失败却不重试,等于没锁
Webman 没有 Eloquent 自动版本校验,靠手写 SQL 实现乐观锁时,最容易忽略 rowCount() 返回值 —— 它为 0 表示别人已抢先更新,当前操作应失败或重试。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
- 单条 SQL 必须包含版本校验:
UPDATE users SET balance = ?, version = version + 1 WHERE id = ? AND version = ? - 执行后严格判断:
if ($stmt->rowCount() === 0) { /* 处理冲突 */ },不能只看是否抛异常 - 不要拆成两步:
$user->version++再$user->save(),这会导致两个并发请求都读到相同version,都写入version + 1,丢失一次更新 - 重试逻辑建议控制次数(如最多 3 次),避免无限循环拖垮响应时间
跨库写入别硬扛强一致性
Webman 不支持跨连接事务,Db::transaction() 对 mysql2 连接完全无效。试图在 HTTP 请求里同步写主库 + 日志库,只会拉长响应、耗尽连接、增加超时风险。
- 放弃“全成功或全失败”的幻想,改用补偿事务 + 最终一致性
- 主库操作成功后,把日志内容写入 Redis 队列或本地文件,标记为
pending_log - 起独立 Worker 异步消费队列,失败则延时重试(带最大次数限制)
- 另设定时任务扫描超时
pending_log,触发告警或人工介入 - 绝对不要在
go协程里调beginTransaction(),再在主协程commit()—— 连接不一致,事务直接失效
真正难的不是写对一行 SQL,而是让每个连接、每个协程、每个锁粒度都在你预期的轨道上运行。Webman 的常驻进程模型放大了任何微小的连接或事务管理疏漏,越早盯住 Uncommitted transactions 日志和 rowCount() 返回值,越不容易在线上翻车。










