tp8中db::query()/execute()表面行为不变但底层校验更严:强制sql类型匹配、占位符规范(?与:name不可混用)、标识符需白名单校验、非事务语句事务中被拦截。

ThinkPHP 5 升级到 ThinkPHP 8 后,Db::query() 和 Db::execute() 的行为**表面没变,但底层约束收紧、参数校验更严,直接迁移会静默失败或报错**。TP8 不再兼容 TP5 那种“拼字符串 + 手动转义”的写法,也不再容忍占位符误用、标识符硬拼等旧习惯。
TP8 中 query/execute 的语义和返回值没变,但调用规则变了
TP8 仍要求:Db::query() 仅用于 SELECT/SHOW/EXPLAIN,返回二维数组;Db::execute() 仅用于 INSERT/UPDATE/DELETE,返回影响行数(int 或 false)。但 TP8 加了运行时 SQL 类型检测——比如你用 Db::query() 执行 UPDATE,TP8 会直接抛出 think\db\exception\SqlException,不再像 TP5 那样默默返回空数组。
- TP5:不检查 SQL 关键字,只看 PDO 返回结果
- TP8:在执行前解析 SQL 开头(如
trim($sql)后取前 6 个字符),匹配SELECT、SHOW等白名单,否则拒绝执行 - 错误示例:
Db::query(" UPDATE user SET name = ? WHERE id = ?", [1, 2])→ TP8 报SQL type not allowed for query()
占位符支持升级:TP8 全面支持问号绑定,但命名占位符必须用 :key 形式
TP5 不支持问号占位符自动绑定,TP8 原生支持且强制推荐。但注意:? 是位置绑定,:key 是命名绑定,二者不能混用;且命名绑定的键名必须与参数数组键完全一致,大小写敏感。
- ✅ 正确(问号):
Db::query("SELECT * FROM user WHERE status = ? AND name LIKE ?", [1, "%{$kw}%"]) - ✅ 正确(命名):
Db::execute("UPDATE user SET content = :content WHERE id = :id", ['content' => $text, 'id' => $id]) - ❌ 错误(混用):
Db::query("SELECT * FROM user WHERE id = ? AND status = :status", [1, 'active'])→ 第二个参数是索引数组,但 SQL 含命名占位符 - ❌ 错误(大小写):
['ID' => 8]对应:id→ 绑定失败,PDO 报Invalid parameter number
表名/字段名/ORDER BY 动态拼接必须过白名单,TP8 不再容忍 raw 字符串注入
TP5 中还能靠 Db::raw() 或手动拼接绕过校验,TP8 在 Db::query()/Db::execute() 内部对所有非值参数做了强过滤。任何出现在 SQL 字符串中、未通过白名单校验的变量,都会被替换为空字符串或触发异常。
- 动态表名必须显式校验:
in_array($table, ['user', 'order', 'log']) ?: throw new InvalidArgumentException('Invalid table') - 字段名同理:
$field = in_array($field, ['id', 'name', 'create_time']) ? $field : 'id'; $sql = "SELECT * FROM user ORDER BY {$field} DESC"; -
__USER__表前缀语法依然可用,但仅限字面量写法,不能用于变量拼接:"SELECT * FROM __USER__ WHERE id = ?"✅,"SELECT * FROM {$prefix}user"❌
事务中执行原生 SQL 必须确保可回滚,TP8 对非事务语句更敏感
TP8 的事务管理器会对原生 SQL 做额外识别:如果检测到 DROP、CREATE TEMPORARY TABLE、SET @var= 等 MySQL 非事务语句,会在 Db::commit() 前主动抛出异常,阻止事务提交,避免“部分成功”状态。
- ✅ 安全操作:
Db::execute("UPDATE user SET updated_at = NOW() WHERE id = ?", [$id]) - ❌ 静默拦截:
Db::execute("CREATE TEMPORARY TABLE tmp_log AS SELECT * FROM log WHERE time > ? ", [$time])→ TP8 抛NonTransactionalStatementException - 调试建议:开启
'SHOW_SQL' => true,查Db::getLog()看真实执行链路,别依赖_fetchSql(true)—— 它在 TP8 中已弃用
最易被忽略的是:TP8 默认关闭了 PDO 的 ATTR_EMULATE_PREPARES,这意味着某些老式 DDL(如带子查询的 ALTER TABLE ... ADD COLUMN ... DEFAULT (SELECT ...))会直接报错,必须显式启用模拟预处理或改用分步语句。











