Laravel中执行原生SQL必须按语句类型选用对应方法:DB::select()仅支持SELECT并返回对象数组;INSERT/UPDATE/DELETE需用DB::insert()等专用方法或DB::statement();多语句或会话变量须用DB::unprepared()并手动事务管理。

直接执行原生 SQL 在 Laravel 中完全可行,但必须分清语句类型选对方法,否则会静默失败、报错或破坏事务一致性。
DB::select() 只能查,不能改数据
很多人误以为 DB::select() 是“万能入口”,其实它底层调用的是 PDO::query() + fetchAll(),仅支持 SELECT。传入 INSERT 或 UPDATE 不会报错,但无任何效果——尤其在 MySQL 的严格模式关闭时更难察觉。
-
DB::select()返回stdClass对象数组,不支持FETCH_ASSOC模式切换 - 支持
?占位符和命名绑定(如:id),但命名绑定需显式传入关联数组:['id' => 1] - IN 子句不能写成
WHERE id IN (?),必须手动拼占位符:DB::select("SELECT * FROM users WHERE id IN (" . implode(',', array_fill(0, count($ids), '?')) . ")", $ids) - 日期字符串建议先用
Carbon::parse($date)->toDateTimeString()格式化,避免时区差异导致查询遗漏
写操作必须用对应方法或 DB::statement()
DB::insert()、DB::update()、DB::delete() 都返回影响行数(int)或布尔值,语义明确;而 DB::statement() 是兜底方案,等价于 PDO::exec(),适用于 DDL、TRUNCATE、存储过程调用等不返回结果集的语句。
-
DB::insert()插入单条且要主键:用DB::table()->insertGetId()更方便;批量插入则用DB::insert()配合二维数组 -
DB::statement('TRUNCATE TABLE posts')不能用DB::delete()替代——后者生成DELETE FROM,不重置自增 ID -
DB::statement()不返回数据,哪怕你写SELECT 1也会抛出异常 - 所有写操作都不自动参与 Eloquent 的延迟加载或批量优化,性能边界需自行评估
多语句、变量、存储过程得用 DB::unprepared() + 手动事务
像 SET @var = 1; SELECT @var; 这类带会话变量或多语句脚本,DB::select() 和 DB::statement() 都不支持。必须用 DB::unprepared(),但它禁用预处理模拟(PDO::ATTR_EMULATE_PREPARES = false),且必须包裹在事务中手动控制生命周期。
- 启用前确认数据库连接配置已设
'options' => [PDO::ATTR_EMULATE_PREPARES => false] - 必须显式调用
DB::beginTransaction()→DB::unprepared()→DB::commit()或DB::rollback() -
DB::transaction()闭包内不能用DB::unprepared(),因为它绕过 Laravel 的异常捕获机制,无法自动回滚 - MySQL 存储过程调用如
CALL sp_update_stats()必须走DB::unprepared(),且注意输出参数需在 PDO 层级处理
事务里混用原生 SQL 和 Eloquent 查询要小心
原生 SQL 默认不参与 Eloquent 的事务管理,除非显式包裹。但即使用了 DB::transaction(),Eloquent 模型的事件(creating、saving)和观察者也不会触发。
- 在事务闭包中,
DB::select()和DB::update()共享同一连接,读写一致,但DB::table()->where()->update()这类查询构造器语句也安全 - 若需获取最后插入 ID,
DB::insert()不返回 ID,得用DB::getPdo()->lastInsertId() - 跨连接事务(如同时操作 MySQL 和 PostgreSQL)不被支持;
DB::transaction()只作用于当前默认连接 - 死锁重试次数可传参:
DB::transaction(function () { ... }, 5),但重试期间原生 SQL 会重复执行,需确保幂等
最易被忽略的一点:命名绑定在 DB::select() 中可用,但在 DB::statement() 和 DB::unprepared() 中实际被忽略——它们只认 ? 占位符,拼错就直接 SQL 错误。


















