
直接执行原生 SQL 用 DB::select()、DB::insert() 等,不是 DB::raw()
很多人以为 DB::raw() 是用来“执行”原生 SQL 的,其实它只是个占位符,只在 Eloquent 或查询构造器里起作用(比如 whereRaw()),单独调用不会执行。真要跑一条 SELECT,得用 DB::select();增删改对应 DB::insert()、DB::update()、DB::delete()。
常见错误现象:DB::raw("SELECT * FROM users") 直接写在控制器里,页面没报错但也没数据——因为它根本没被执行。
-
DB::select()返回数组,每项是stdClass对象,字段名区分大小写(取决于数据库配置) - 参数必须用
?占位,不能拼字符串,否则有注入风险:DB::select("SELECT * FROM users WHERE id = ?", [$id]) - 如果 SQL 里有变量要插进去(比如表名、字段名),
DB::raw()不能用,得自己白名单校验后拼接——这是最容易被忽略的漏洞点
Eloquent 查询里混用原生表达式:优先选 whereRaw() 而不是 DB::raw()
DB::raw() 本身不执行,但在 whereRaw()、selectRaw()、orderByRaw() 这些方法里,它才真正参与 SQL 构建。这时候它负责绕过 Eloquent 的字段转义,让原生片段进最终 SQL。
使用场景:查 JSON 字段、调数据库函数(如 DATE(created_at))、复杂条件(如 WHERE a + b > 10)。
-
whereRaw()的第二个参数才是绑定值,别漏掉:->whereRaw("json_contains(tags, ?)", ['"php"']) -
selectRaw("COUNT(*) as cnt")和select("COUNT(*) as cnt")效果不同:后者会被当成字段名加引号,导致语法错误 - MySQL 和 PostgreSQL 对函数支持差异大,比如
ILIKE在 PG 里可用,MySQL 得用LOWER()配合LIKE
想查出模型实例?别用 DB::select(),走 fromRaw() 或 newQuery()->from()
如果你执行了原生 SELECT,又希望结果是 User 模型对象(带访问器、属性转换、关系加载能力),DB::select() 返回的纯数组没法自动转——它压根不经过 Eloquent 生命周期。
正确做法是让 Eloquent “接管”这个查询:用 fromRaw() 把原生表/子查询塞进 Eloquent 查询构造器,或者用 newQuery()->from() 手动指定来源。
-
User::fromRaw("(SELECT * FROM users WHERE status = ?) as u", [1])->get()—— 注意别漏as u别名,不然 MySQL 会报错 -
User::newQuery()->from(DB::raw("(SELECT id, name FROM users) as u"))->get()更灵活,适合动态构建 - 这种写法仍受模型的
$table、$casts、$appends影响,但关系预加载(with())可能失效,要看子查询是否包含外键字段
性能和事务边界:原生 SQL 默认不参与 Eloquent 的延迟加载和批量优化
Eloquent 的 with() 预加载、chunkById() 分批、cursorPaginate() 游标分页,这些优化都建立在它能解析查询结构的基础上。一旦用了 DB::select() 或复杂 fromRaw(),这些机制就断了。
更隐蔽的问题是事务:Eloquent 操作默认在当前 DB 连接事务里,但如果你在事务中混用 DB::insert() 和 User::create(),它们其实共享同一个连接,这点没问题;可一旦开了多个 DB 连接(比如读写分离),DB::connection('write') 必须显式指定,否则可能写到从库。
- 大批量插入别用
DB::insert()循环,改用DB::transaction()包裹 +DB::statement()执行INSERT ... VALUES (),(),() -
DB::statement()用于无返回结果的语句(CREATE、DROP、UPDATE),但它不返回影响行数,需要DB::affectingStatement() - 调试时看真实 SQL,别信
toSql()输出——它不展开绑定参数,也模拟不了fromRaw()的嵌套结构
事情说清了就结束。










