بهینه سازی دسترسی به دیتابیس در جنگو
یه مدت میشه که جنگو رو شروع کردم و از این به بعد اینجا قراره از تجربیاتم بنویسم.
خب، بریم که شروع کنیم.
حل مشکلN+1 کوئری در جنگو
یکی از اولین مشکلاتی که هنگام کار کردن با دیتای واقعی بهش برمیخورید مسئلهN+1 کوئریه. میگم دیتای واقعی چون وقتی رکوردهای یک جدول زیاد بشه و زمان پاسخ سرور بالا بره تازه متوجه میشید که یه جای کار میلنگه. اینجا سعی شده با یک مثال ساده، اصل مسئله و راه حلش توضیح داده بشه.
فرض کنید این مدلها رو داریم:
class Author(models.Model):
name = models.CharField(max_length=30)
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.ForeignKey(Author, on_delete=’models.Cascade’, related_name=books)
حالا میخوایم یک کتاب با id مشخص رو بگیریم و نام نویسنده اش رو بیرون بکشیم:
book = Book.objects.get(pk=1)
book.author.name
اینجا یک کوئری برای خط اول و select کردن کتاب و یه کوئری دیگه برای خط دوم و گرفتن نام نویسنده کتاب اجرا میشه.
تا اینجا به نظر نمیاد مشکل خاصی وجود داشته باشه.
جایی به مشکل برمیخوریم که تعداد زیادی کتاب هست و میخوایم لیستی از نام کتاب به همراه نام نویسنده کتاب داشته باشیم.
output = []
for book in Book.objects.all():
output.append({
‘book’: book.name,
‘author’: book.author.name
})
یکبار همه کتابها رو select می کنیم و بعدش برای هر کتاب نام نویسنده اش رو می گیریم. هربار دسترسی به کلید خارجی نیاز به یک کوئری داره و اینجا ما به تعداد کتاب ها کوئری خواهیم داشت به علاوه کوئری اول که همه کتاب ها رو گرفتیم. N+1 از اینجا میاد.
هر چه تعداد کتاب ها بالاتر بره تعداد کوئری ها بیشتر میشه و سرعت بیرون کشیدن اطلاعات کم و کمتر.
دلیل این اتفاق هم این هست که در جنگو اساساً Querysetها Lazy هستند و تا وقتی نیاز به اونها نباشه، اجرا نمیشن. در اینجا هم وقتی select روی کتاب ها اتفاق میوفته، آبجتهای مرتبط (related_object) مثل نویسنده کتاب لود نمیشه. درست وقتی که نام نویسنده رو میخوایم کوئری مربوط به اون اجرا میشه.
در مقابل این مفهوم، Eager loading رو داریم. در Eager loading تمام دادههای مورد نیاز بصورت یکجا لود میشن و بنابراین فقط یک کوئری کافیه تا نام کتاب ها و نویسندهاشون رو داشته باشیم.
در جنگو از طریق تابع select_related میتونیم Eager loading رو پیاده کنیم:
for book in Book.objects.all().select_related(‘author’):
output.append({
‘book’: book.name,
‘author’: book.author.name
})
این select_related درواقع یک Join در SQL هست که برای ارتباطات یک به یک و یک به چند کاربرد داره.
تابع دیگری به اسم prefetched_related وجود داره که همین Join رو نه در SQL که در پایتون انجام میده و برای روابط یک به چند و چند به چند استفاده میشه.
مطلبی دیگر در همین موضوع
در باب خواندن کتاب کاج های زرد
مطلبی دیگر در همین موضوع
مغزی که خود را تغییر می دهد (قسمت 1)
بر اساس علایق شما
جشن نو دانشجو معلمان🎓