原生sql在多表join聚合、大偏移分页、批量操作、json深度查询时明显更快;eloquent适合标准crud、关联预加载、用户输入简单查询及需模型事件的场景;混用时应分层策略并严守参数绑定。

原生 SQL 在复杂查询和大批量操作上普遍比 Eloquent 快 2–10 倍,但可维护性陡降;Eloquent 在标准 CRUD 和关联加载场景下开发快、不易出错,性能损耗通常可接受。
什么时候原生 SQL 明显更快
不是“所有查询都该换原生”,而是这几类场景差异显著:
- 多表 JOIN + 聚合 + 窗口函数(如
RANK() OVER (PARTITION BY category ORDER BY sales DESC))——Eloquent 无法生成等效语句,硬套withSum()或selectRaw()容易漏字段或绑定错位置 - 分页偏移极大(
LIMIT 10000, 20)——Eloquent 的skip()仍会执行全扫描,而原生 SQL 可配合游标(WHERE id > ? ORDER BY id LIMIT 20)跳过前 1w 行 - 批量更新/删除(万级记录)——
DB::update()或DB::statement('UPDATE ... WHERE id IN (?)')一次执行;Eloquent 的each()+save()是 N 次查询,还带模型事件开销 - JSON 字段深度查询(MySQL 的
JSON_EXTRACT(users.meta, '$.preferences.theme'))——Eloquent 不识别 JSON 路径语法,whereRaw()是唯一选择,但必须手动绑定参数
Eloquent 更稳的使用边界
别为了“看起来高级”去手写 SQL,以下情况坚持用 Eloquent 反而省事又安全:
- 单表增删改查 + 简单条件(
User::where('active', 1)->get())——生成的 SQL 干净,还能走查询缓存 - 一对多/多对多关联预加载(
Post::with(['author', 'tags'])->get())——避免 N+1,且自动处理反向约束(如软删除过滤) - 字段值来自用户输入但无需运算(
where('email', $request->email))——Eloquent 自动转义,DB::select()忘了绑定就直接注入 - 需要触发模型事件(
creating,saving,deleting)——原生 SQL 完全绕过这些钩子,业务逻辑可能断裂
参数绑定是原生 SQL 的生死线
写原生 SQL 时,90% 的问题出在没绑参或乱拼字符串。记住这三条铁律:
- 永远不用
DB::select("SELECT * FROM users WHERE name = '{$name}'")—— 单引号、双引号、花括号全是漏洞入口 - 问号占位符只用于值,不能用于字段名、表名、ORDER BY 子句:
DB::select('SELECT * FROM ? WHERE status = ?', [$table, $status])❌;正确做法是白名单校验后拼接:$table = in_array($table, ['users', 'posts']) ? $table : 'users'; - 命名绑定(
:status)比问号更易读,尤其参数超 3 个时:DB::select('SELECT * FROM users WHERE status = :status AND type IN (:types)', ['status' => 1, 'types' => ['admin', 'editor']])—— 注意IN的数组需展开为多个占位符,Laravel 不自动处理
混合策略才是真实项目里的常态
没人纯用 Eloquent,也没人全写原生 SQL。实际代码里常见的是分层混用:
- 主查询用 Eloquent(保证结构清晰、事件触发),聚合字段用
selectRaw()补充(如selectRaw('COUNT(orders.id) as order_count')) - 报表导出走原生
DB::select()(避免模型实例化开销),但条件组装仍用 Query Builder:DB::table('users')->where('created_at', '>=', $start)->toSql()拿 SQL 再补 raw 部分 - 高频更新场景用
DB::update(),但更新后立刻用 Eloquent 查一条(User::find($id))同步内存状态,避免缓存不一致
最常被忽略的一点:原生 SQL 查询结果默认是 stdClass 对象数组,不是 Eloquent 模型实例——拿不到访问器(getFullNameAttribute)、不会触发 casts、也不能调用 load()。如果后续还要做对象级操作,得手动 new 模型或用 fromQuery()(Laravel 11+)映射,否则容易在下游踩空指针。











