db::transaction() 仅保证原子性,不提升性能;拖慢事务的是其中的非数据库操作、模型事件和长耗时逻辑,应将http请求、文件读写、发短信等io操作移出事务并异步执行。

DB::transaction() 本身不提速,只保原子性;真正拖慢事务的,是里面混进的非数据库操作、模型事件、长耗时逻辑。
事务里别做 HTTP 请求或文件读写
事务开启后,PDO 连接一直被占用,直到 commit 或 rollback。一旦你在 DB::transaction() 闭包里调用 Http::post()、Storage::put() 或循环处理大数组,连接就卡住不动——其他请求排队等连接,轻则响应变慢,重则触发 SQLSTATE[HY000]: General error: 2006 MySQL server has gone away 或连接池耗尽。
- 把发短信、调支付回调、生成 PDF 这类 IO 操作全部移出事务块
- 改用
dispatchNow()或dispatch()+ 队列,在事务提交后再异步执行 - 如果必须同步等外部结果(比如验签),先做完再进事务,别反过来
批量插入别用 Eloquent::insert(),改用 DB::table()->insert()
用 User::insert($data) 看似方便,但会触发 creating、created 事件,自动补 created_at/updated_at,还走模型类型转换和验证钩子——每条记录多 3–5 次函数调用,1000 条就是上千次开销。
- 直接用
DB::table('users')->insert($rows),$rows 是纯二维关联数组,键名严格对应字段名 - 时间戳自己加:
'created_at' => now()->toDateTimeString() - 需要禁事件时,插前调
User::unsetEventDispatcher(),插完再User::setEventDispatcher(...) - 单次
insert()不要超过 500 行,避免超max_allowed_packet
别信“嵌套事务”,真要局部回滚得用 SAVEPOINT
Laravel 的 DB::transaction() 嵌套只是计数器叠加,不是真正的子事务。第二层调用不会新建事务,commit() 无效,rollback() 会干掉整个外层——你根本控制不了粒度。
- 想在订单创建中单独回滚库存扣减而不影响用户创建?别写两层
DB::transaction() - 改用原生 savepoint:
DB::statement('SAVEPOINT sp_inventory')和DB::statement('ROLLBACK TO SAVEPOINT sp_inventory') - 注意:启用
PDO::ATTR_EMULATE_PREPARES = false,否则 savepoint 语句可能被预处理拦截报错 - savepoint 不是银弹,它仍持锁,只是缩小了回滚范围
查不到刚插的数据?检查隔离级别和连接复用
在事务里 DB::table('logs')->insert(...) 后立刻 Log::where('id', $id)->first() 查不到,大概率不是事务没生效,而是你用了不同连接或隔离级别干扰。
- 确保所有查询都走同一个连接:不要在事务中调
DB::connection('tenant')->table(...),除非显式复用 - MySQL 默认
REPEATABLE READ,事务内 SELECT 能看到自己 INSERT 的数据,但跨连接看不到 - 别在事务里 dispatch 队列任务并指望它查到未提交数据——队列用的是新连接
- 验证方法:事务内用
DB::getPdo()->inTransaction()确认是否真在事务中
事务性能瓶颈从来不在 DB::transaction() 这行代码上,而在你往它里面塞了什么。最常被忽略的是:模型 Observer 里的 DB 操作默认不进事务,static::created() 写日志表失败,订单却已落库——这种不一致比慢更危险。











