أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

حلّ N+1 Query في ORMs: من 200ms إلى 20ms بأمر واحد

📅 ٢٥ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
حلّ N+1 Query في ORMs: من 200ms إلى 20ms بأمر واحد

لو الـ API بتاعك بياخد 218ms في endpoint بسيط بيرجّع قائمة بـ 50 منتج، المشكلة غالبًا مش في السيرفر ولا الـ DB. المشكلة اسمها N+1 query problem، والحل أمر واحد بيوفّر 90% من الوقت.

N+1 Query Problem: المشكلة الصامتة في كل ORM

رفوف خوادم قاعدة بيانات تمثل مشكلة كثرة الاستعلامات N+1 في ORM

المشكلة باختصار

لما تجيب 50 منتج من قاعدة البيانات، وكل منتج يطلب صنفه (category) في query منفصل، أنت بتعمل 51 query بدل query واحد. ده اسمه N+1: استعلام أساسي + N استعلام فرعي.

الـ ORMs (Sequelize، Prisma، Django ORM، ActiveRecord) بتخفي ده عنك. الكود بيبان نظيف وبسيط، لكن في الخلفية فيه طوفان queries بيضرب الـ DB ويأكل الـ latency.

مثال للمبتدئين: تخيّل إنك في مكتبة

عندك قائمة بـ 50 كتاب، وعايز تعرف اسم المؤلف لكل واحد. الطريقة الغلط: تروح لأمين المكتبة، تطلب الكتاب الأول، يفتح حاسوبه، يجيبلك اسم المؤلف، ترجع لمكتبك، تطلب الكتاب التاني، وهكذا. بتعمل 50 رحلة ذهاب وإياب.

الطريقة الصح: تقول للأمين "هاتلي الـ 50 كتاب بأسامي مؤلفينهم مرة واحدة". رحلة واحدة، نتيجة كاملة.

ده بالظبط الفرق بين N+1 query وeager loading. كل round trip للـ DB تكلفته شبكة + parsing + execution، فلما تعملها 51 مرة بدل مرة، الفرق بيوصل لأكتر من ضعف ونص.

التعريف العلمي الدقيق

N+1 query problem هو نمط استعلام ينتج عن استعلام أساسي يُرجع N سجل، يتبعه تنفيذ N استعلام إضافي لجلب بيانات مرتبطة لكل سجل على حدة. النتيجة الكلية N+1 round trip للـ database بدل round trip واحد. كل round trip بيضيف latency شبكي + parsing overhead + execution، حتى لو كل query منهم بسيط وسريع منفردًا.

مثال كود حقيقي بالأرقام

ده كود Sequelize بيعاني من N+1 بشكل كلاسيكي:

JavaScript
// 51 query: 1 للمنتجات + 50 للأصناف
const products = await Product.findAll({ limit: 50 });
for (const p of products) {
  const category = await p.getCategory(); // كل iteration = query جديد
  console.log(p.name, category.name);
}

الـ profiling على PostgreSQL محلي مع جدولين فيهم index على foreign key:

  • عدد queries: 51
  • زمن إجمالي: 218ms
  • زمن round trips الشبكي: 165ms
  • P95 على staging مع latency 2ms للـ DB: 410ms

الحل: eager loading عبر include بيخلي الـ ORM يبني JOIN واحد:

JavaScript
// query واحد بـ JOIN
const products = await Product.findAll({
  limit: 50,
  include: [{ model: Category }]
});
for (const p of products) {
  console.log(p.name, p.Category.name);
}

بعد التعديل على نفس البيانات ونفس الجهاز:

  • عدد queries: 1
  • زمن إجمالي: 22ms
  • تحسّن: حوالي 90%
  • الأرقام دي تقديرية لبيئة محلية، الفرق على بيئة prod مع latency أعلى بيكون أكبر.

شاشة طرفية تعرض سجل استعلامات ORM متكررة تكشف نمط N+1

نفس الفكرة في Prisma

JavaScript
// قبل: N+1
const products = await prisma.product.findMany({ take: 50 });
for (const p of products) {
  const cat = await prisma.category.findUnique({ where: { id: p.categoryId } });
}

// بعد: include
const products = await prisma.product.findMany({
  take: 50,
  include: { category: true },
});

في Django ORM الأمر اسمه select_related للـ foreign key وprefetch_related للـ many-to-many. في ActiveRecord الأمر includes.

ازاي تكتشف N+1 في كودك

  1. فعّل log الـ queries في الـ ORM. في Sequelize: { logging: console.log } في الـ config.
  2. افتح endpoint واحد، عدّ الـ queries في الـ console.
  3. لو endpoint بسيط دخل أكتر من 5 queries، فيه N+1 على الأرجح.
  4. على PostgreSQL استخدم pg_stat_statements تشوف الـ queries المتكررة بنفس الـ shape بأرقام مختلفة.
  5. أدوات APM زي New Relic أو Datadog بتظهرلك "N+1 detected" مباشرة لو مفعّلة.

الـ trade-offs اللي لازم تعرفها

eager loading مش مجاني. بتكسب round trips أقل، بتخسر:

  • حجم نتيجة أكبر في الـ query الواحد، خصوصًا لو الـ JOIN على one-to-many (الصفوف بتتكرّر جزئيًا).
  • memory أعلى على السيرفر لو الـ relation فيها أعمدة كبيرة (TEXT، JSON).
  • complexity في الـ query plan لو الـ JOIN بقى على 4+ جداول. ممكن الـ planner يختار خطة أسوأ.

الافتراض هنا: N صغير نسبيًا (≤ 1000 سجل في الـ batch)، وعندك indexes صح على الـ foreign keys. لو N = 100K، الحل مش include، الحل pagination + cursor + dataloader pattern.

متى لا تستخدم eager loading

مش كل N+1 لازم يتحل، وفيه حالات الـ include بيضرّ أكتر ما ينفع:

  • الـ relation بتُستخدم في 10% من الـ requests فقط: lazy أوفر للذاكرة وللـ DB.
  • الجدول المرتبط فيه أعمدة TEXT/JSON كبيرة ومش هتعرضها كلها: استخدم attributes أو select لاختيار أعمدة بعينها بدل eager loading كامل.
  • عندك caching layer قوي قدام الـ DB والـ N+1 كله بيضرب الـ cache في O(1).
  • الـ relation polymorphic (سجل ممكن يرتبط بأكتر من جدول): JOIN ساعتها معقد، dataloader أنضف.

الخطوة التالية

افتح أبطأ endpoint عندك دلوقتي، فعّل query logging على staging لمدة 5 دقائق، وعدّ كم query بيتنفّذ في request واحد. لو الرقم أكبر من 10، عندك N+1 شغّال. ابدأ بأكبر استعلام لـ list endpoint وطبّق include أو select_related حسب الـ ORM بتاعك، وقيس قبل وبعد. لو الفرق أقل من 30% راجع الـ indexes على الـ foreign key قبل ما تكمل تحسينات تانية.

المصادر

  • Sequelize Eager Loading: sequelize.org/docs/v6/advanced-association-concepts/eager-loading
  • Prisma Query Optimization & N+1: prisma.io/docs/orm/prisma-client/queries/query-optimization-performance
  • Django select_related & prefetch_related: docs.djangoproject.com/en/stable/ref/models/querysets
  • Active Record Eager Loading Guide: guides.rubyonrails.org/active_record_querying
  • PostgreSQL pg_stat_statements: postgresql.org/docs/current/pgstatstatements.html
  • DataLoader pattern (GraphQL Foundation): github.com/graphql/dataloader

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة