thinkphp执行原生sql必须严格区分:查数据用db::query(),返回结果集;改数据用db::execute(),返回影响行数;混用会导致逻辑错乱、静默变更或遍历报错。

ThinkPHP 中执行原生 SQL,核心就两条路:查数据用 Db::query(),改数据用 Db::execute()。混用不仅逻辑错乱,还容易导致数据静默变更或遍历报错。
查数据必须用 Db::query()
它专为 SELECT、SHOW、EXPLAIN 这类返回结果集的语句设计,自动转成 PHP 数组,可直接 foreach 或传给模板渲染。
- 写法示例:
Db::query('SELECT id, name FROM user WHERE status = ? AND created_at > ?', [1, '2026-01-01']) - 命名占位符更清晰:
Db::query('SELECT * FROM user WHERE role = :role', ['role' => 'admin']) - 别用问号占位符拼字段或表名,比如
ORDER BY ?不生效;动态字段需白名单校验后手动拼接 - 跨库查询唯一可行方式:手写全库名 SQL,如
SELECT u.id FROM db1.user u JOIN db2.order o ON u.id = o.user_id
改数据必须用 Db::execute()
INSERT、UPDATE、DELETE、TRUNCATE 等写操作,必须走这个方法。它不返回数据,只返回影响行数(整数),用于判断是否成功执行。
- 示例:
Db::execute('UPDATE user SET last_login = NOW() WHERE id = ?', [123]) - 批量插入也支持:
Db::execute('INSERT INTO log (uid, action) VALUES (?, ?), (?, ?)', [1,'login',2,'logout']) - 误用 Db::query 执行 UPDATE,会返回空数组
[],但 SQL 实际已执行——极易造成“以为没改”的假象 - 要获取新增 ID,不能靠 execute 返回值,得额外调
Db::getLastInsID()
安全绑定是底线,不是可选项
所有用户可控的值,必须用占位符;表名、字段名、排序方向等结构化部分,绝不能由用户输入直接拼接。
- TP6 原生支持问号和命名占位符,底层走 PDO 预处理,安全可靠
- 表名动态拼接必须白名单校验:
$allowed = ['user', 'order', 'log']; if (!in_array($table, $allowed)) { throw new \Exception('非法表名'); } - LIKE 模糊查询注意通配符位置:
['%' . $kw . '%'],不能写成?%,PDO 不识别 - 禁止字符串拼接 SQL:
"WHERE name = '" . $name . "'"是高危写法,$name 含' OR 1=1 --就会注入
事务与特殊场景要留心
在事务中执行原生 SQL,要注意某些语句(如 CREATE TEMPORARY TABLE、ALTER TABLE)可能不支持回滚,导致事务一致性被破坏。
- 临时表适合复杂跨库只读场景,但需注意权限、命名冲突和及时清理
- DDL 语句(如 ALTER)在部分 MySQL 版本可能被 PDO 默认禁用,需检查
PDO::ATTR_EMULATE_PREPARES设置 - 读写分离环境下,query 固定走读库,execute 固定走写库,不用额外指定
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











