🗂
サクッと学べるN+1問題とその解決策(PHPコードで解説) Eager Loading , Batch Loading
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などの手法があります。
冗長なデータベースアクセスはしないこと。
Discussion