==================================================== 60:Django ORMでどんなSQLが発行されているか気にしよう ==================================================== .. maigo:: ORMを使えばSQLを知らなくても良い? * 後輩W:新しい機能を実装したらレスポンスがすごい遅い……。 * 先輩T:どんなSQLが発行されてるか確認してみた? * 後輩W:どうやったらわかるんですか? * 先輩T: :index:`Django Debug Toolbar` を使うといいよ。あるいは :index:`settings.LOGGING` にこんな設定を書いて、DBのSQL発行をログ出力しよう。 .. code:: python LOGGING = { 'version': 1, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False }, }, } * 後輩W:ブラウザでアクセスしたら何十行もSQLが出てきました。 * 先輩T:それはSQLを発行しすぎみたいだね。SQLを発行している実装コードを確認してみよう。 * 後輩W:これは何か問題があるんですか? * 先輩T:そうだね、SQL発行ごとにデータベースと通信してデータをやりとりするので、意図せずSQL発行が多くなってるのは問題があるよ。こういうのを **N+1問題** って言うんだ。 * 後輩W:そうなんですね……。そういうのはORMでうまくやってくれるんだと思ってました……。 .. index:: ORM .. index:: Django ORM .. index:: SQL 残念ながら、ORMは「SQLを知らなくても使える便利な仕組み」ではありません。 簡単なクエリであればSQLを確認する必要はなく、多くの要件は簡単なクエリの発行で済むかもしれません。 だからといって、ORMがどんなSQLを発行しているか気にしないままでいると、落とし穴にはまってしまいます。 ORMを使ってクエリを作成していると、どんなSQLが発行されているか見えづらくなります。 Pythonの辞書データを使う感覚でDBへのクエリを発行すると、同じSQLが何度も発行されたり、Pythonプログラムとデータベースとの間でデータが往復していたりして、その分アプリは遅くなっていきます。 このような問題はORMで大量のデータを扱ったことがない場合に発生します。 具体的な失敗 ================== 1. データベースに格納されているマスターデータ(本のジャンルや企業の営業所名)などの、めったに変更されないけれどよく参照するデータを1リクエスト中に何度も取得している 2. SELECTで数十万件のIDをデータベースから取得して、それを少し加工してから次のSQLに渡している 3. 期待するデータを得ようとORMで複雑なコードを書いた結果、複雑なSQLが組み立てられてしまい、DBでの処理コストが非常に高い 4. SELECTで数件のIDを取得して、プログラム側のループ処理でIDそれぞれについて別のテーブルから該当するデータを取得しており、件数に比例してクエリ実行回数が増加する 1番目の問題は、たとえばログ出力に以下のようなSQL発行が短時間のうちに繰り返されている状態です。 .. code:: text DEBUG [2019-12-13 03:23:56,373] django.db.backends (0.000) SELECT "genre"."id", "genre"."name", "genre"."created_at" FROM "genre"; ... DEBUG [2019-12-13 03:23:56,374] django.db.backends (0.000) SELECT "genre"."id", "genre"."name", "genre"."created_at" FROM "genre"; ... DEBUG [2019-12-13 03:23:56,375] django.db.backends (0.000) SELECT "genre"."id", "genre"."name", "genre"."created_at" FROM "genre"; ... 2番目と3番目は :doc:`62-SQLから逆算してDjango_ORMを組み立てる` で説明します。 4番目は :doc:`61-ORMのN+1問題を回避しよう` で説明します。 本項では、そもそも問題に気づくためにはどうすればよいか説明します。 ベストプラクティス ================== 以下のポイントを守りましょう。 * ORMを使ったクエリを新しく書いたら、ORMが生成するSQLを確認する * 1回のSELECTで書けるクエリが複数回に分けて実行されていたら、1つにまとめることを検討する * 1つのリクエスト中に何度も同じSQLが発行されていたら、1回で済むように修正する .. omission:: 関連 ==== * :doc:`62-SQLから逆算してDjango_ORMを組み立てる` * :doc:`61-ORMのN+1問題を回避しよう` * :doc:`../トラブルシューティング・デバッグ/76-シンプルに実装しパフォーマンスを計測して改善しよう`