Db::query()仅执行SELECT/SHOW/EXPLAIN等查操作并返回二维数组,Db::execute()仅执行INSERT/UPDATE/DELETE等改操作并返回影响行数;二者不可混用,且原生SQL不记录在getLastSql()中。

Db::query() 只能查,不能改
查数据就用 Db::query(),它只认 SELECT、SHOW、EXPLAIN 这类带结果集的语句。底层走的是 PDO 的 execute() + fetchAll(),自动转成二维数组,直接丢给 foreach 或模板 volist 就行。
常见错误是拿它执行 UPDATE 或 INSERT:
-
Db::query("UPDATE user SET name = ? WHERE id = ?", [1, 8])—— 不报错,但返回空数组[],后续遍历直接崩 - TP5 不支持
"%?%"这种写法,通配符必须拼在 PHP 变量里:["%{$kw}%"]✅,["%?%"]❌ - 参数绑定只防值注入,表名、字段名、
ORDER BY字段必须白名单校验后手动拼接,"SELECT * FROM :table"会当字面量处理,直接出错
Db::execute() 只能改,不能查
增删改、清空表、建表这些不返回数据的操作,必须用 Db::execute()。它调的是 PDO 的 exec(),成功返回影响行数(int),失败返回 false。
典型翻车场景:
立即学习“PHP免费学习笔记(深入)”;
-
Db::execute("SELECT * FROM log LIMIT 10")—— 返回0或1,不是数组,foreach立刻报错 -
Db::execute("INSERT INTO user (name) VALUES (?)", ["abc"])—— 返回1,要获取插入 ID 得额外调Db::getLastInsID() - 批量插入别硬写
VALUES (),(),(),优先用Db::name('user')->insertAll($data),避开 SQL 长度限制和拼接风险
读写分离下 query/execute 自动路由
如果你开了读写分离,Db::query() 永远走读库,Db::execute() 永远走写库——不管 SQL 本身是什么类型。这意味着:
-
Db::query("DELETE FROM log")虽然语法上是写操作,但会被发到读库,大概率报错或被拒绝 -
Db::execute("SELECT SLEEP(1)")会被发到写库,可能拖慢主库,还浪费连接 - DDL 语句如
ALTER TABLE在 MySQL 8.0+ 默认可能被 PDO 禁用,得确认PDO::ATTR_EMULATE_PREPARES设置是否允许
想看刚执行的 SQL?别信 getLastSql() 直接调
Db::getLastSql() 不是“本次执行的 SQL”,而是“上一次查询构建器或原生语句生成的 SQL”。你得先触发一次真实查询,再立刻取:
- 查构建器:先
$data = Db::table('user')->where('id', 8)->select(),再echo Db::table('user')->getLastSql() - 查原生 SQL:
Db::query("SELECT * FROM user WHERE id = ?", [8])后,Db::getLastSql()仍不会返回这句——它只记录构建器生成的 SQL,原生语句不进这个管道 - 调试原生 SQL 最靠谱方式是开 PDO 日志,或用数据库代理(如 ProxySQL)抓包
混用 query 和 execute 看似省事,实际最容易在类型判断上翻车:一个返回数组,一个返回数字,PHP 弱类型会悄悄转成 0 或空,下游逻辑就断了。



















