查数据必须用db::query(),改数据必须用db::execute();混用会导致返回值错乱、sql静默走错库、数据不生效却无报错。

查数据必须用 Db::query(),改数据必须用 Db::execute();混用会导致返回值错乱、SQL静默走错库(读写分离下尤其致命),甚至线上数据不生效却无报错。
Db::query() 只能执行 SELECT/SHOW/EXPLAIN
它专为有结果集的语句设计,返回二维数组(空结果是 []),失败返回 false。底层强制路由到读库,哪怕你写了 UPDATE 也不会执行写逻辑。
- 正确用法:
Db::query("SELECT * FROM user WHERE status = :status", ['status' => 1]) - 错误写法:
Db::query("UPDATE user SET name = ? WHERE id = ?", [$name, $id])—— 看似没报错,实际 SQL 根本没发往写库,数据不变 -
Db::getLastSql()必须紧跟在query()后调用才有效,否则取到的是上一次操作的 SQL - MySQL 8.0+ 下报错
SQLSTATE[HY000]?大概率是 PDO 预处理未开启模拟模式,需手动配置PDO::ATTR_EMULATE_PREPARES => true
Db::execute() 专用于 INSERT/UPDATE/DELETE
它只返回影响行数(int),不返回数据。底层强制走写库,且不支持 SELECT 类语句——即使语法合法,也会因无结果集而返回数字,后续 foreach 直接崩溃。
- 正确用法:
$affected = Db::execute("UPDATE user SET updated_at = NOW() WHERE id IN (?)", [$ids]) - 错误写法:
$list = Db::execute("SELECT id,name FROM user"); foreach($list as ...)——$list是整数,不是数组 - 要获取插入 ID,不能依赖
execute()返回值,得额外调Db::getLastInsID() - 批量插入建议优先用
Db::name('user')->insertAll($data),比手写原生INSERT更安全、防超长 SQL 截断
动态表名/字段名必须白名单校验后拼接
:table 或 :order_field 这类占位符不会被替换,PDO 会当成字符串字面量或直接报语法错误。ThinkPHP 的绑定机制只认「值」,不认结构。
- 错误写法:
Db::query("SELECT * FROM :table WHERE id = ?", [$tableName, $id]) - 正确做法:先定义白名单
$allowedTables = ['user', 'order', 'log'],再校验in_array($tableName, $allowedTables),最后拼接"SELECT * FROM {$tableName} WHERE ..." - 表前缀可用
__USER__,但仅限静态写法;变量拼接场景下仍需白名单 + 手动拼接 - LIKE 模糊查询通配符必须包在绑定值里:
['name' => "%{$keyword}%"],别写进 SQL 字符串
事务中执行原生 SQL 容易回滚失效
框架事务靠 Db::startTrans() → Db::commit()/Db::rollback() 控制,但某些原生语句(如 CREATE TEMPORARY TABLE、SET @var=1、DROP TABLE)本身不参与事务,rollback() 对它们完全无效。
- 所有原生操作必须严格落在
startTrans()和commit()之间 - 每个
Db::query()/Db::execute()都要检查返回值,或用try-catch捕获think\db\exception\DataNotFoundException或 PDO 异常 - 避免在事务块中执行非事务性语句,否则看似成功提交,实则部分操作已不可逆
最常被忽略的一点:读写分离环境下,Db::query() 和 Db::execute() 的语义不仅是返回值差异,更是数据库节点路由指令——写错函数,等于把 UPDATE 发到了只读从库,连错误都不报。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











