Hyperf原生SQL必须用Db::select()或Db::update()配合参数数组绑定,严禁字符串拼接;Db::raw()仅用于固定SQL片段,动态字段须白名单校验;事务内SQL同样需绑定;上线前须确认PDO::ATTR_EMULATE_PREPARES为false。

Hyperf 原生 SQL 必须用 Db::select() 或 Db::update() 配合参数数组
Hyperf 不会自动帮你绑定变量,Db::select() 第二个参数为空或缺失,就等于没防护。哪怕只拼一个 $id,也会直接执行恶意语句。
- 错误写法:
Db::select("SELECT * FROM user WHERE id = {$id}")—— 完全跳过预处理,$id里塞1 OR 1=1就查全表 - 正确写法(问号占位):
Db::select("SELECT * FROM user WHERE id = ? AND status = ?", [$id, $status]) - 更推荐(命名占位):
Db::select("SELECT * FROM user WHERE id = :id AND name = :name", ['id' => $id, 'name' => $name]),重复用同一变量时不易错位 - 整数也必须走绑定:
Db::select("SELECT * FROM order WHERE amount > ?", [$minAmount]),别信“int 就安全”,协程下类型可能被污染
Db::raw() 不是变量容器,只放固定 SQL 片段
Db::raw() 的作用是绕过转义、原样插入,它不参与参数绑定。把它当“安全壳”包用户输入,反而最危险。
- 高危写法:
Db::select("SELECT * FROM user WHERE name = " . Db::raw("'{$name}'"))—— 外层仍是字符串拼接,Db::raw()没起任何作用 - 安全写法:
Db::select("SELECT * FROM user WHERE created_at > " . Db::raw("DATE_SUB(NOW(), INTERVAL 7 DAY)")),里面是硬编码函数,无用户输入 - 动态字段要白名单校验后才进
Db::raw():$field = in_array($request->input('sort'), ['id', 'created_at'], true) ? $request->input('sort') : 'id'; Db::orderBy(Db::raw($field), 'DESC');
事务内原始 SQL 同样要绑定,别因“加了事务”放松警惕
事务保证原子性,不防注入。资金类操作常走事务,一旦漏绑,危害反而更大。
MySQL 数据库管理技能,能够通过自然语言查询和管理数据库,支持 SELECT/INSERT/UPDATE/DELETE、表管理、备份恢复等操作。当用户提及 MySQL、数据库查询、建表或数据备份时使用此技能。
- 错误示范:
Db::transaction(function () use ($uid, $amount) { Db::update("UPDATE account SET balance = balance + {$amount} WHERE uid = {$uid}"); }); - 正确做法:
Db::transaction(function () use ($uid, $amount) { Db::update("UPDATE account SET balance = balance + ? WHERE uid = ?", [$amount, $uid]); }); - 注意:事务闭包里所有
Db::*调用都得遵守绑定规则,包括Db::insert()、Db::delete()
上线前必须确认 PDO::ATTR_EMULATE_PREPARES 为 false
Hyperf 默认启用模拟预处理(true),在某些 MySQL 版本下会导致绕过绑定,让 ? 占位被客户端拼接回 SQL 字符串。
- 检查方式:
var_dump(\Hyperf\Database\Connection::getPdo()->getAttribute(\PDO::ATTR_EMULATE_PREPARES));,应输出bool(false) - 强制关闭(配置中):
'options' => [PDO::ATTR_EMULATE_PREPARES => false]加到数据库连接配置的options项 - 影响:设为
false后,MySQL 服务端真正做预编译,但要求 MySQL 版本 ≥ 5.1.17,且部分旧驱动可能报错
真正难的不是写对一行 Db::select(),而是所有入口——分页参数、排序字段、搜索关键词、回调钩子里的 SQL —— 全部统一走绑定+白名单。漏掉任意一个动态点,防护就形同虚设。


















