تكرار السؤال: …
الإجابة المختصرة
تحدث N+1 عندما تجلب قائمة كائنات باستعلام واحد ثم يُنفذ استعلام إضافي لكل كائن عند الوصول إلى علاقته. يحلها select_related للعلاقات ForeignKey و OneToOne عبر JOIN، و prefetch_related للعلاقات المتعددة عبر استعلام ثانٍ واحد يُربط في Python.
الشرح
الـ QuerySet في Django كسول؛ الوصول إلى order.customer داخل حلقة يطلق استعلامًا لكل طلب إذا لم تُحمّل العلاقة مسبقًا. استخدم select_related عندما تكون العلاقة إلى كائن واحد، و prefetch_related للعلاقات من نوع many-to-many أو العكسية، ويمكن تخصيص الاستعلام المسبق بـ Prefetch. اكتشف المشكلة بأدوات مثل django-debug-toolbar، أو بالتحقق من عدد الاستعلامات في الاختبارات عبر assertNumQueries.
مثال بالكود
# N+1: استعلام للطلبات + استعلام لكل عميل
for order in Order.objects.all():
print(order.customer.name)
# استعلام واحد مع JOIN
for order in Order.objects.select_related("customer"):
print(order.customer.name)
# استعلامان فقط مهما كان العدد
authors = Author.objects.prefetch_related("books")
class OrderTests(TestCase):
def test_list_queries(self):
with self.assertNumQueries(1):
list(Order.objects.select_related("customer"))أخطاء شائعة
- استخدام select_related مع علاقة many-to-many (لا يعمل؛ المطلوب prefetch_related).
- استدعاء filter على علاقة مُحمّلة مسبقًا داخل الحلقة فيُلغى أثر الـ prefetch ويُطلق استعلام جديد.