db::begintransaction事务未生效的主因是协程隔离、连接上下文丢失及模型未共享事务连接;应统一用db::实例、同协程内配对操作,或优先使用db::transaction闭包确保自动回滚与连接绑定。

Db::beginTransaction 事务没生效,数据还是提交了
手动开启事务后,Db::beginTransaction() 执行了,但后续 Db::commit() 或 Db::rollback() 没起作用,SQL 还是写进了数据库——这说明事务根本没真正启用,或者被意外“绕过”了。
常见原因有三个:
-
Db::beginTransaction()后没在同一个协程内执行后续操作(比如把commit放到了另一个协程或异步任务里),连接上下文已丢失 - 调用了非
Db::系列方法(例如直接 new PDO、用原生 mysqli、或通过模型静态方法如User::create()而未指定连接),这些都脱离了当前事务上下文 - 事务过程中发生了未捕获的异常,且没有
try-finally保证rollback,协程退出时 Hyperf v2.2+ 虽会尝试回收连接,但不保证回滚
正确做法是:所有 SQL 操作必须用 Db:: 实例(如 Db::table('user')->insert(...)),并在同一协程内严格配对:
Db::beginTransaction();
try {
Db::table('user')->insert(['name' => 'Alice']);
Db::table('profile')->insert(['user_id' => 1, 'bio' => 'Hello']);
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
为什么 Db::transaction 闭包方式更安全
Db::transaction() 不是语法糖,它底层做了三件事:自动 beginTransaction、绑定当前协程连接、异常时强制 rollback。而手动方式漏掉任意一环就会失效。
尤其注意协程隔离性:
- 闭包内所有
Db::调用天然复用同一连接,无需担心跨协程错乱 - 闭包退出时,无论是否异常,框架都会清理事务状态,避免连接池中残留“半开事务”
- 手动方式若在事务中启动新协程(比如
go(function() { Db::table(...)->update(); })),那个协程拿到的是全新连接,完全不在当前事务里
所以除非你要用保存点(SAVEPOINT)或嵌套控制,否则别碰 beginTransaction。
事务 + 异步任务组合时必然失效
这是高频翻车点:在 Db::transaction() 闭包里调用 PushMessageTask::deliver() 这类异步任务,或者手动事务中 go(...) 启动协程去更新状态——事务一提交,异步任务才开始跑,此时数据库早已解锁。
典型错误写法:
Db::transaction(function () {
$order = Order::create([...]);
// ❌ 错误:异步任务和事务不同步
go(function () use ($order) {
Db::table('log')->insert(['order_id' => $order->id]);
});
});
解决思路只有两个:
- 把异步逻辑全收进事务闭包内,用同步方式执行(适合轻量、确定不超时的操作)
- 改用最终一致性:事务只保证核心数据落地,异步任务失败后靠重试+幂等+补偿查漏(比如定时扫描
status = 'pending'的订单补日志)
事务里混用模型和 Db:: 导致部分操作不回滚
Hyperf 的 Eloquent 模型默认走自己的连接实例,和 Db:: 不共享事务上下文。你在事务中调 User::create(),哪怕配置了 'connection' => 'default',也不会被 Db::transaction() 或 Db::rollback() 拦住。
验证方法很简单:在事务闭包里分别执行
-
Db::table('user')->insert(...)→ 会被回滚 -
User::create(...)→ 不会被回滚,数据已落库
要让模型参与事务,必须显式传入连接实例:
$connection = Db::connection(); User::on($connection)->create([...]); Profile::on($connection)->create([...]);
或者更稳妥地,全程只用 Db::table(),避开模型抽象层——事务场景下,越直白越可靠。











