N+1
مسئله N+1 یکی از مشکلات متداول در دسترسی به دیتابیس است و معمولاً وقتی پیش میآید که دادهها را به صورت تودرتو (nested) یا رابطهای (مثل user
و posts
) میخوانیم.
فرض کن میخوای همه کاربران و پستهای آنها را از دیتابیس بگیری.
یک برنامه بد این کار را اینطور انجام میدهد:
- اول همه کاربران را از دیتابیس میگیری → این یک query است.
- بعد برای هر کاربر، پستهایش را جداگانه از دیتابیس میگیری → اگر N کاربر داشته باشی، این N query اضافه میشود.
نتیجه:
- 1 query برای کاربران
- N query برای پستها
- جمعاً N + 1 query → به همین دلیل اسمش N+1 problem است.
مشکل این روش
- تعداد queryها به تعداد رکوردها وابسته است → با بزرگتر شدن دادهها، سرعت برنامه شدیداً افت میکند.
- فشار روی دیتابیس زیاد میشود.
var users []User
db.Find(&users)
for _, u := range users {
db.Model(&u).Association("Posts").Find(&u.Posts)
}
راه حل
راه حل اصلی استفاده از join یا eager loading است:
- به جای N query جداگانه، دادهها را یکجا با یک query میگیری.
- اکثر ORMها (مثل GORM در Go، Eloquent در Laravel) قابلیت
Preload
یاInclude
دارند که این مشکل را حل میکنند.
مثال ساده (Go + GORM)
var users []User
db.Find(&users)
for _, u := range users {
db.Model(&u).Association("Posts").Find(&u.Posts)
}
// Preload(like join)
var users []User
db.Preload("Posts").Find(&users)