DB::transaction闭包是最稳妥的手动事务控制方式,它自动处理提交回滚、支持嵌套降级、确保连接状态清理、可配置超时,并要求闭包内仅执行数据库操作且通过throw抛异常。
直接用 DB::transaction 闭包是最稳妥的手动事务控制方式,它自动处理提交与回滚,且支持嵌套事务的降级处理。
DB::commit() 和 DB::rollback()
手动配对调用容易出错:比如忘记在 catch 中 rollback、异常未被捕获、或事务中途被其他逻辑意外中断。更关键的是,Laravel 的底层 PDO 连接在 rollback 后若未重置状态,后续查询可能报 SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active 这类隐晦错误。
DB::transaction 内部用 try/finally 确保无论是否抛异常,连接状态都被正确清理Illuminate\Database\TransactionExcept
ion
DB::transaction 闭包里写什么才安全?闭包内只能执行数据库操作,不要混入 HTTP 请求、文件写入、队列分发等外部副作用。一旦这些操作失败,事务无法回滚它们,造成数据不一致。
save()、delete()、update())和原生查询(DB::insert()、DB::table()->where()->update())都受事务保护DB::transaction 嵌套——虽然 Laravel 支持,但深度嵌套会让调试和锁等待变得难以追踪最常踩的坑是用了 throw_if()、abort_if() 或自定义异常但没注意异常类型——DB::transaction 只会在 Throwable 被抛出时触发 rollback,而某些校验函数默认 throw Exception,这没问题;但如果你用 return response()->json(...) + exit 这种老式终止方式,事务根本不会回滚。
throw 发出异常,而不是 die、exit 或静默返回Validator::validate())抛的 ValidationException 是 Throwable 子类,能被正常捕获if ($condition) {
throw new RuntimeException('业务规则不满足');
}事务越长,锁持有时间越久,高并发下容易触发死锁或超时。MySQL 默认隔离级别是 REPEATABLE READ,Laravel 不会帮你改;PostgreSQL 则默认 READ COMMITTED。不同引擎行为差异大:
SELECT ... FOR UPDATE 在无索引字段上会升级为表锁DB::transaction 在 SQLite 上只是模拟,不适合生产环境多写场景DB::transaction(..., $timeout) 显式设低超时值防堆积真正难的是权衡一致性与响应速度——比如一个订单创建要扣库存、记流水、发通知,前三步必须原子,最后一步失败不能让订单回滚,得单独设计补偿逻辑。这时候事务边界就得切在“扣库存+记流水”之后,而不是包揽全部。