ThinkPHP 6.x 中 PDO::ATTR_EMULATE_PREPARES 默认未显式设置,实际为 true(开启),需在 database.php 的 'params' 中显式设为 false 才能启用 MySQL 原生预处理、真正防止 SQL 注入。

ThinkPHP里PDO::ATTR_EMULATE_PREPARES默认是开还是关
ThinkPHP 6.x 默认未显式设置 PDO::ATTR_EMULATE_PREPARES,实际行为取决于底层 PDO 驱动的默认值(通常是 true)。这意味着你写的 $this->where('id', $id)->find() 看似用了预处理,但很可能只是 PDO 在 PHP 层把 $id 转成字符串后拼进 SQL——参数根本没发给 MySQL 做原生绑定。
为什么关闭PDO::ATTR_EMULATE_PREPARES才能真正防SQL注入
开启模拟预处理时,PDO 自己做占位符替换,绕过了数据库对参数类型的识别和边界校验。尤其在 GBK、BIG5 等宽字节编码下,%df%27 这类构造可能被截断重组,触发注入。关闭后,MySQL 服务端收到的是纯模板语句(如 SELECT * FROM user WHERE id = ?)和独立的二进制参数包,两者完全隔离。
- 错误现象:
sql.log里只看到SQL: SELECT * FROM user WHERE id = ?,没有Binding: [123] - 真实场景:ThinkPHP 的
Db::table()->where()->select()底层调用pdo->prepare(),但若PDO::ATTR_EMULATE_PREPARES === true,prepare 实际不发往 MySQL - 兼容性注意:MySQL 5.5+、MariaDB 10.2+ 均支持原生预处理;旧版 MySQLi 驱动或某些 Docker 镜像(如
mysql:5.6默认配置)可能需额外确认
ThinkPHP中如何正确关闭PDO::ATTR_EMULATE_PREPARES
必须在数据库连接初始化阶段就强制设为 false,不能靠运行时 setAttribute() 补救——因为第一次 prepare() 调用前连接已建立,模拟模式已生效。
- 修改
config/database.php中对应连接的'params'配置项 - 加入
PDO::ATTR_EMULATE_PREPARES => false,同时建议配PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - 示例片段:
'mysql' => [ 'type' => 'mysql', 'hostname' => '127.0.0.1', 'database' => 'test', 'params' => [ \PDO::ATTR_EMULATE_PREPARES => false, \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, ], ], - 验证方式:执行一条带变量的查询后,检查
runtime/log/sql.log,确认出现Binding: [value]行,而非只有SQL:行
关闭后要注意的几个硬性限制
原生预处理不是万能开关,它会暴露一些平时被模拟层掩盖的问题。
立即学习“PHP免费学习笔记(深入)”;
- 不支持在同一个
prepare()语句中混用不同数据类型字段:比如WHERE id = ? AND name = ?,若第一个?绑定整数、第二个绑定字符串,MySQL 可能报错SQLSTATE[HY093]: Invalid parameter number - 动态表名/字段名无法绑定:ThinkPHP 的
->table($tableName)或->field($fields)仍是字符串拼接,必须自行白名单校验 - 部分特殊函数(如
FIND_IN_SET)在原生预处理下可能因参数类型推导失败而报错,需显式用bindValue($param, $value, PDO::PARAM_STR) - ThinkPHP 模型的
whereRaw()和查询构造器的exp类方法仍属原生 SQL 拼接,关闭模拟预处理对其无防护作用
关闭 PDO::ATTR_EMULATE_PREPARES 是 ThinkPHP 项目防注入的底线操作,但它只管住“参数绑定”这一环;字段名、表名、函数名、原始 SQL 片段,依然要靠白名单、正则过滤或彻底避免动态拼接来兜底。



















