| この記事の要点 |
|
方法 1: settings.py で全 SQL をログに出す
# settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"sql": {"format": "[%(asctime)s] %(message)s"},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "sql",
},
},
"loggers": {
"django.db.backends": {
"handlers": ["console"],
"level": "DEBUG", # ここを DEBUG にすると SQL が出る
"propagate": False,
},
},
}
実行すると、かかった秒数付きで次のように出力されます。
(0.001) SELECT "app_user"."id", "app_user"."name" FROM "app_user" WHERE "app_user"."active"; args=(True,)
重要な制約: このロガーは settings.DEBUG = True のときしか動きません。Django が SQL を記録するかどうかを DEBUG で切り替えているためです。本番で確認したい場合は、後述のデータベース側のログを使ってください。
方法 2: 特定のクエリの SQL だけ見る
qs = User.objects.filter(active=True).order_by("-created_at")[:10]
print(qs.query) # 実行せずに SQL 文字列を得る
# SELECT "app_user"."id", ... FROM "app_user" WHERE "app_user"."active" = True
# ORDER BY "app_user"."created_at" DESC LIMIT 10
# パラメータを分けて見たいとき
print(qs.query.sql_with_params())
# 実行計画まで見たいとき(PostgreSQL / MySQL)
print(qs.explain())
print(qs.explain(analyze=True))
qs.query の出力は値が埋め込まれた見た目になり、そのまま DB に貼っても動かない場合があります。原因調査には十分ですが、正確な発行内容は次の connection.queries で確認してください。
方法 3: 実行済みクエリの一覧
from django.db import connection, reset_queries
reset_queries() # 計測開始
users = list(User.objects.all())
for u in users:
print(u.profile.nickname) # ここで N+1 が起きる
print(len(connection.queries)) # 発行された本数
for q in connection.queries:
print(q["time"], q["sql"][:120])
connection.queries も DEBUG = True のときだけ蓄積されます。長時間動くプロセスではメモリを食い続けるので、区切りごとに reset_queries() を呼びます。
N+1 問題を見つける
from django.db import connection, reset_queries
from contextlib import contextmanager
@contextmanager
def count_queries(label):
reset_queries()
yield
print(f"{label}: {len(connection.queries)} 本")
with count_queries("最適化なし"):
for u in User.objects.all():
print(u.profile.nickname) # 1 + N 本
with count_queries("select_related"):
for u in User.objects.select_related("profile"):
print(u.profile.nickname) # 1 本(JOIN される)
with count_queries("prefetch_related"):
for u in User.objects.prefetch_related("orders"):
print(len(u.orders.all())) # 2 本(別クエリでまとめて取得)
| 関係 | 使うもの |
|---|---|
ForeignKey / OneToOneField(1 対 1・多対 1) | select_related() |
逆参照 / ManyToManyField(1 対多・多対多) | prefetch_related() |
方法 4: django-debug-toolbar
pip install django-debug-toolbar
# settings.py
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
INTERNAL_IPS = ["127.0.0.1"]
# urls.py
from django.urls import include, path
import debug_toolbar
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
画面の右側にパネルが出て、SQL の本数・実行時間・重複回数・実行計画が一覧できます。「同じクエリが 50 回」といった表示が出れば N+1 です。開発環境専用なので、INSTALLED_APPS への追加は if DEBUG: で囲んでください。
本番で SQL を確認したいとき
DEBUG = True を本番で有効にしてはいけません。設定値やソースコードが画面に露出します。本番ではデータベース側のログを使います。
-- MySQL: 遅いクエリだけ残す
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 1 秒以上
SHOW VARIABLES LIKE 'slow_query_log_file';
-- PostgreSQL: postgresql.conf
-- log_min_duration_statement = 1000 (ミリ秒)
-- SELECT pg_reload_conf();
アプリ側で残したい場合は、connection.execute_wrapper() で自前のフックを差し込む方法もあります。こちらは DEBUG に依存しません。
import logging
import time
from django.db import connection
logger = logging.getLogger("sql")
def log_slow(execute, sql, params, many, context):
start = time.monotonic()
try:
return execute(sql, params, many, context)
finally:
elapsed = time.monotonic() - start
if elapsed > 0.5: # 0.5 秒以上だけ記録
logger.warning("slow %.3fs %s", elapsed, sql[:500])
# ミドルウェアやビューの中で
with connection.execute_wrapper(log_slow):
list(User.objects.all())
SQL には検索条件として個人情報が入り得ます。ログに残す前に、出力先の権限と保存期間を決めておいてください。
関連
- django — 親カテゴリ
- ログ出力方法 — アプリのログ全般
- ログのローテート設定
- データベースへの接続と操作
- filterの検索オプション
- 素のSQLを直接実行する方法
子ページ
子ページはありません
同階層のページ
- 環境構築とプロジェクト/アプリの作成
- MVC(MVT)のそれぞれの使い方と説明
- データベースへの接続と操作
- Django Administration
- git管理
- エラー一覧
- バージョンの確認方法
- ログ出力方法
- SQLのログ出力方法
- ログのローテート設定
- settings.pyの定数にアクセスする方法
- 本番環境へのインストールとアプリのデプロイ(apache編)
- 本番環境へのインストールとアプリのデプロイ(nginx編)
- djangoアプリの本番の開始URLを変更する
- 静的(static)ファイルの置き場所と読み込み(画像、css、js )
- CSRFトークンをAjaxで使用する方法
- ajaxの使用例(POST編)
- ファイルのアップロードとファイルの名前
- クイックスタート/チュートリアル
- ログイン機能
- テンプレート側のログイン判定
- ビュー側のログイン判定
- 管理者ユーザーの作成/判定と管理画面
- モデルのjson化とレスポンス
- runserverでポートを指定する方法
- cronによるバッチ実行
- テンプレートで利用する共通のcontextを定義する方法
- プログラムが本番サーバーで反映されない場合の対処法
- APIの作成
- cron用コマンド・ファイルの作成
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38