db::execute() 是执行 update 的唯一合法入口,必须用它替代 db::query();需显式判断返回值、绑定参数防注入、白名单校验动态标识符、批量更新优先用 case when 或 update() 方法。

Db::execute() 是唯一合法入口
执行 UPDATE 必须用 Db::execute(),不能用 Db::query()。后者只处理 SELECT/SHOW/EXPLAIN,拿它跑 UPDATE 语句不会报错,但返回空数组 [],后续 if 或 foreach 会误判逻辑——比如以为没更新成功,其实数据已经改了。
常见错误写法:Db::query("UPDATE user SET name = ? WHERE id = ?", [$name, $id])——SQL 发出去了,但你拿到的是 [],不是影响行数。
-
Db::execute()调的是 PDO 的exec(),只返回 int(影响行数)或false - 失败时返回
false,必须显式判断:if ($affected === false) { /* 处理错误 */ } - 要获取插入 ID?
Db::execute()不提供,得额外调Db::getLastInsID()
参数必须绑定,别拼字符串
直接拼接变量进 SQL 字符串是高危操作,比如 "UPDATE user SET name = '{$name}'",一旦 $name 是 ' OR 1=1 -- 就炸了。
ThinkPHP 6+ 支持问号或命名占位符,TP5 需手动转义(但推荐升级或绕过)。安全写法:
- 问号绑定(顺序敏感):
Db::execute("UPDATE user SET status = ? WHERE id = ?", [2, 123]) - 命名绑定(更清晰):
Db::execute("UPDATE user SET content = :content WHERE id = :id", ['content' => $text, 'id' => $id]) - LIKE 模糊查询:通配符必须包在值里,
['%'.$kw.'%']✅,不能写成"name LIKE '%?%'"❌
动态表名或字段名必须白名单校验
? 和 :param 只绑定值,不解析为标识符。写成 "UPDATE :table SET ..." 或 "ORDER BY :field" 会直接当字符串字面量处理,甚至报语法错误。
正确做法是先校验再拼接:
- 白名单定义:
$allowedTables = ['user', 'order', 'log']; - 校验后拼:
if (!in_array($table, $allowedTables)) { throw new \Exception('非法表名'); } - 拼接使用:
"UPDATE {$table} SET status = ? WHERE id = ?"
表前缀可用 __USER__ 静态写法让框架自动替换,但变量场景下仍需白名单+手动拼接。
批量更新别硬写 VALUES,优先走 CASE WHEN 或 update()
如果要按不同 ID 更新不同值(比如一批用户设不同积分),手写 UPDATE ... VALUES (),(),() 不现实,也容易超 SQL 长度限制。
推荐两种稳方案:
- 统一条件更新(如“所有 status=0 的订单改为 1”):
Order::update(['status' => 1], ['status' => 0]),注意空 WHERE 会清空全表,必须校验 - 差异化更新(每条记录值不同):
Db::execute()+ 原生CASE WHEN,ID 列表用array_column($data, 'id')提取,CASE 分支用array_map拼,且整个 SQL 必须用Db::execute()执行并绑定参数,禁止字符串插值
最易被忽略的点:MySQL 8.0+ 默认关闭模拟预处理(PDO::ATTR_EMULATE_PREPARES),原生 SQL 报错时先检查这个配置,不是 SQL 写错了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











