ThinkPHP 必须在 PDO 连接初始化阶段通过 PDO::ATTR_INIT_COMMAND 设置事务隔离级别,因为 MySQL 隔离级是会话级且连接复用会导致污染;正确写法是在 database.php 的 'params' 中配置 SET SESSION TRANSACTION ISOLATION LEVEL。

ThinkPHP 数据库配置文件中设置事务隔离级别,本质不是框架“控制”隔离级,而是通过 PDO 初始化指令,让每个新数据库连接在建立时就执行 SET SESSION TRANSACTION ISOLATION LEVEL ...,从而覆盖 MySQL 默认的 REPEATABLE READ。
为什么必须在连接初始化阶段设置
MySQL 的事务隔离级别是会话(session)级的,一旦连接建立,其默认值就固定为服务器全局配置(5.7+ 默认 REPEATABLE READ)。ThinkPHP 不在每次 Db::transaction() 前自动重设,也不在 beginTransaction() 时透传参数——它复用 PDO 连接池中的已有连接,而上一个请求可能已把隔离级改成了别的值。若只靠运行时执行 SET 语句,极易被连接复用污染。
- 连接池中一个连接被 A 请求设为
READ COMMITTED,B 请求复用该连接却没重设,就会沿用这个级别(可能不符合 B 的预期) - 反之,若 A 没设、B 设了,B 结束后连接归还池中,C 请求拿到的就是带
READ COMMITTED的连接,造成“意外继承” -
PDO::ATTR_INIT_COMMAND是唯一能确保“每个新建连接第一件事就是设隔离级”的机制
database.php 中的正确写法(TP6/TP7)
在 config/database.php 的具体连接配置(如 'default')里,将初始化命令写入 params 数组:
'params' => [
\PDO::ATTR_INIT_COMMAND => "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"
]
- 该语句在 PDO 构造连接时自动执行,早于任何业务 SQL
- 仅影响当前连接配置,不影响其他连接(如从库、备用库)
- 不推荐使用
SET GLOBAL,它需要 SUPER 权限,且影响所有新会话,运维风险高
不推荐的替代方案及其问题
绕过初始化配置、试图在业务中动态调整,多数情况下不可靠:
立即学习“PHP免费学习笔记(深入)”;
-
Db::execute('SET SESSION ...'):只对当前连接当前会话生效,无法保证后续请求复用同一连接时仍保持该级别 - 在
Db::transaction()回调里设:Db::connect()->execute(...)再startTrans()—— 若连接已被其他请求用过,中间可能有状态残留 - 修改 MySQL 全局变量:
SET GLOBAL transaction_isolation = 'READ-COMMITTED'—— 需要高权限,重启失效,且影响所有应用,违反最小权限原则
验证是否生效的方法
部署后可通过以下方式确认连接确实以目标级别启动:
- 在任意控制器中执行:
Db::query("SELECT @@transaction_isolation")[0]['@@transaction_isolation'] - 或直接连 MySQL 执行:
SELECT @@tx_isolation;(注意 MySQL 8.0+ 返回值格式为READ-COMMITTED,而非旧版字符串) - 若返回
REPEATABLE-READ,说明ATTR_INIT_COMMAND未触发,检查是否写错键名(必须是params,不是option或attributes),或 PDO 版本过低不支持



















