db::transaction()闭包方式是thinkphp6.x唯一推荐的多表事务入口,自动复用连接、提交/回滚及捕获pdo异常;手动starttrans易出错,非pdo异常须显式re-throw,且依赖innodb引擎与主库读写一致性。

Db::transaction() 闭包方式必须用,别手写 startTrans
ThinkPHP6.x 中,Db::transaction() 是唯一推荐的多表事务入口。它自动复用连接、自动提交/回滚、自动捕获 PDO 异常,而手动调用 Db::startTrans()、Db::commit()、Db::rollback() 极易漏掉某一步或混用模型实例,导致事务失效。
常见错误现象:订单创建成功但库存没扣减,或日志写入了但主表回滚了——这基本是手动事务没配对,或跨模型用了不同 Db 实例。
-
Db::transaction()内所有操作(包括User::create()、Order::save()、Db::name('log')->insert())天然共享同一连接,无需额外指定 - 闭包外抛出的异常(如文件读取失败、HTTP 请求超时)不会触发回滚,必须在闭包内
catch后throw原异常 - 不要在闭包里调用
Db::name()->select()做判断再写入——若启用了读写分离,该select可能走从库,查不到刚改但未提交的数据
读写分离环境下必须强制走主库
事务期间所有 SQL 必须路由到主库执行,否则从库查不到未提交变更,业务逻辑会误判、重复写入或跳过关键校验。
错误做法:只在写操作前加 Db::master(),而读操作仍用默认连接;或完全没配置,依赖自动路由。
- 全局生效:在事务开始前调用
Db::setConfig(['read_master' => true]) - 局部生效:用
Db::connect('mysql')->transaction(...)显式指定主库连接 - 验证方法:开启 SQL 日志,确认事务内所有语句 host 都指向主库 IP,而非从库
非 PDO 异常必须显式 re-throw
Db::transaction() 只拦截 PDO 抛出的异常(如 SQL 错误、约束冲突),对业务层 throw new Exception、第三方 SDK 超时、文件不存在等完全无感——此时事务会“静默提交”,数据状态已不一致。
典型场景:库存校验通过后发起支付回调,回调失败但没 throw,订单和库存已写入,钱却没收到。
- 所有非数据库操作(HTTP 请求、文件写入、缓存更新)都应包裹在
try/catch中 -
catch块内必须throw $e或throw new RuntimeException(...),不能仅log或return false - 模型层的
validate()失败默认抛ValidateException,它会被捕获回滚;但自定义校验逻辑若只return false就不行
InnoDB 和外键约束是事务生效的前提
事务只是应用层控制流,底层保障靠数据库。如果表引擎是 MyISAM,或关键字段没加唯一索引、外键,Db::transaction() 再规范也拦不住脏数据。
容易被忽略的点:开发环境用 InnoDB,上线后因迁移脚本漏执行,部分表仍是 MyISAM;或中间表(如用户-角色关联)没设外键,导致主表回滚但中间表残留记录。
- 检查命令:
SHOW CREATE TABLE `order`确认引擎为ENGINE=InnoDB - 订单号、手机号等关键字段必须加
UNIQUE INDEX,避免并发重复插入 - 外键要双向约束:比如
order.user_id关联user.id,且ON DELETE RESTRICT
transaction,而在数据库引擎、连接一致性、异常传播路径这三处是否全部对齐。少一个,就可能在高并发下暴露数据不一致。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











