Laravel 中如何把 SQL 打印到日志,并监视慢查询耗时

小满
小满
发布时间:2026-08-25 | 190人浏览过
原创

前言

开发 Laravel 时,SQL 往往是排查功能异常和性能问题的关键线索,但很多项目只看 ORM 调用,真正落库执行的语句却没有留下记录。本文按 Laravel 11 与 Laravel 10 两种情况分别说明查询日志的接入方式,再补上慢查询阈值监视,帮助你判断该记录什么、该在哪一层做预警。

开发 Laravel 项目时,数据库问题往往最难靠表面现象定位:页面慢、结果不对、接口偶发超时,最后常常都要回到 SQL 本身。本文从日志记录和耗时监视两条线展开,分别说明 Laravel 11 的直接写法、Laravel 10 的兼容处理,以及慢查询预警适合放在什么位置,方便你按版本和排查目标快速落地。

为什么要把 SQL 打进日志

在开发和排障过程中,记录程序实际执行的 SQL 语句,通常比只看 ORM 调用更有效。因为最终送到数据库执行的语句,才是真正影响结果与性能的那一层。

除了查看具体 SQL,还可以顺手把执行耗时一起记录下来。这样做有两个直接价值:

  • 排查查询条件、参数绑定和生成语句是否符合预期。
  • 识别执行时间过长的 SQL,进一步做索引、改写查询或增加预警。

Laravel 已经提供了现成的查询监听能力,核心做法是在 Provider 的 boot 方法中注册监听回调。

Laravel 11:监听查询事件并记录 SQL

这里以 Laravel 11 为例。Laravel 提供的 SQL 监听事件,可以在查询执行后拿到 SQL 信息与耗时,适合直接接入日志系统。

用卡片和箭头展示 Laravel 11 查询监听后,SQL 与耗时写入日志的过程
查询监听与日志输出适合放在 Laravel 11 查询监听一节后,帮助读者快速理解“监听事件 -> 生成 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 10 手动拼接 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 执行得太慢。前者帮助你快速定位业务逻辑或参数问题,后者帮助你把优化重点集中到真正影响性能的查询上。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表