ویدیو کار با متدهای select_related و prefetch_related در جنگو
ابزار ORM جنگو به ما کمک می کند تا با استفاده از کد پایتون که بعداً به SQL ترجمه می شود، پرس و جوهایی را در پایگاه داده های رابطه ای انجام دهیم. ORM متدهایی را ارائه می دهد که برای بهینه سازی پرس و جوها در پایگاه داده استفاده می شود.متدهای select_related و prefetch_related جنگو میتوانند با استفاده از پایتون یا SQL جداول دیتابیس را به یکدیگر join بزنند. هدف بهینه سازی آنها کاهش تعداد پرس و جوها است.
تمام مثالهای این مقاله از مدل زیر استفاده میکنند:
from django.db import models
class Author(models.Model):
first_name = models.CharField(max_length=512)
last_name = models.CharField(max_length=512)
class Book(models.Model):
title = models.CharField(max_length=512)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
مدل Author با اطلاعات زیر پر شده:
id | first_name | last_name |
1 | William | Shakespeare |
2 | Miguel | de Cervantes |
همچنین مدل Book با اطلاعات زیر پر شده:
id | title | author_id |
1 | Hamlet | 1 |
2 | Romeo and Juliet | 1 |
3 | Macbeth | 1 |
4 | Don Quixote | 2 |
# تابع select_related جنگو
متد Select_related جنگو برای متصل کردن جداول با استفاده از INNER JOIN زبان SQL استفاده میشود. این متد یک QuerySet را برمیگرداند که روابط کلید خارجی را دنبال میکند و دادههای شی مرتبط اضافی را هنگام اجرای پرس و جو انتخاب میکند. در این حالت دیگر نیازی نیست برای گرفتن اطلاعات مرتبط دوباره به دیتابیس کوئری بزنید. این توضیح ممکن است کمی انتزاعی به نظر برسد پس بهتر است یک مثال ببینیم.
بیایید بدون استفاده از select_related ابتدا به یک کوئری بزنیم:
>>> Book.objects.get(id=1).author.first_name
‘William’
دستور ORM بالا به کد sql زیر ترجمه خواهد شد:
SELECT "book"."id", "book"."title", "book"."author_id" FROM "book" WHERE "book"."id" = 1
SELECT "author"."id", "author"."first_name", "author"."last_name" FROM "author" WHERE "author"."id" = 1
همانطور که مشخص است برای گرفتن اطلاعات دوبار به دیتابیس کوئری زده شده. کوئری اول author_id مرتبط با کتاب را پیدا میکند و کوئری دوم اطلاعات مربوط به نویسنده را برمیگرداند. این روش اصلا بهینه نیست. به جای آن میتوانیم از متد select_related جنگو استفاده کنیم که هر دو جدول را در یک کوئری برمیگرداند:
>>> Book.objects.select_related('author').get(id=1).author.first_name
‘William’
این دستور به شکل زیر ترجمه خواهد شد:
SELECT "book"."id", "book"."title", "book"."author_id", "author"."id", "author"."first_name", "author"."last_name" FROM "book" INNER JOIN "author" ON ("book"."author_id" = "author"."id") WHERE "book"."id" = 1
ما هنوز هم نتیجه یکسانی دریافت کردیم اما این بار فقط با یک کوئری. میتوانیم ببینیم که select_related جنگو از INNER JOIN برای متصل کردن دو جدول به یکدیگر استفاده کرده است.
# تابع prefetch_related جنگو
متد prefetch_related جنگو هم دقیقا هدفی مشابه select_related دارد اما به روشی متفاوت این کار را انجام میدهد. متد prefetch_related هر دو جدول را پرس و جو می کند و آنها را با استفاده از Python (نه SQL) به هم متصل می کند، بنابراین اساساً کارایی آن تا حدودی کمتر از روش select_related است. اما prefetch_related ویژگی های دیگری نیز دارد.
بیایید ابتدا تفاوتهای بین کوئری ساده و متد prefetch_related را در جنگو بررسی کنیم. همانطور که قبلاً دیدیم، کوئری ساده به دو کوئری SQL ترجمه می شود. آیا می توانیم با استفاده از متد prefetch_related نتیجه بهتری بگیریم؟
>>> Book.objects.prefetch_related('author').get(id=1).author.first_name
'William'
دستور زیر به شکل زیر ترجمه میشود:
SELECT "book"."id", "book"."title", "book"."author_id" FROM "book" WHERE "book"."id" = 1;
SELECT "author"."id", "author"."first_name", "author"."last_name" FROM "author" WHERE "author"."id" IN (1);
در حال حاضر نمیتوانیم هیچ نوع بهینهسازی ببینیم. پس بیایید یک مثال دیگر ببینیم:
books = Book.objects.prefetch_related('author')
# <QuerySet [<Book: Book object (1)>, <Book: Book object (2)>, <Book: Book object (3)>, <Book: Book object (4)>]>
for book in books:
print(book.author.first_name)
# William
# William
# William
# Miguel
خروجی یکسان است اما دستور بالا به شکل زیر ترجمه میشود:
SELECT "book"."id", "book"."title", "book"."author_id" FROM "book";
SELECT "author"."id", "author"."first_name", "author"."last_name" FROM "author" WHERE "author"."id" IN (1, 2);
همانطور که می بینید، هیچ کوئری اضافی انجام نمی شود. در عوض، ما دو پرس و جو داریم که هر دو جدول را واکشی می کنند و پایتون این جداول را به هم وصل می کند. که البته مزایا و معایب خود را دارد. از نظر کارایی، select_related سریعتر و کارآمدتر است، اما select_related نمی تواند برای ManyToManyField استفاده شود. و این مزیت اصلی prefetch_related است.
روابط ManyToMany نباید با استفاده از SQL مدیریت شوند زیرا بسیاری از مشکلات عملکرد ممکن است هنگام برخورد با جداول بزرگ ظاهر شوند. به همین دلیل است که متد prefetch_related جداول را با پایتون به هم متصل میکند و از ایجاد اتصالات SQL بزرگ جلوگیری میکند.
ارسال نظر