🗂

サクッと学べるN+1問題とその解決策(PHPコードで解説) Eager Loading , Batch Loading

2023/11/12に公開

N+1問題とは

DBに大量のSQLクエリを送信することで負荷が高まり、パフォーマンスが悪化する問題の事です。
フレームワークを使用して開発をしている際に、予期せず知らぬ間にとんでもない数のSQLクエリを叩いてしまっていることはないですか?
具体的には、1つのクエリで親データを取得し、その後子データを取得するプロセスで親の数だけ余分なクエリが発生し、この状態が繰り返されることを指します。これがN+1問題です。

N+1問題が発生しているPHPコード

以下のPHPコードは、N+1問題が発生している例です。ループ内で別テーブルを呼び出しており、親データのemp_noを基にループでSQLを発行しています。

  <?php
  $connection = mysqli_connect("ホスト名", "ユーザー名", "パスワード", "データベース名");

  $employeesQuery = "SELECT * FROM employees";
  $employeesResults = mysqli_query($connection, $employeesQuery);

  // N+1問題が発生中 親データのemp_noを基にループでSQLを叩いてますね。
  while ($employee = mysqli_fetch_assoc($employeesResults)) {
      $empNo = $employee['emp_no'];
      $salariesQuery = "SELECT * FROM salaries WHERE emp_no = '$empNo'";
      $salarieResults = mysqli_query($connection, $salariesQuery);
      // 処理
  }

  mysqli_close($connection);
  ?>

N+1問題が発生したSQL

上記PHPコードより、ループ内で別テーブルを呼び出すため、N+1問題が発生しています。

SELECT * FROM employees;
-- N+1問題が発生中 親データ(employees)のemp_noを基にループでSQLを叩いてますね。
SELECT * FROM salaries WHERE emp_no = 1;
SELECT * FROM salaries WHERE emp_no = 2;
-- ...

N+1問題対策

1.Eager Loading(積極的読み込み)

Eager Loadingは、関連するデータを予め取得しておく手法です。
以下のSQLクエリは、親レコードを取得する際に子レコードも同時に取得して、N+1問題を回避します。

-- 親レコードを取得する際に子レコードも同時に取得して、N+1問題を回避
SELECT *
  FROM employees
       JOIN salaries
       ON employees.emp_no = salaries.emp_no;

2.Batch Loading(一括読み込み)

Batch Loadingは、親データのIDを基に、一括で子データを取得する手法です。
これにより、N+1問題を回避しつつ必要なデータをまとめて取得できます。

SELECT *
  FROM salaries
 WHERE emp_no IN (id1, id2, id3);

まとめ

DBを取り扱う時は、Eager LoadingやBatch Loadingなどの手法があります。
冗長なデータベースアクセスはしないこと。

GitHubで編集を提案

Discussion