事务未生效主因是异常被静默捕获、嵌套事务模拟失效、连接复用导致查不到新数据及隔离级别未显式设置;须确保异常重抛、避免嵌套调用、统一Db实例、配置PDO初始化命令。

事务没生效?检查 Db::transaction() 是否被异常吞掉
ThinkPHP 的事务默认是“遇到异常自动回滚”,但很多人在 try...catch 里把异常捕获后没重新抛出,结果事务悄悄提交了。比如你写了 catch (\Exception $e) { Log::error($e); },那事务就真提交了——表面没报错,数据却乱了。
- 务必确认所有
catch块里:不处理异常就别写catch;要处理也得throw $e或调用Db::rollback() -
Db::transaction()内部抛出的异常类型包括\think\Exception和底层 PDO 异常,建议统一捕获\Throwable - 如果用了模型事件(如
afterSave),事件里抛异常不会触发外层事务回滚,得手动控制
嵌套事务不回滚?ThinkPHP 默认不支持真嵌套
PHP 没有原生嵌套事务支持,ThinkPHP 的 Db::transaction() 嵌套只是“计数模拟”:外层 begin、内层又 begin,只算次数;外层 commit 才真提交。一旦内层想单独 rollback,实际会失败,且可能破坏外层一致性。
- 避免在事务中再调用
Db::transaction()—— 改用Db::startTrans()+Db::commit()/Db::rollback()显式控制 - 如果必须分段控制,每个子逻辑应返回布尔值,由外层统一判断是否继续或回滚
- 注意 MySQL 的
SAVEPOINT在 ThinkPHP 中需手动执行Db::execute('SAVEPOINT sp1'),且不被Db::transaction()自动管理
升级到 ThinkPHP 6.1+ 后事务隔离级别失效?检查配置和驱动
TP6.1 开始,database.php 中的 'deploy' => 0(单数据库)下,'params' 里的 PDO::ATTR_EMULATE_PREPARES 和事务隔离设置容易被忽略。尤其 MySQL 8+ 默认 REPEATABLE READ,但 TP 不自动 set,导致幻读或脏读感知不到。
- 在数据库配置的
'params'数组中显式加:PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED" - 使用
Db::connect()->getPdo()->setAttribute(...)动态设置仅对当前连接有效,不适合长连接池场景 - PostgreSQL 驱动下,
READ UNCOMMITTED会被自动降级为READ COMMITTED,TP 不报错但行为已变
事务中查不到刚插入的数据?注意查询连接是否复用
ThinkPHP 默认开启连接池和连接复用,但在事务中用 Db::table()->insert() 后,紧接着用另一个 Db::name() 查询,可能走的是不同连接(尤其配置了读写分离),导致查不到未提交数据。
立即学习“PHP免费学习笔记(深入)”;
- 强制同连接查询:所有操作都基于同一个
Db实例,例如$db = Db::connect(); $db->startTrans(); $db->table(...)->insert(...); $db->table(...)->select(); - 读写分离时,事务期间必须关闭读库路由:
Db::setReadMaster(true),否则写完立刻读可能打到从库 - 模型操作(
UserModel::create())默认走独立连接,事务中慎用,优先用Db原生接口
事务不是开关,是上下文。TP 封装越深,越容易漏掉连接归属、异常传播、隔离级别这些底层事实。上线前至少用慢日志 + SHOW ENGINE INNODB STATUS 看一眼事务状态,比写十遍 Db::transaction() 都管用。



















