事务回滚失效的根本原因是混用db()和db::name()导致跨pdo连接操作,而事务仅在同一个pdo实例中有效;begin与insert在a连接,db('user')->insert()却走b连接直写磁盘,致使rollback无效。

因为混用 db() 和 Db::name() 会导致事务跨数据库连接,而事务只能在**同一个 PDO 连接实例**上生效。一旦 SQL 执行和 rollback() 操作不在同一条连接上,rollback 就成了“对空喊话”——执行了,但没作用。
事务失效的核心原因
ThinkPHP 的 Db::transaction() 闭包内部必须全程复用同一个数据库连接。但:
-
db('user')是助手函数,每次调用都可能新建或切换连接(尤其配置了读写分离时) -
Db::name('user')默认复用当前默认连接(通常是主库),遵守事务上下文 - 两者底层获取的 PDO 实例不同 → BEGIN 在 A 连接,INSERT 在 A 连接,但
db('user')->insert()实际走 B 连接 → B 连接根本没开启事务,数据直写磁盘
典型表现与验证方式
你可能会看到这些现象:
- 日志里没有
BEGIN TRANSACTION记录,或报错There is no active transaction -
Db::rollback()执行不报错,但查库发现数据已写入 - 用
SHOW PROCESSLIST或慢日志观察:同一事务闭包内,SQL 出现在多个连接 ID 上
正确写法与替代方案
统一连接入口,禁用显式连接切换:
- ✅ 全部使用
Db::name('user')或模型方法(如UserModel::create()) - ✅ 如需强制走主库查刚写入的数据,用
Db::master()->name('user')->select() - ❌ 禁用
db('user')、Db::connect('slave')、Db::master()等显式连接操作 - ❌ 不要在事务中调用封装了
db()的第三方服务或旧工具函数
额外要确认的底层前提
即使连接一致,以下两点不满足,事务照样不回滚:
- 数据表引擎必须是 InnoDB(执行
SHOW CREATE TABLE user;查ENGINE=InnoDB) - 异常必须穿透闭包 —— 闭包内不能有
try/catch吞掉异常;若要记录日志,末尾必须throw $e;











