ThinkPHP 8.0 执行原生 SQL 必须严格区分读写语义:查数据用 Db::query()(仅限 SELECT 等有结果集语句,返回二维数组或 false),改数据用 Db::execute()(仅限 INSERT/UPDATE/DELETE 等无结果集语句,返回影响行数或 false);动态字段/表名须白名单校验后拼接,不可用占位符;调试时 Db::getLastSql() 需紧跟调用。

ThinkPHP 8.0 执行原生 SQL 必须严格区分读写语义,用错方法会导致查不到数据、改不成功,甚至在读写分离环境下走错库。
查数据:必须用 Db::query()
只用于 SELECT、SHOW、EXPLAIN、WITH 等有结果集的语句,返回二维数组(空结果也是 []),失败返回 false。
- 正确写法:
Db::query("SELECT * FROM user WHERE status = :status", ['status' => 1])—— 推荐命名占位符,清晰不易错 - 问号绑定也支持,但顺序必须和数组值严格一致:
Db::query("SELECT name FROM user WHERE id = ? AND status = ?", [123, 1]) - 错误写法:
Db::query("UPDATE user SET name = 'test' WHERE id = 1")—— 不报错,但 SQL 不会发往写库,数据毫无变化
改数据:必须用 Db::execute()
专用于 INSERT、UPDATE、DELETE、REPLACE、TRUNCATE 等无结果集的写操作,返回影响行数(int),失败也返回 false。
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 正确写法:
$affected = Db::execute("UPDATE user SET name = ? WHERE id = ?", [$name, $id]); if ($affected === false) { /* 处理失败 */ } - 要获取最后插入 ID,不能依赖 execute() 返回值,得额外调:
Db::getLastInsID() - 错误写法:
$list = Db::execute("SELECT * FROM user"); foreach($list as ...)——$list是数字(如 0 或 1),直接 foreach 会报错
动态字段/表名必须白名单校验后拼接
占位符 :table 或 :field 不会被替换,PDO 会当成字符串字面量,导致语法错误或注入风险。
立即学习“PHP免费学习笔记(深入)”;
- 错误写法:
Db::query("SELECT * FROM :table WHERE id = ?", [$tableName, $id]) - 正确做法:先校验变量是否在白名单中,再安全拼接
$sortField = in_array($input, ['id', 'name', 'create_time']) ? $input : 'id';<br>Db::query("SELECT * FROM user ORDER BY {$sortField} DESC"); - 表前缀可用
__USER__静态写法,框架自动替换;变量拼接场景仍需白名单+手动拼
调试与安全细节
Db::getLastSql() 只记录最近一次 query() 或 execute() 调用的 SQL,调试时务必紧跟其后调用。
- LIKE 模糊查询:通配符必须包在绑定值里,
['name' => "%{$keyword}%"],别写进 SQL 字符串 - MySQL 8.0+ 报错?大概率是 PDO 预处理未开启模拟:
PDO::ATTR_EMULATE_PREPARES => true需手动配置 - 批量插入建议优先用
Db::name('user')->insertAll($data),比手写原生 INSERT 更稳,避免 SQL 长度超限


















