Db::query()只用于SELECT等只读语句,返回二维数组或空数组;Db::execute()用于INSERT等写操作,返回影响行数。二者混用会导致数据未查出或未更新。参数绑定推荐命名占位符。动态字段需白名单校验后拼接。MySQL8.0下需开启PDO模拟预处理。事务中禁止执行DDL语句。
ThinkPHP8.0原生SQL执行与查询方法
作者:技术拾光
时间:2026-07-05
浏览:185
文章目录
Db::query()专用于SELECT等只读语句,返回二维数组;Db::execute()专用于INSERT/UPDATE等写操作,返回影响行数;二者语义严格区分,混用将导致数据未查出、未更新或静默失败。

在ThinkPHP8.0的数据库操作中,Db::query()和Db::execute()这两个方法常被混淆。实际上,它们有严格的用途区分:若用query()执行修改数据的SQL,或使用execute()查询数据列表,通常会导致查询无结果、修改无效,甚至静默失败。在读写分离的环境下,还会将SQL语句发送到错误的数据库。
查询数据必须使用Db::query(),不可用于UPDATE
query()仅处理产生结果集的语句:SELECT、SHOW、EXPLAIN、WITH。它返回一个二维数组,即使无数据也会返回空数组[]而非null。若执行失败,则返回false。
- 错误示例:
Db::query("UPDATE user SET status = 1 WHERE id = ", [123])——该写法不会报错,但数据不会被更新,因为SQL未进入写库流程。 - 正确示范:
Db::query("SELECT * FROM user WHERE status = :status", ['status' => 1])。使用命名占位符绑定参数,可读性和维护性更高。 - TP8也支持问号绑定,但
的顺序必须与数组值严格对应,否则匹配会出错。建议优先使用命名绑定(:param)。 - 调试时,
Db::getLastSql()仅记录最近一次调用,若要获取正确的SQL,必须确保它紧跟在query()或execute()之后。
修改数据必须使用Db::execute(),不可用于查询
execute()仅处理无结果集的写操作:INSERT、UPDATE、DELETE、REPLACE、TRUNCATE。它返回受影响的行数(整数),执行失败时返回false。
- 踩坑示例:
$list = Db::execute("SELECT * FROM user"); foreach($list as ...)——此处$list实际是一个数字(如0或1),对数字执行foreach直接导致报错。 - 正确做法:
$affected = Db::execute("UPDATE user SET name = WHERE id = ", [$name, $id]); if ($affected === false) { /* 处理失败 */ }。先判断返回值是否为false,再继续后续逻辑。 - 若需获取最后插入的自增ID,不能依赖
execute()的返回值,需额外调用Db::getLastInsID()。 - 批量插入数据时,建议使用
Db::name('user')->insertAll($data)。这比手写原生INSERT更稳定,且能自动处理SQL长度超限问题。
动态字段/表名不能绑定,必须经白名单校验后拼接
一个容易忽略的点::table或:order_field这类占位符在PDO预处理器中不会被真正替换,框架会将其视为普通字符串字面量,或直接报语法错误。
- 错误写法:
Db::query("SELECT * FROM :table WHERE id = ", [$tableName, $id]) - 正确做法:先建立白名单数组,例如
['id', 'name', 'create_time'],校验$sortField是否在其中。校验通过后手动拼接:"ORDER BY {$sortField} DESC"。 - 表前缀可用
__USER__写法,由框架自动替换。但仅适用于静态写死的场景。若表名为变量,仍需白名单加手动拼接。 - 编写LIKE模糊查询时,将通配符放入绑定值中:
['name' => "%{$keyword}%"],切勿将通配符直接写到SQL字符串里。
MySQL 8.0+下原生SQL报错?通常是PDO预处理未开启模拟
使用MySQL 8.0及以上版本时,执行原生SQL偶尔会报SQLSTATE[HY000]: General error: 2034错误。原因是TP8默认未启用PDO::ATTR_EMULATE_PREPARES => true,而MySQL 8.0+对原生预处理的要求更严格。
- 解决办法:在
config/database.php对应的MySQL连接配置中显式添加:'options' => [PDO::ATTR_EMULATE_PREPARES => true]。 - 重要注意事项:DDL语句(如CREATE TEMPORARY TABLE、ALTER TABLE)不支持事务回滚,禁止在
Db::startTrans()块内执行此类操作。 - 事务中所有原生操作,必须包裹在
try-catch中,同时捕获think\db\exception\DataNotFoundException和底层的PDO异常。 - 即使只执行
Db::query(),只要未抛异常,框架就认为“一切正常”,不会自动中断事务。回滚与否完全取决于在catch块中是否主动throw或调用rollback()。
真正容易被忽略的是:事务边界和语句兼容性,从来不是靠框架自动兜底的。一句SET @var = 1或DROP TABLE写入,即使外面套了事务,rollback()也形同虚设。
实用工具推荐
电子包浆
使用 ›绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
图片压缩
使用 ›选择画质和输出格式,预览压缩前后的大小。
相关文章