开发 Laravel 项目时,数据库问题往往最难靠表面现象定位:页面慢、结果不对、接口偶发超时,最后常常都要回到 SQL 本身。本文从日志记录和耗时监视两条线展开,分别说明 Laravel 11 的直接写法、Laravel 10 的兼容处理,以及慢查询预警适合放在什么位置,方便你按版本和排查目标快速落地。
为什么要把 SQL 打进日志
在开发和排障过程中,记录程序实际执行的 SQL 语句,通常比只看 ORM 调用更有效。因为最终送到数据库执行的语句,才是真正影响结果与性能的那一层。
除了查看具体 SQL,还可以顺手把执行耗时一起记录下来。这样做有两个直接价值:
- 排查查询条件、参数绑定和生成语句是否符合预期。
- 识别执行时间过长的 SQL,进一步做索引、改写查询或增加预警。
Laravel 已经提供了现成的查询监听能力,核心做法是在 Provider 的 boot 方法中注册监听回调。
Laravel 11:监听查询事件并记录 SQL
这里以 Laravel 11 为例。Laravel 提供的 SQL 监听事件,可以在查询执行后拿到 SQL 信息与耗时,适合直接接入日志系统。

<?php
namespace AppProviders;
use IlluminateDatabaseEventsQueryExecuted;
use IlluminateSupportFacadesDB;
use IlluminateSupportFacadesLog;
use IlluminateSupportServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
DB::listen(function (QueryExecuted $query) {
// 打印 sql 及执行耗时
Log::debug($query->toRawSql() . sprintf(' (%.2fms)', $query->time));
});
}
}
这段代码的重点在于 DB::listen() 和 $query->toRawSql():
DB::listen()会在每次查询执行后触发回调。$query->toRawSql()会把绑定参数替换进 SQL,便于直接阅读。$query->time可拿到本次查询耗时,示例中用sprintf(' (%.2fms)', $query->time)追加到了日志内容后面。
如果你当前项目已经使用统一日志渠道,这种写法通常就够用了。它对调试数据库查询、核对 where 条件、确认分页或关联查询是否符合预期都很直接。
Laravel 10:没有 toRawSql() 时怎么处理
如果项目还在较低版本的 Laravel 中,例如 Laravel 10,那么就没有 $query->toRawSql() 这个方法。这时需要自己把原始 SQL 和绑定参数拼接后再输出。
/**
* Bootstrap any application services.
*/
public function boot(): void
{
DB::listen(function (QueryExecuted $query) {
// $query->sql;
// $query->bindings;
// $query->time;
$bindings = $query->bindings;
$sql = $query->sql;
foreach ($bindings as $replace) {
$value = is_numeric($replace) ? $replace : "'" . $replace . "'";
$sql = preg_replace('/?/', strval($value), $sql, 1);
}
Log::debug($sql . " ({$query->time}ms)");
});
}
这段兼容写法的思路很简单:先拿到 $query->sql、$query->bindings 和 $query->time,再按顺序把绑定值替换回 SQL 中。
需要注意的是,这类手动拼接方案主要用于日志观察和问题排查,重点是提升可读性,而不是重新构造一份严谨的数据库执行器。对于包含特殊类型、复杂转义或驱动差异的场景,日志内容更适合作为定位依据,而不是作为复制执行的唯一标准。
版本差异下怎么选
如果是 Laravel 11,优先使用 $query->toRawSql(),代码更短,也更适合日常维护。
如果是 Laravel 10 及更低版本,就保留监听逻辑,用手动拼接方式补齐完整 SQL 输出。这样即便框架版本不同,日志观察能力仍然可以保持一致。
监视 SQL 耗时,识别慢查询
除了把每条 SQL 打进日志,还可以进一步监听执行时间过长的查询。这个能力更适合做慢查询提醒、开发环境预警,或者在内部通知系统中触发告警。

Laravel 官方文档中也提供了对应能力:Monitoring Cumulative Query Time。
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// 对超过 500ms 的 sql 增加回调监听
DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
// Notify development team...
});
}
这里的关键点是 DB::whenQueryingForLongerThan(500, ...):
500表示阈值,单位是毫秒。- 当查询耗时超过这个值时,就会进入回调。
- 回调中可以继续接入通知逻辑,例如提醒开发团队关注慢 SQL。
这种方式和单纯记日志的区别在于,它更强调“筛选”。如果你的项目查询量较大,把所有 SQL 全量输出到日志,后期查找成本会迅速上升;而设置一个明确阈值后,通常更容易把注意力集中到真正影响性能的问题上。
实际使用时的落地建议
如果你的目标是开发期排查问题,可以先启用 DB::listen(),把 SQL 和耗时完整输出,优先看语句是否正确。
如果你的目标是性能治理,则更适合在此基础上增加 DB::whenQueryingForLongerThan(500, ...),把超过阈值的查询单独处理。这样既能保留基础排查能力,也能把慢查询监视从普通日志中分离出来。
总的来说,这套方法解决的是两个常见问题:一是程序到底执行了什么 SQL,二是哪些 SQL 执行得太慢。前者帮助你快速定位业务逻辑或参数问题,后者帮助你把优化重点集中到真正影响性能的查询上。













