💡
【データベース】N+1問題について
N+1問題とは
N件の行を持つテーブルの前データ取得でクエリを1回実行
別のテーブルから、前述のテーブルの各行に紐づくデータを1件ずつ取得するためクエリをN回実行
合計でN+1回のクエリを実行している問題のことです。
Nが大きくなるとクエリの数も増えてしまうので、処理に時間がかかってしまいます。
レンタルDVDの全データを取得し、それぞれのDVDを借りている人の情報を取得するとします。
まず、レンタルDVDの情報を全データ取得します。
SELECT * FROM DVD;
次に、各DVDを借りている人のIDを見て、対応する利用者の情報を取得します。
SELECT * FROM 利用者 WHERE 利用者ID=1;
SELECT * FROM 利用者 WHERE 利用者ID=2;
SELECT * FROM 利用者 WHERE 利用者ID=3;
このように関連する情報を取得する際に、何度もクエリを実行してしまうことがN+1問題になります。
解決策
JOIN句を使う
JOINでテーブルを結合し、1回のクエリ実行で関連する情報も丸ごと取得する方法です。
SELECT DVD.ID, DVD.DVD名, 利用者.利用者名
FROM DVD JOIN 利用者 ON DVD.利用者ID = 利用者.ID
Eager Loadingを使う
テーブルの取得に1回クエリ発行
別テーブルから、今後の処理に必要なデータを1回のクエリでまとめて取得
その後アプリ側で、データの結合などの処理を行う
この場合は、2回のクエリ実行を行います。
まずDVDテーブルのデータを全件取得します。
SELECT * FROM DVD;
各DVDの利用者IDを格納する配列を作成します。
userIDs = [1, 2, 3]
利用者テーブルから、利用者情報を取得します。
SELECT * FROM 利用者 WHERE ID IN (1, 2, 3);
NestJSで実装してみた
下記コードはJOIN句を使った処理になります。
こうすることで、1回のクエリ実行で必要なデータを取得することができます。
SELECT
user.id AS user_id,
user.name AS user_name,
user.email AS user_email,
books.id AS books_id,
books.name AS books_name,
books.userId AS books_userId,
FROM
user
LEFT JOIN
book AS books
ON
user.id = books.userId;
async getUsersWithBooks(): Promise<User[]> {
return this.userRepository
.createQueryBuilder("user")
.leftJoinAndSelect("user.books", "books") // ユーザーと本を結合
.getMany();
}
参考Project
下記のプロジェクトにJOIN句で1回クエリ実行してデータ取得するコードを掲載しています。
よければ参考にしてください!
参考になった記事
Discussion