◀ 31.

SQLの実行ログ出力方法

▶

SQLの確認では、組み立てた1クエリを見るのか、実行した一連のSQLを数えるのかを分けます。調査のためでもSQL全文やbindingsを公開せず、対象接続・記録範囲・停止方法を決めてから確認します。

SQL組立てと実行後の観測、秘密値の取扱いの区別

値を記録しない観測メタデータの例

次の関数はQueryExecutedから接続名・SQL文字列のSHA256・実行時間だけを選び、SQL全文・bindingsを返しません。これだけでログへ登録されるわけではありません。必要な場合にアプリの登録箇所で呼び出し、専用チャンネル/期間/件数上限/閲覧権限を別途決めます。ハッシュも完全な匿名化ではなく、推測可能な入力を含む場合の取扱いが必要です。

<?php

namespace ContentExamples;

use Illuminate\Database\Events\QueryExecuted;

function querySummary(QueryExecuted $query): array
{
    return [
        'connection' => $query->connectionName,
        'sql_sha256' => hash('sha256', $query->sql),
        'time_ms' => $query->time,
    ];
}

Laravel 13と隔離MySQLで、バインド値とSQLへ直接書いたダミー秘密値の双方がこの関数の返却値に出ないことを確認しました。例外本文、他のロガー、保存済みファイルまで無害化する関数ではありません。

本数・時間を比較するときの手順

  1. 同じ検索語/ページ/接続とデータ条件で測る。メニューAjaxと記事表示など別HTTP処理のSQLを混ぜない。
  2. 一時的に収集する場合は、元の収集状態を確認し、対象処理だけを囲む。長時間workerのリスナー重複登録にも注意する。
  3. paginateの件数SQL、一覧、Eager Loading、ループ内取得を分ける。toSqlだけでは後続の追加SQLを数えられない。
  4. 本数だけでなく結果の集合・順序・総件数・時間を比較する。少ないSQLが必ず低負荷とは限らない。
  5. 収集を止め、既存の記録と新しいログ出力の両方を点検する。disableQueryLogでlistenまで止まったとは扱わない。

公式: QueryExecutedと接続/監視、Debugbarの現行配布先。パッケージは対象Laravel/PHPとの対応と閲覧制御を確認し、この記事の改善では本体に追加・有効化していません。関連はページングとQuery Builderです。

この記事の要点
  • その場で見るなら DB::enableQueryLog() → 処理 → dd(DB::getQueryLog())
  • 実行せずに SQL だけ見たいなら toSql() / toRawSql()(後者はバインド値を埋め込んだ SQL)
  • 実行後のイベントを観測するならDB::listen。登録範囲・接続・ログ保存方針を先に決める
  • 画面で常時確認したいなら Laravel Debugbar や Telescope などの開発用パッケージ
  • 本番でクエリを全件ログに出すとログ肥大化・個人情報漏えいの原因になる。環境で切り替える

Laravel の Eloquent やクエリビルダが実際にどんな SQL を発行しているかを確認する方法を、目的別にまとめます。N+1 問題の発見や、意図しない条件で検索されていないかの確認に使えます。

目的別の方法の選び方

やりたいこと方法実行されるか
特定の処理で発行された SQL を一覧で見るDB::enableQueryLog / getQueryLog実行される
1 つのクエリの SQL 文を確認するtoSql / toRawSql / dumpRawSql実行されない
すべての SQL をログファイルに残すDB::listen実行される
ブラウザ上で常に確認するLaravel Debugbar / Telescope実行される

方法 1: DB::enableQueryLog と getQueryLog

次の旧例は画面へSQLと値を出すため、本番/公開画面で実行しないでください。 ログは接続ごとのメモリ収集で、必要な対象だけを短時間計測します。disableQueryLogは収集停止、flushQueryLogは収集済み配列の消去であり、保存済みログファイルの削除ではありません。DB::listenを解除する操作でもありません。

クエリログの収集を有効にしてから処理を実行し、最後に収集した結果を取り出します。

use Illuminate\Support\Facades\DB;

DB::enableQueryLog();

// ここで SQL を実行する処理
$items = TestTable::orderBy('col1', 'desc')->paginate(9);

dd(DB::getQueryLog());

次は旧出力の抜粋で、paginateの全SQLが1本という意味ではありません。 通常のpaginateは件数取得も行います。TestTableのモデル/スコープ/関連取得や版によって本数・テーブル名・出力は変わるため、以下を固定の期待値にしないでください。

array:1 [▼
  0 => array:3 [▼
    "query" => "select * from `test_table` order by `col1` desc limit 9 offset 0"
    "bindings" => []
    "time" => 2.22
  ]
]

query は SQL 文(プレースホルダは ? のまま)、bindings はプレースホルダに入る値、time は実行時間(ミリ秒)です。古い記事では use DB; と書かれていることがありますが、現在は use Illuminate\Support\Facades\DB; と完全なクラス名で書くのが確実です。

クエリログはメモリに溜まり続けるため、バッチ処理などで大量の SQL を流す場合は有効にしたままにしないでください。必要なら DB::disableQueryLog() や DB::flushQueryLog() で止める・消すことができます。別の DB 接続を使っている場合は DB::connection('mysql2')->enableQueryLog() のように接続ごとに有効にします。

方法 2: toSql / toRawSql で SQL 文だけ見る

クエリを実行せずに、組み立てられた SQL を確認できます。

$query = User::where('status', 'active')->where('age', '>=', 20);

// プレースホルダのままの SQL
echo $query->toSql();
// select * from `users` where `status` = ? and `age` >= ?

// バインド値を埋め込んだ SQL(Laravel 10.15 以降)
echo $query->toRawSql();
// select * from `users` where `status` = 'active' and `age` >= 20

// 出力して続行 / 出力して停止
$query->dumpRawSql();
$query->ddRawSql();

toRawSql()はバインド値を含む表示用SQLです。秘密値が入り得るため、そのまま共有・再実行しないでください。EXPLAINもDB/構文によって実行の有無や負荷が異なり、EXPLAIN ANALYZE等を確認目的で本番へ安易に実行しません。ただし、get() や first() を呼ぶ前のクエリビルダに対して使う必要があります。リレーションの Eager Loading(with)で追加発行される SQL は含まれないため、それらも見たい場合は方法 1 か方法 3 を使います。

方法 3: DB::listen ですべての SQL をログに出す

次はbindingsを保存してしまう旧例で、推奨設定ではありません。 APP_DEBUGだけを記録許可条件にせず、本番でtrueにして調査しないでください。生SQLに値を直接書いた場合は、bindingsを省略してもsql自体に秘密が残ります。下の全文ログ方式を採用せず、上のメタデータ例と必要な観測範囲を検討してください。

ログの出力先はチャンネル/日次分割/保存レベル等の設定に依存し、常にstorage/logs/laravel.logとは限りません。以下は旧リスナー例として、app/Providers/AppServiceProvider.php の boot() にリスナーを登録します。

use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

public function boot(): void
{
    if (config('app.debug')) {
        DB::listen(function (QueryExecuted $query) {
            Log::debug('SQL', [
                'sql' => $query->sql,
                'bindings' => $query->bindings,
                'time_ms' => $query->time,
            ]);
        });
    }
}

旧例はAPP_DEBUG=trueを条件にしますが、機密値の除去や保持容量を保証しません。採用可否を決める際は専用のログチャンネル(config/logging.php に sql チャンネルを追加し、Log::channel('sql') で書く)を作ると、通常のログと混ざらず見やすくなります。

遅いクエリだけを検出したい場合は、DB::whenQueryingForLongerThan() を使うと、1 リクエスト内のクエリ合計時間がしきい値を超えたときだけ処理を実行できます。

方法 4: 開発用パッケージで画面に表示する

  • Laravel Debugbar(現行公式の配布名はfruitcake/laravel-debugbar。旧barryvdh名と区別): ページ下部にツールバーを出し、そのリクエストで実行された SQL・件数・時間・重複クエリを表示する
  • Laravel Telescope(公式): リクエスト・クエリ・ジョブ・例外などを記録し、専用画面で後から閲覧できる。遅いクエリの強調表示もある

Debugbarは開発用として扱います。Telescopeはローカル限定導入と、本番で認可・収集対象・保持/削除を設計する導入を区別します。--devだけで閲覧制御が完成するわけではありません。公式Telescopeの認可とフィルターを確認し、導入用のDB変更も含め通常の承認手順へ分離してください。

よくある落とし穴

  • getQueryLog が空になる: enableQueryLog を SQL 実行より後に呼んでいる、または別の接続で実行されている
  • dd() の後の処理が動かない: dd は処理を止める。続行したいなら dump() を使う
  • 本番でログが肥大化する: DB::listen を無条件に登録すると、全リクエストの SQL が記録されディスクを圧迫する。パスワードや個人情報がバインド値としてログに残る点にも注意
  • toRawSql の値は参考用: 埋め込み表示はあくまで確認用。実際の実行はプリペアドステートメントで行われている

確認方法

方法 3 を設定した場合は、ページを 1 回開いてから storage/logs/laravel.log を確認します。同じ形のSQLの繰返しは、ループ・意図した繰返し・再試行等を呼出箇所と照合します。N+1ならEloquentのwithやQuery Builderの一括取得を検討しますが、withだけであらゆる重複を除けるわけではありません。修正前後でログに出る SQL の件数を比べると、効果を確認できます。

関連

子ページ

子ページはありません

同階層のページ
  1. インストールと設定
  2. クイックスタート & チュートリアル(初心者向け)
  3. クイックスタート & チュートリアル(中級者向け)
  4. ルーティング
  5. Bladeテンプレート(ビュー/レイアウト)
  6. コントローラー
  7. マイグレーションとテーブル定義
  8. データベースの設定
  9. Eloquentモデル (ORM)
  10. SQLとクエリビルダー
  11. バリデーション
  12. .envファイルの設定値へのアクセス
  13. 動作環境による分岐処理
  14. configフォルダ配下の設定値へのアクセス
  15. assetヘルパーを利用したpublicフォルダへのアクセス
  16. storageフォルダへのアクセス
  17. アプリケーション名の変更
  18. メンテナンス
  19. ログイン画面(認証システム)の作成
  20. ログインの必須化
  21. ログインユーザー情報の取得
  22. ルートの認証化
  23. 本番サーバーへのデプロイ方法
  24. 多言語化
  25. csrf_field
  26. ファイルのダウンロード
  27. CSVのアップロードおよび読み込み(maatwebsite/excel)
  28. ページタイトルの設定
  29. コマンド一覧
  30. エラー一覧
  31. SQLの実行ログ出力方法
  32. キャッシュのクリア
  33. Selectの結果の最初もしくは最後に任意の値を追加する方法
  34. ajaxでPOST通信する際の注意点
  35. ソーシャルログインの実装
  36. セッション情報の確認
  37. ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
  38. redirectやreturn viewにメッセージを付与する方法
  39. クッキー(cookie)の設定と取得
  40. クラスの再読み込み
  41. csrfの有効時間を変更する方法
  42. ViewComposerを用いてviewに共通の値を付与する方法
  43. View::shareを用いて共通の値を各ビューに渡す方法
  44. ミドルウェアを用いた処理の共通化
  45. Middleware内でAuth::check()などを使用する方法
  46. Controller以外でリダイレクトする方法
  47. セッションの値の取得/保存/更新/削除
  48. $requestの値を変更する方法
  49. 常時SSL化
  50. ページング(ページネーション)をする方法
  51. vue.jsとの連携
  52. Vue.jsと連携するSPA実行環境構築
  53. .envの値をvue.jsで参照する方法
  54. vue.jsを本番環境にリリースする方法
  55. could not find driver(Windows, MySQL編)

最近更新/作成されたページ