N+1 queries چیست و چگونه از آن جلوگیری کنیم
خوب اول ببینیم این مساله N+1 کوئری چیه و چطوری به وجود میاد.
خیلی ساده بخواهیم بگیم N+1 کوئری یک مشکلی هست که توسط برنامه نویس ایجاد میشه و باعث سرازیر شدن کوئری ها به سمت دینابیس میشه و تعداد بالای کوئری ها و اتصال و قطع شدن کانکشن های دیتابیس و زمان لود دیتا باعث کندی عملکرد اپلیکیشن هامون میشه .
زمانی که شما تو فاز MVP هستید و توسعه میدید شاید اتفاق عجیبی نیفته ولی موقعی که روی Production میرید و بار روی سیستم زیاد تر میشه خودش رو به خوبی نشون میده . و مخصوصا اگر دیتابیس روی سروی مستقل باشه اون موقع باید برای هر کوئری و کانکشن یک زمان پاسخ در نظر بگیرید که خودش عدد قابل توجهی میشه .
یک مثال ساده بزنیم :
فرض کنید شما یک اپلیکیشن دارید که یکسری یوزر داره و هر کاربر ۱۰۰۰ یا N تا دوست داره . حالا ما میخواهیم تو برنامه بگیم تمام کاربر با تمام دوستاشون رو لود کن و برامون نشون بده . متودهاش رو ببینید:
[php]
$users = load_users();
foreach ($users as $user) {
$users_friends = load_friends_for_user($user);
}
[/php]
خوب میتونید حدس بزنید زیر این کدها چه کوئری هایی لود میشن ؟
خیلی ساده اس :
برای خط اول کد ،کوئری شبیه به این باید لود بشه تا بتونه کاربرها رو لود کنه
[php]
SELECT * FROM users WHERE …
[/php]و داخل foreach و متود load_friends_for_user هم کوئری هایی شبیه به این رو صدا میکنه .
[php]SELECT * FROM user WHERE …
SELECT * FROM friend WHERE userID = 1
SELECT * FROM friend WHERE userID = 2
SELECT * FROM friend WHERE userID = 3
SELECT * FROM friend WHERE userID = 4
SELECT * FROM friend WHERE userID = 5
[/php]پس همونطور که میبینید ما برای اینکه به داده دلخواه برسیم میبایست N+1 کوئری رو اجرا کنیم .
خوب چیکار کنیم که این همه کوئری اجرا نشه ؟ میتونیم با یک کوئری تمام نتایج رو بگیریم بعد اصطلاحا تو حلقه بگردونیمش (iterating)
خوب پس باید اول یه تغییری تو کدمون بدیم که اول اطلاعات رو به دست بیاریم.
[php]
$cats = load_users();
$friends = load_all_friends_for_these_users($users);
foreach ($users as $user) {
$users_friends = $friends[$user->getID()];
}
[/php]حالا چه اتفاقی می افته ؟
هیچی به جای اون همه کوئری این کوئری ها زیر انجام میشه و تو این قطعه کد، اتصال به دیتابیس یکبار بیشتر باز و بسته نمیشه و خیلی سریعتر از حالت قبل خروجی به دست میاد.
[php]
SELECT * FROM users WHERE …
SELECT * FROM friends WHERE userID IN (1,2,3,4,5,…)
[/php]
مهم نیست چقدر آبجکت داشته باشید هر چقدر که باشه این دو تا کوئری بیشتر اجرا نمیشن.