◀ 31.

SQLの実行ログ出力方法

▶
この記事の要点
  • その場で見るなら DB::enableQueryLog() → 処理 → dd(DB::getQueryLog())
  • 実行せずに SQL だけ見たいなら toSql() / toRawSql()(後者はバインド値を埋め込んだ SQL)
  • 全リクエストの SQL をログファイルに残すなら DB::listen() を AppServiceProvider に書く
  • 画面で常時確認したいなら 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

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

use Illuminate\Support\Facades\DB;

DB::enableQueryLog();

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

dd(DB::getQueryLog());

上記の実装をすることで、SQL を走らせると画面に以下のような結果が出力されます。

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() の出力はそのままデータベースクライアントに貼り付けて実行できるので、EXPLAIN で実行計画を調べるときに便利です。ただし、get() や first() を呼ぶ前のクエリビルダに対して使う必要があります。リレーションの Eager Loading(with)で追加発行される SQL は含まれないため、それらも見たい場合は方法 1 か方法 3 を使います。

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

アプリ全体で発行された 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(barryvdh/laravel-debugbar): ページ下部にツールバーを出し、そのリクエストで実行された SQL・件数・時間・重複クエリを表示する
  • Laravel Telescope(公式): リクエスト・クエリ・ジョブ・例外などを記録し、専用画面で後から閲覧できる。遅いクエリの強調表示もある

どちらも開発環境専用として composer require --dev で入れるのが基本です。本番環境で有効のままにすると、SQL やリクエスト内容が外部から見える危険があります。

よくある落とし穴

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

確認方法

方法 3 を設定した場合は、ページを 1 回開いてから storage/logs/laravel.log を確認します。同じ形の SQL が何十回も並んでいれば N+1 問題の可能性が高く、with() による Eager Loading で回数を減らせます。修正前後でログに出る SQL の件数を比べると、効果を確認できます。

関連

Post Share
子ページ

子ページはありません

同階層のページ
  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編)