db::listen()是最轻量可控的sql监听方式,需在appserviceprovider::boot()中注册,生产环境用app()->environment('local')包裹;db_log_queries仅对sqlite/mysql有效且需配置日志通道;enablequerylog()只记录当前连接查询,须及时获取;tosql()仅返回带?的模板语句。

开启 Laravel 的 DB::listen() 监听 SQL
开发时想实时看到每条 SQL 怎么发出去的,DB::listen() 是最轻量、最可控的方式。它不依赖日志文件,也不影响生产环境配置,适合调试阶段快速验证查询逻辑。
常见错误是直接在控制器里写监听但没注意生命周期——它只对后续执行的查询生效,且每次请求需重新注册。
- 把监听代码放在服务提供者(如
AppServiceProvider::boot())里,确保每次请求都注册 - 监听回调中别做耗时操作(比如写文件或发 HTTP 请求),否则拖慢响应
- 生产环境务必用
app()->environment('local')包裹,避免意外泄露 SQL
DB::listen(function ($query) {
\Log::info($query->sql, $query->bindings);
});
Laravel 日志里查不到 SQL?检查 DB_LOG_QUERIES 和日志通道
很多人开了 DB_LOG_QUERIES=true 却没在 storage/logs/laravel.log 里看到 SQL,问题往往不在开关本身,而在日志通道配置和驱动兼容性上。
这个环境变量只对 sqlite 和 mysql 驱动有效,pgsql 不支持;而且它依赖 Laravel 的日志系统是否把 database 通道路由到了实际输出位置。
- 确认
.env中设置了DB_LOG_QUERIES=true,且运行了php artisan config:clear - 检查
config/logging.php中channels.stack是否包含database通道 -
DB_LOG_QUERIES在 Laravel 10+ 才正式支持,旧版本需手动监听或换方案
用 DB::enableQueryLog() 查当前请求的 SQL(含绑定参数)
适合在某个接口里临时抓取全部 SQL,比如排查 N+1 或重复查询。它记录的是内存中的查询快照,不写磁盘,开销小,但只对当前连接生效。
容易踩的坑是调用时机不对:必须在查询前启用,且 DB::getQueryLog() 要在所有查询结束后立刻取,否则中间其他地方的查询会混进来。
- 启用后记得关闭(
DB::disableQueryLog()),尤其在命令行或长生命周期脚本中 - 返回结果里的
bindings是原始值,sql字符串里仍是 ? 占位符,需手动替换才能看完整语句 - 不适用于读写分离场景——只记录主连接的查询,从库查询不会出现
DB::enableQueryLog();
User::where('active', 1)->get();
$queries = DB::getQueryLog(); // 立即取
为什么 DB::table()->toSql() 显示的 SQL 没参数?
toSql() 只生成带 ? 占位符的模板语句,不是真实执行的 SQL。它本质是构建器的“编译”结果,不触发查询,也不解析 bindings。
这在写单元测试或调试复杂 where 条件时很有用,但不能代替日志看实际执行内容。如果真想看带值的 SQL,得自己拼接:
- 用
str_replace(['?', '?'], $bindings, $sql)简单替换(仅当绑定值不含 ? 时安全) - 更稳妥的做法是用
DB::listen()回调里的$query->sql和$query->bindings,Laravel 已做了转义处理 -
toSql()对子查询、JSON 字段操作等可能输出不完整,别依赖它判断最终执行逻辑
SQL 日志最麻烦的从来不是怎么开,而是关——忘了关 DB::listen() 或 enableQueryLog(),上线后查一次接口就打几百条日志到磁盘,或者把敏感字段直接记进日志文件。动手前先想清楚:这次要查的是哪一层?是构造逻辑、执行路径,还是真实参数?选错方式,后面花的时间比写业务还多。










