Laravel怎么使用DB事务_Laravel数据库事务处理教程【一致】

2次阅读

是的,db::transaction() 中抛出 exception 或 Error 会自动回滚,但普通 return 或静默失败不会;laravel 不支持真嵌套事务,内层 throw 由最外层捕获并回滚整个事务。

Laravel怎么使用DB事务_Laravel数据库事务处理教程【一致】

DB::transaction() 里抛异常就自动回滚吗

是的,但只对 ExceptionError 生效,普通 return 或静默失败不会触发回滚。

常见错误现象:在事务闭包里写了 if ($x) { return; },后面逻辑跳过,但事务已提交——因为没抛异常,Laravel 认为执行成功。

  • 必须用 throw new Exception()throw new RuntimeException() 中断流程
  • DB::beginTransaction() + DB::commit()/DB::rollback() 手动控制更明确,适合复杂分支判断
  • Laravel 10+ 对 FatalError 也支持自动回滚,但 PHP 8.0+ 才稳定,旧版本建议兜底捕获

嵌套事务在 Laravel 里怎么处理

Laravel 的 DB::transaction() 不支持真嵌套,第二次调用只是“加一层 try-catch”,不是新开事务。

使用场景:比如服务类 A 调用服务类 B,两者都写了 DB::transaction(),实际只有最外层生效。

  • 内层事务里的 throw 会向上冒泡,由最外层捕获并回滚整个事务
  • 想隔离内层操作?改用 DB::unprepared('SAVEPOINT sp1') + DB::unprepared('ROLLBACK TO SAVEPOINT sp1')
  • 注意:mysql 默认隔离级别是 REPEAtable READ,SAVEPOINT 在长事务中可能引发锁等待,压测时留意 SHOW ENGINE INNODB STATUS 中的 lock info

事务里查不到自己刚插入的数据

不是 bug,是事务隔离级别的正常表现。默认 REPEATABLE READ 下,事务启动瞬间的快照被固定,后续 select 看不到本事务中未提交的 INSERT/UPDATE。

但 Laravel 的 DB::table()->insertGetId() 或模型 save() 后立刻 fresh() 能拿到数据,因为它们绕过了快照,直读最新行(前提是引擎支持,InnoDB 可以)。

  • 如果非要 SELECT * 查刚插的记录,用 DB::select('SELECT * FROM users WHERE id = ?', [$id]) —— 原生查询在当前事务上下文中读最新
  • 避免在事务里依赖“查自己刚写的”,改为用返回值或变量暂存
  • 切到 READ COMMITTED 隔离级别能缓解,但需全局配置 DB::connection()->getDoctrineConnection()->getdatabasePlatform()->setTransactionIsolationLevel(...),影响面大,不推荐只为这点改

事务超时导致死锁或连接

MySQL 默认 innodb_lock_wait_timeout=50 秒,但 Laravel 的 pdo 连接还有 timeoutwait_timeout 两层限制,容易混淆。

典型现象:SQLSTATE[HY000]: General error: 2006 MySQL server has gone awayLock wait timeout exceeded

  • config/database.php 的 MySQL 配置里加 'options' => [PDO::ATTR_TIMEOUT => 10],控制单条语句等待上限
  • 长事务(如导入万级数据)务必拆成小批量,每次 DB::transaction() 控制在 100 条以内,配合 DB::table()->upsert() 减少 round-trip
  • 别在事务里做 http 请求、文件读写、sleep() —— 这些会让连接空占着,触发 wait_timeout 断连

事务不是银弹,跨服务、跨库、异步场景下,最终一致性比强一致更实际。该用消息队列补偿的时候,别硬扛在事务里塞逻辑。

text=ZqhQzanResources