M365Con 2027 – 無料オンラインカンファレンス · 11–22 Jan 2027 · 120 sessions→ m365con.net

ブログ · Mirko Peters

N+1 クエリ問題を今度こそ完全に解決する

By ·

開発者の皆さん、ようこそお戻りくださいました。Entity Framework Core でアプリケーションを作ったことがあれば、首をかしげたくなるようなパフォーマンスの壁にぶつかったことがあるはずです。開発環境では一瞬で終わるクエリが、本番環境ではなぜか極端に遅くなる。その原因は、実はすぐ目の前に隠れていることが多く、悪名高い N+1 クエリ問題です。この記事では、この静かなパフォーマンスの敵が何を引き起こすのか、ユーザーが気づく前にどう検出するのか、そして Eager Loading、プロジェクション、そして賢い最適化テクニックを使ってどう効果的に修正するのかを詳しく見ていきます。

Automatically translated with AI from the original post. Read the English original

▶ BLOGConquering the N+1 QueryProblem Once and For AllM365.FM
Titelbild: Conquering the N+1 Query Problem Once and For All

開発者の皆さん、ようこそお戻りくださいました。Entity Framework Core でアプリケーションを作ったことがあれば、首をかしげたくなるようなパフォーマンスの壁にぶつかったことがあるはずです。開発環境では一瞬で終わるクエリが、本番環境ではなぜか極端に遅くなる。その原因は、実はすぐ目の前に隠れていることが多く、悪名高い N+1 クエリ問題です。この記事では、この静かなパフォーマンスの敵が何を引き起こすのか、ユーザーが気づく前にどう検出するのか、そして Eager Loading、プロジェクション、そして賢い最適化テクニックを使ってどう効果的に修正するのかを詳しく見ていきます。

技術的な詳細に入る前に、関連するポッドキャストのエピソード Improve Entity Framework Core Query Performance もぜひご覧ください。このエピソードでは、よくある Entity Framework の落とし穴をいくつか取り上げ、アプリケーションを高速に保つための実践的なアドバイスを紹介しています。

N+1 クエリ問題を理解する

N+1 とは何か?

N+1 問題は、関連データの読み込み方によって、余分なクエリが大量に発生する現象です。Entity Framework Core では、ループの中で関連データにアクセスすると、この問題が起きやすくなります。たとえば、100 件の注文を取得するとします。その後、注文ごとに Customer プロパティにアクセスします。すると、注文全体を取得する 1 つのクエリに加えて、顧客ごとにさらに 100 個のクエリが発行されます。結果として、1 つで済むはずのクエリが 101 個になります。これはアプリの速度を低下させ、特にデータ量が多い場合に顕著になります。小さなデータセットでは問題に気づきにくいものの、本番環境では大きな障害になります。

本番環境での N+1 クエリの検出

N+1 問題は、アプリがデータベースに送信するクエリの数を監視することで見つけられます。検出のための方法をいくつか紹介します。

  • SQL ログを有効にして、実行されるすべてのクエリを確認する。
  • プロファイリングツールを使って、クエリのパフォーマンスを確認する。
  • クエリの数を監視する。結果が少ないのに多くのクエリが発行されている場合は、N+1 問題の可能性があります。
  • 本番環境に近い大規模なデータセットを使って、アプリをテストする。

ヒント: 新しいクエリを追加したときや、関連データの読み込み方を変更したときには、必ずログとプロファイリングツールを確認してください。

Eager Loading で N+1 を解決する

Entity Framework Core で N+1 問題を解決するには、Eager Loading を使用します。Eager Loading を使うと、必要なデータをすべて 1 つのクエリで取得できます。これには Include メソッドを使います。これにより EF は関連テーブルを結合し、必要なものを一度にまとめて取得します。たとえば、著者とその著書を取得したい場合は、次のように書けます。

var authorsWithBooks = context.Authors.Include(a => a.Books).ToList();

このコードは、著者と著書を結合する 1 つのクエリを実行します。アプリは一度ですべてのデータを受け取ります。Eager Loading はクエリの数を減らし、アプリを高速にします。すぐに関連データが必要だとわかっている場合には、Eager Loading を使うべきです。これにより速度低下を避けられ、アプリを円滑に動かし続けることができます。

パフォーマンスのための追跡あり と 追跡なし の比較

Entity Framework Core は、読み込んだすべてのエンティティを追跡します。これがデフォルトの設定です。追跡によって、EF は変更が加えられたかどうかを把握できます。しかし、追跡はアプリの速度を低下させることがあります。大規模なデータセットでは特に遅くなります。また、メモリの使用量も増える可能性があります。追跡が有効な場合、クエリの実行時間も長くなることがあります。asnotracking を使うと、クエリが高速になり、メモリの使用量も抑えられます。その結果、アプリはより速く応答でき、同時により多くのユーザーに対応できるようになります。

ヒント: 追跡をオフにすると、クエリの実行が速くなります。これは、データの読み取りだけが必要な場合に最適です。

データを更新や削除したくない場合は、AsNoTracking を使用してください。多くの Web API はデータを読み取るだけです。エンドポイントの約 70~80% は情報を表示するだけです。これらは AsNoTracking と組み合わせると効果的に動作します。AsNoTracking が適している場面は次のとおりです。

  • データを表示するだけのレポートやダッシュボード
  • 表示するリストや詳細を返す API レスポンス
  • データベースからの読み取りだけを行う検索やフィルターのエンドポイント

ページング処理と大量データ

EF で大量のデータを扱うと、大きな問題が発生することがあります。一度に読み込みすぎると、アプリが遅くなったり、クラッシュしたりすることさえあります。次のようなことが起こり得ます。

  • アプリがメモリを使いすぎて、枯渇してしまう可能性があります。
  • データ量が多すぎるため、結果が返るまでに長い時間がかかることがあります。
  • 一度に大量に読み込もうとすると、データベースが停止する可能性があります。

これらの問題は、ページングを使うことで解決できます。ページングではデータを小さな単位に分割します。これにより、アプリの速度が向上し、使いやすくなります。EF では、これを実現するために Skip() と Take() を使用できます。Skip では一部のレコードを読み飛ばし、Take では必要な件数だけを取得します。これにより大量のデータを効率的に扱え、アプリを快適に動作させ続けられます。

一括操作とバッチ処理

一括操作を使うと、データベースの処理を高速化できます。これらの方法では、多数のレコードを同時に変更できます。標準の EF メソッドは小規模な処理には問題ありません。しかし、データ量が多くなると処理が遅くなります。一括操作を使うと、数千行の挿入、更新、削除を素早く行えます。CSV ファイルからデータをインポートする場合や、レコードの同期や夜間の更新処理を行う場合にも役立ちます。

バッチ処理では、処理をまとめて実行することで、アプリの動作を速くできます。バッチ更新では UpdateRange() を使って、多数の変更をまとめて行えます。SaveChanges() を頻繁に呼び出さないようにしてください。呼び出すたびにデータがデータベースに送信されるためです。EF Core 7 では、一括変更のための ExecuteUpdate や ExecuteDelete などの新しいメソッドが追加されました。

まとめ

Entity Framework Core のパフォーマンス最適化は、当て推量である必要はありません。N+1 クエリ問題がどのように発生するかを理解し、SQL の生成を監視するツールを活用し、Eager Loading と No-Tracking クエリを利用し、ページングと一括操作を取り入れることで、動作の遅いデータレイヤーを高性能なエンジンへと変えられます。常にパフォーマンスの基準値を測定し、現実的な条件下でクエリを十分にテストしてください。

これらの戦略をさらに深く学ぶには、対応するエピソード Improve Entity Framework Core Query Performance もぜひお聴きください。これらのベストプラクティスを実践すれば、隠れたボトルネックからアプリケーションを守り、ユーザーに快適な体験を提供できます。

Entities in this article

Entity Framework Core

RSS