laravel调用含out参数的mysql存储过程需分两步:先db::unprepared('call sp(@var)'),再db::select('select @var');db::select()直接调用会报错,因pdo默认不支持多结果集且laravel不处理输出参数绑定。

直接用 DB::select() 调用带 OUT 参数的存储过程会报错
MySQL 存储过程如果有 OUT 或 INOUT 参数,Laravel 默认的 DB::select() 无法获取返回值,会抛出 SQLSTATE[HY000]: General error 或直接忽略输出参数。这是因为 PDO 默认不启用存储过程多结果集支持,且 Laravel 的查询构造器不处理变量绑定中的输出参数。
- 必须用
DB::statement()或原生 PDO 执行CALL语句 - 若需读取
OUT变量,得先在 SQL 层用@var_name暂存,再单独查一次SELECT @var_name - 确保数据库连接配置中启用了
PDO::ATTR_EMULATE_PREPARES => true(尤其 MySQL 8.0+),否则CALL可能被拦截
DB::unprepared() 是最简方式执行无返回结果的存储过程
适用于只做数据变更、不关心返回值的场景,比如日志归档、批量更新。它绕过 PDO 预处理,直接发送原始 SQL 到服务端。
DB::unprepared('CALL sp_archive_old_logs(30)');
- 不能传参,所有参数必须拼进字符串 —— 注意 SQL 注入风险,务必对输入做
intval()或Str::slug()等强校验 - 不返回结果集,也不检查执行是否成功(失败只抛异常)
- 比
DB::statement()更底层,适合确定无交互的“fire-and-forget”调用
需要返回结果集?用 DB::select() + CALL 但必须禁用预处理
如果存储过程本身有 SELECT 输出(比如返回统计行),且你用的是 MySQL,可以这样写:
DB::select('CALL sp_get_user_stats(?)', [123]);
- 前提是数据库连接配置里加了
'options' => [PDO::ATTR_EMULATE_PREPARES => true] - 不能混用命名占位符(
:id)和位置占位符(?),MySQL 存储过程只认位置绑定 - 若过程内含多个
SELECT,Laravel 只取第一个结果集;后续结果会被丢弃,除非改用原生 PDO 处理多结果集
要读取 OUT 参数?只能分两步:CALL + SELECT @var
这是最常被忽略的坑 —— Laravel 没有封装对 MySQL 用户变量的支持,必须手动拆解。
DB::unprepared('SET @out_count = 0');
DB::unprepared('CALL sp_count_active_users(@out_count)');
$count = DB::select('SELECT @out_count as count')[0]->count;
-
@out_count是会话级变量,同一数据库连接内有效,换连接就失效 - 不要在事务里跨多次
DB::transaction()调用依赖同一个@var,因为事务可能复用连接也可能新建 - 高并发下慎用全局变量名,建议加前缀如
@sp_user_count_123避免冲突
DB::unprepared();真要拿 OUT 值,就得接受这种两步写法 —— 没有更短的路径。











