Db::transactionLevel() 返回事务嵌套层级计数而非布尔值,反映 transTimes 状态,用于 savepoint 判断;返回0却仍在事务中常见于手动 startTrans 后状态未清理或异常被吞,需用 getPdo()->inTransaction() 验证真实状态。

Db::transactionLevel() 返回的是当前数据库连接上已开启的事务嵌套层级计数,不是“是否在事务中”的布尔值。它反映的是 transTimes 内部变量的当前值,直接决定后续 commit 或 rollback 的行为——比如是否触发 SAVEPOINT 回滚而非全局回滚。
为什么 Db::transactionLevel() 返回 0 却还在事务里?
常见于手动调用 Db::startTrans() 后未走框架自动管理流程,或闭包内异常被吞掉导致事务状态未清理。此时 transTimes 可能已归零,但底层 PDO 连接仍处于 active transaction 状态(MySQL 侧可查 SELECT @@in_transaction)。这种“状态错位”会导致后续 Db::commit() 报错或静默失败。
- 检查是否在
Db::transaction()外层又手动调用了Db::startTrans()—— ThinkPHP 不允许混用两种事务模式 - 确认闭包里没用
try/catch捕获异常后继续执行,这会让框架误判事务已安全结束 - 调用
Db::transactionLevel()前,先用Db::getPdo()->inTransaction()(PHP 8.1+)或SELECT @@in_transaction验证 MySQL 实际状态
Db::transactionLevel() 在嵌套事务中的真实作用
它不控制逻辑,只反映当前 transTimes 计数。ThinkPHP 6/8 的嵌套事务本质是:外层 beginTransaction() + 内层 SAVEPOINT。而 Db::transactionLevel() 就是这个计数器的读取接口,用于业务层做条件判断(比如避免重复 commit)。
- 层级为 1:说明是顶层事务,
commit()会真正提交到数据库 - 层级 > 1:说明处于 savepoint 范围内,
rollback()默认只回滚到最近 savepoint,不会影响外层 - 不要依赖它来“判断是否该开启事务”,而应统一用
Db::transaction()封装业务逻辑
哪些操作会让 Db::transactionLevel() 不准?
主要来自连接复用和状态泄漏。ThinkPHP 的事务计数器绑定在单个 PDOConnection 实例上,一旦连接被复用或跨请求残留,transTimes 就可能失真。
立即学习“PHP免费学习笔记(深入)”;
- 在 Swoole/Workerman 长生命周期环境中,未在每次请求后重置连接(如没调用
Db::close()) - 模型方法内部调用了
Db::connect('other_config'),切换了连接但没同步事务状态 - 闭包中执行了 DDL 语句(如
CREATE TABLE),触发 MySQL 隐式提交,但 PHP 层transTimes未减 - 使用
Db::query()手动执行 SQL 时绕过了事务钩子,导致计数器与实际状态脱节
最易被忽略的一点:事务层级数字本身没有业务含义,它只是 savepoint 机制的副产品。别把它当开关用,也别在 if 分支里靠它决定是否调用 Db::commit() —— 正确做法是让所有写操作都在同一个 Db::transaction() 闭包里完成,由框架统一收口。



















