Db::query()仅用于SELECT/SHOW/EXPLAIN,返回二维数组;Db::execute()专用于INSERT/UPDATE/DELETE,返回影响行数;混用会导致静默失败、逻辑错误。

Db::query() 和 Db::execute() 必须分开用,混用会静默失败——查数据返回空数组还继续 foreach,改数据返回数字却当数据用了。
Db::query() 只能执行 SELECT/SHOW/EXPLAIN
它走 PDO 的 execute() + fetchAll(),结果自动转成二维数组,适合模板 volist 或 PHP 遍历。但一旦拿它执行 UPDATE 或 INSERT,语句其实发给了数据库,只是没返回数据,你拿到的是空数组 [],后续 foreach 直接报错。
- 正确:
Db::query("SELECT * FROM user WHERE status = ? AND name LIKE ?", [1, "%{$kw}%"]) - 错误:
Db::query("UPDATE user SET name = 'abc' WHERE id = 8")—— 不报错,但返回[],逻辑断在下游 -
LIKE的通配符必须拼在 PHP 变量里:["%{$kw}%"]✅,不能写成["%?%"]❌ - TP5 不支持问号占位符自动绑定(TP6 才原生支持),所以必须用
Db::query($sql, $params)形式,底层靠PDO::prepare()防注入
Db::execute() 专用于 INSERT/UPDATE/DELETE/REPLACE
它调的是 PDO 的 exec(),只返回影响行数(int)或 false,不返回任何数据。这是写操作的唯一合法入口。常见翻车是拿它查数据,比如 Db::execute("SELECT * FROM log"),结果返回 0 或 1,接着 foreach 就崩了。
- 正确:
Db::execute("UPDATE user SET age = ? WHERE id = ?", [25, 8]) - 错误:
Db::execute("SELECT id FROM user LIMIT 10")—— 返回数字,不是数组 - 要获取插入 ID?
Db::execute()不提供,得额外调Db::getLastInsID() - 批量插入别硬写
VALUES (),(),(),优先用Db::name('user')->insertAll($data),避开 SQL 长度限制和手动拼接风险
参数绑定只认值,表名/字段名/ORDER BY 必须白名单校验后拼接
ThinkPHP 的占位符(? 或 :status)只做 PDO 参数绑定,不解析为标识符。写成 "SELECT * FROM :table" 或 "ORDER BY :field" 会直接报错或把占位符当字符串字面量处理。
立即学习“PHP免费学习笔记(深入)”;
- 值绑定安全:
Db::query("SELECT * FROM user WHERE id = ? AND status = :status", [8, 'active']) - 动态表名?不行:
Db::query("SELECT * FROM :table WHERE id = ?", [$tableName, 8])❌ - 动态排序字段?先校验:
in_array($sortField, ['id', 'name', 'create_time']) ? "ORDER BY {$sortField} DESC" : "ORDER BY id DESC" - 表前缀可用
__USER__,框架自动替换,但依然不能用于变量拼接场景
事务中执行原生 SQL 得手动兜底,否则 rollback 可能失效
TP5 的 Db::startTrans() 不自动拦截原生 SQL 的事务兼容性问题。像 CREATE TEMPORARY TABLE、SET @var=1、DROP TABLE 这些语句本身就不进事务,即使包在 startTrans() 里,rollback() 也完全无效。
- 所有原生操作必须严格落在
Db::startTrans()和Db::commit()/Db::rollback()之间 - 每个
Db::query()或Db::execute()都要检查返回值,或用try-catch捕获think\db\exception\DataNotFoundException和底层 PDO 异常 - 禁用在事务块中执行 DDL(如
ALTER TABLE)、变量赋值、临时表等非事务语句 - 调试时可调
Db::getLastSql()看最后执行的 SQL,但它只记录最近一次,不是实时日志,且 TP5 中需先执行再调用才有效
最易被忽略的是:TP5 原生 SQL 不支持问号位置绑定的语法糖(那是 TP6 加的),所有参数必须显式传入第二个数组参数;另外,Db::query() 和 Db::execute() 在读写分离部署下会固定走不同库,和 SQL 内容无关——哪怕你 execute("SELECT ..."),它也发往写库。



















