thinkphp 5.1 的事务嵌套实为基于 savepoint 的模拟,并非真正多层事务;必须使用 db::transaction() 闭包,禁用 starttrans/commit/rollback 手动组合,所有操作应置于同一闭包内,避免多层调用导致状态错乱。

ThinkPHP 5.1 的事务嵌套不是真正意义上的多层事务,而是基于 SAVEPOINT 的模拟实现。一旦写法不规范,极易出现“内层失败、外层仍提交”“部分回滚失效”“rollback to 不存在的保存点”等隐蔽问题。核心原则是:**避免逻辑嵌套,统一收口到单个闭包事务中;必须用 Db::transaction(),禁用 startTrans/commit/rollback 手动组合。**
优先用单层闭包事务,不拆分嵌套逻辑
即使业务上看起来是“分步骤”,也应把所有数据库操作写在同一个 Db::transaction() 闭包内。框架不会因为你调用了两次 Db::transaction() 就自动维护两层状态——它只认最外层连接的事务上下文。
- ✅ 正确:所有操作在一个闭包里完成,异常由框架自动捕获并回滚
- ❌ 错误:把用户创建、日志记录、积分更新分别封装成三个函数,每个函数内部又调用
Db::transaction()
若必须分层封装,用参数传递连接实例,不依赖全局 Db
当确实需要复用某段带事务的逻辑(比如通用的扣减库存方法),不要让它自行开启事务,而是接收一个已开启事务的连接对象作为参数:
- 定义函数时接受
\think\db\Connection $conn参数 - 调用方在顶层事务闭包中传入
Db::getConnection()实例 - 被调用函数内部只用该连接执行
$conn->table(...)->insert(),不调用任何事务控制方法
隔离级别要传 PDO 常量,不能传字符串
指定事务隔离级别时,必须使用 ThinkPHP 提供的常量,如 Db::TRANSACTION_SERIALIZABLE。传 'SERIALIZABLE' 或 'serializable' 都无效,PDO 会静默忽略,降级为默认级别(MySQL 默认是 REPEATABLE READ)。
- ✅
Db::transaction(fn() => {...}, Db::TRANSACTION_SERIALIZABLE) - ❌
Db::transaction(fn() => {...}, 'SERIALIZABLE')
确保表引擎为 InnoDB,否则事务完全无效
MyISAM 表不支持事务,哪怕代码写得再规范,ROLLBACK 也不会生效。检查方式:
- 执行
SHOW CREATE TABLE user;,确认ENGINE=InnoDB - 建表时显式指定:
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - 批量修改:用 phpMyAdmin 或命令
ALTER TABLE log ENGINE=InnoDB;
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











