db::select()等方法支持?位置占位符和:name命名绑定,参数自动转义防注入;但表名、字段名等不可绑定,须白名单校验;db::statement()不支持绑定,ddl操作应避免拼接用户输入。

DB::select() 里用 ? 占位符绑定参数
直接写原生 SQL 时,DB::select() 支持位置占位符 ?,参数以数组形式传入,顺序必须严格对应。Laravel 会自动转义并防止 SQL 注入。
常见错误是把参数拼进 SQL 字符串里(比如 "WHERE id = $id"),这完全绕过绑定机制,极其危险。
- 正确写法:
DB::select("SELECT * FROM users WHERE status = ? AND created_at > ?", ['active', '2024-01-01']) - 错误写法:
DB::select("SELECT * FROM users WHERE status = '$status'")—— 变量未转义,可能被注入 - 注意:? 只能代入值,不能代入表名、字段名或 ORDER BY 子句;这些必须白名单校验后硬编码或用
Str::of()+in_array()过滤
DB::insert/update/delete 也支持 ? 和命名绑定
DB::insert()、DB::update()、DB::delete() 同样接受参数数组,规则和 DB::select() 一致。命名绑定(:name)在这些方法里也有效,可读性更好,尤其参数多时。
- 位置绑定:
DB::update("UPDATE users SET name = ? WHERE id = ?", ['John', 123]) - 命名绑定:
DB::update("UPDATE users SET name = :name WHERE id = :id", ['name' => 'John', 'id' => 123]) - 命名绑定的键名必须带冒号前缀(
:name),但传参数组里不用写冒号 —— Laravel 内部处理,别写成[':name' => 'John'] - 执行失败时抛出
QueryException,不是静默忽略;建议用 try/catch 捕获并记录$e->getMessage()
DB::statement() 不支持参数绑定,慎用
DB::statement() 用于执行不返回结果集的语句(如 CREATE、ALTER、TRUNCATE),但它**不解析占位符**,参数数组会被直接忽略。传进去的 SQL 是“原样执行”,没有任何绑定或转义。
- 危险示例:
DB::statement("DROP TABLE {$table}", [$table])—— 第二个参数完全无效,$table若来自用户输入,直接导致库级破坏 - 安全做法:这类 DDL 操作必须用白名单控制表名,例如
in_array($table, ['users', 'posts'], true)校验后再拼接 - 如果真要动态建表且参数不可控,改用 Schema Builder(
Schema::create())更可靠
复杂查询建议用 Query Builder 而非原生 SQL 绑定
当 SQL 包含 JOIN、子查询、UNION 或条件分支较多时,硬写原生 SQL + 手动绑定容易漏参数、错顺序,调试困难。Query Builder 的 where()、whereIn() 等方法底层自动绑定,语义清晰且类型安全。
- 比如动态状态过滤:
DB::table('users')->when($status, fn ($q) => $q->where('status', $status))->get()—— $status 自动绑定,无需手动管理 ? - IN 查询注意:
whereIn('id', [1, 2, 3])生成的占位符数量匹配数组长度,而原生 SQL 写WHERE id IN (?, ?, ?)必须手动同步占位符个数 - 性能上无差异:Query Builder 最终也编译为带绑定的 PDO 语句,不会拼字符串
参数绑定本身很简单,难的是判断“哪里该用”和“哪里根本不能用”。表名、排序字段、UNION 分支这些,绑定机制天生不支持,靠文档或报错提示都晚了,得从设计阶段就卡死输入来源。











