استعلاماتنا كانت تتكاثر كالأرانب: كيف أنقذنا ‘الجلب المتعطش’ (Eager Loading) من جحيم مشكلة N+1؟

يا أهلاً وسهلاً فيكم يا جماعة. اسمي أبو عمر، وبدي أحكي لكم قصة صارت معي ومع فريقي قبل كم سنة، قصة علمتنا درس قاسي لكن مهم عن قواعد البيانات والأداء.

كنا وقتها شغالين على تطبيق اجتماعي جديد، زي فيسبوك بس على صغير. الأمور كانت ماشية تمام، والتطبيق شغال زي الحلاوة في مرحلة التطوير. لكن لما أطلقنا النسخة التجريبية لكم مستخدم، بلشت المشاكل. الخادم (السيرفر) صار بطيء، الصفحات بتحمّل بطلوع الروح، والمستخدمين بلشوا يزهقوا. قعدنا نلف وندور، نفحص الكود ونحلل أداء الخادم، ومش لاقيين سبب واضح. كأن فيه عفريت قاعد بخنّق التطبيق تبعنا.

في ليلة من الليالي، وأنا قاعد سهران مع فنجان القهوة بقلّب في سجلات الخادم (Server Logs)، لاحظت إشي غريب. صفحة عرض المنشورات الرئيسية، اللي المفروض تكون بسيطة، كانت بتولّد مئات، بل آلاف، من استعلامات SQL في الدقيقة الواحدة! مسكت راسي وحكيت: “يا جماعة الخير، شو هاد؟! معقول كل هاد عشان نعرض 20 منشور؟”.

الاستعلامات كانت بتتزايد بشكل مخيف، زي الأرانب اللي بتتكاثر بدون حساب. وهنا كانت لحظة الإدراك… لقد وقعنا في الفخ، فخ اسمه “مشكلة N+1”.

ما هي مشكلة الـ N+1 اللعينة؟

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

عندك جدولين في قاعدة البيانات: جدول posts (المنشورات) وجدول users (المستخدمين). كل منشور إله مؤلف واحد (مستخدم).

الآن، بدك تعرض في صفحة واحدة آخر 20 منشور مع اسم مؤلف كل منشور. الطريقة الساذجة أو “الكسولة” (Lazy) اللي بيقع فيها كثير مبرمجين (واحنا منهم وقتها) هي كالتالي:

  1. الاستعلام الأول (The “1”): بتعمل استعلام واحد عشان تجيب آخر 20 منشور. ممتازة لهون.
  2. الاستعلامات الإضافية (The “N”): بعدها، داخل حلقة تكرار (loop) بتمر على كل منشور من العشرين، وبتروح تعمل استعلام جديد ومنفصل عشان تجيب معلومات المؤلف تبع هاد المنشور بالذات.

النتيجة؟ عشان تعرض 20 منشور، أنت عملت:

1 (لجلب كل المنشورات) + 20 (استعلام واحد لكل منشور لجلب مؤلفه) = 21 استعلام!

ولو كانوا 100 منشور؟ راح تعمل 101 استعلام. ولو ألف؟ 1001 استعلام! هاي هي باختصار “مشكلة N+1”. مشكلة بتخنق قاعدة البيانات وبتقتل أداء تطبيقك بدم بارد.

مثال عملي بالكود (الطريقة الخاطئة)

لو بتستخدم إطار عمل (Framework) مع ORM مثل Laravel أو Django أو Rails، الكود اللي بيسبب المشكلة ممكن يكون شكله هيك (المثال هنا بلهجة Laravel/PHP لكن الفكرة نفسها في كل اللغات):


// 1. جلب آخر 20 منشور
// هذا يولد استعلام واحد: SELECT * FROM posts ORDER BY created_at DESC LIMIT 20;
$posts = Post::latest()->take(20)->get();

// المرور على كل منشور لعرض اسم المؤلف
foreach ($posts as $post) {
    // ☠️ هنا الكارثة! ☠️
    // في كل لفة، يتم تنفيذ استعلام جديد لجلب المؤلف
    // SELECT * FROM users WHERE id = ? LIMIT 1;
    echo $post->author->name;
}

الكود شكله بريء وجميل، لكنه في الحقيقة قنبلة موقوتة. كل ما زاد عدد المنشورات (N)، زاد عدد الاستعلامات بشكل خطي، وهذا هو تعريف الكارثة في عالم أداء التطبيقات.

الحل السحري: ‘الجلب المتعطش’ (Eager Loading) يدخل الحلبة

الحمد لله، لكل مشكلة حل. والحل هنا بسيط وأنيق جدًا، واسمه “الجلب المتعطش” أو Eager Loading. الفكرة عبقرية في بساطتها: بدل ما تكون كسول وتطلب المعلومات عند الحاجة إليها داخل الـ loop، كن “متعطشًا” واطلب كل المعلومات اللي بتحتاجها دفعة واحدة مقدمًا.

لما تستخدم Eager Loading، أنت بتقول للـ ORM: “اسمع يا معلم، أنا بدي أجيب المنشورات، وعارف إني راح أحتاج المؤلفين تبعونهم، فالله يرضى عليك جيب لي إياهم معك من الأول وبطريقتك”.

والـ ORM ذكي، بيعمل الآتي:

  1. الاستعلام الأول: بيجيب كل المنشورات المطلوبة (مثلاً 20 منشور) في استعلام واحد.
  2. الاستعلام الثاني: بياخذ كل أرقام المؤلفين (user_id) من العشرين منشور اللي جابهم، وبيروح يجيب كل هؤلاء المؤلفين في استعلام واحد فقط باستخدام جملة WHERE IN (...).

النتيجة؟ مهما كان عدد المنشورات (N)، سواء 20 أو 100 أو 1000، راح يتم تنفيذ استعلامين اثنين فقط! من N+1 إلى 2. فرق السماء عن الأرض يا جماعة.

نفس المثال، لكن بالطريقة الصح

شوفوا ما أبسط التعديل على الكود ليصبح فعالاً:


// ✨ هنا السحر ✨
// نستخدم دالة `with()` لنخبر لارافيل أن يجلب المؤلفين مع المنشورات
// هذا يولد استعلامين فقط مهما كان عدد المنشورات
$posts = Post::with('author')->latest()->take(20)->get();

// الآن المرور على المنشورات آمن تمامًا
foreach ($posts as $post) {
    // لا يوجد أي استعلام جديد هنا!
    // بيانات المؤلف تم جلبها مسبقًا وربطها بالمنشور في الذاكرة
    echo $post->author->name;
}

الاستعلامات التي سيتم تنفيذها في الخلفية ستكون شبيهة بالآتي:


SELECT * FROM posts ORDER BY created_at DESC LIMIT 20;

SELECT * FROM users WHERE id IN (5, 8, 12, 23, ...); -- قائمة أرقام المؤلفين

هذا التغيير البسيط في الكود هو ما أنقذ تطبيقنا من الانهيار. كأننا شلنا حِمل ثقيل جدًا عن كاهل قاعدة البيانات.

مش بس هيك، فيه كمان! (مواضيع متقدمة)

جمال الـ Eager Loading لا يتوقف هنا. هناك الكثير من الإمكانيات المتقدمة التي تجعله أداة قوية جدًا.

الجلب المتداخل (Nested Eager Loading)

ماذا لو أردت جلب المنشور، ومؤلف المنشور، وبلد المؤلف؟ هل ستعود مشكلة N+1؟ لا طبعًا!

يمكنك استخدام “الصيغة النقطية” (dot notation) لجلب العلاقات المتداخلة.


// جلب المنشورات مع مؤلفيها، مع بلدان المؤلفين
$posts = Post::with('author.country')->get();

هذا سينفذ 3 استعلامات فقط: واحد للمنشورات، واحد للمؤلفين، وواحد للبلدان. رائع، أليس كذلك؟

تقييد الجلب المتعطش (Constraining Eager Loads)

أحيانًا، قد ترغب في جلب علاقة معينة ولكن مع تطبيق شروط عليها. مثلاً، جلب المنشورات مع تعليقاتها “الموافق عليها” فقط.


$posts = Post::with(['comments' => function ($query) {
    $query->where('approved', true);
}])->get();

هذا يعطيك تحكمًا دقيقًا في البيانات التي تجلبها، مما يحسن الأداء أكثر وأكثر.

نصائح من دار أبو عمر 🧔

من خلال خبرتي وتجاربي، جمعت لكم كم نصيحة عملية أتمنى أن تفيدكم:

  • كن شكّاكًا دائمًا: عند بناء أي صفحة تعرض قائمة من الأشياء مع علاقاتها (مقالات ومؤلفوها، منتجات وفئاتها، …)، افترض دائمًا أنك ستواجه مشكلة N+1، واستخدم Eager Loading بشكل استباقي.
  • استخدم أدوات المراقبة: معظم أطر العمل الحديثة توفر أدوات رائعة لمراقبة أداء التطبيق (مثل Laravel Telescope أو Django Debug Toolbar). هذه الأدوات تكشف لك كل الاستعلامات التي يتم تنفيذها في أي طلب (request). استخدمها! هاي الأدوات زي كشّاف بضوي لك على “الحرامية” (الاستعلامات الزايدة) في الكود تبعك.
  • اجلب ما تحتاجه فقط: لا تكن طماعًا في الجلب. إذا كنت تحتاج فقط اسم المؤلف وبريده الإلكتروني، لا تجلب كل بياناته. يمكنك تحديد الأعمدة التي تريدها لتحسين الأداء أكثر.
    Post::with('author:id,name,email')->get();
  • افهم الفرق بين السياقات: مشكلة N+1 هي بالأساس مشكلة “حلقات التكرار” (loops). إذا كنت في صفحة تعرض تفاصيل منشور واحد فقط (/posts/1)، فإن “الجلب الكسول” (Lazy Loading) للمؤلف أو التعليقات ليس مشكلة على الإطلاق، بل قد يكون أفضل أحيانًا. السياق هو الملك.

تذكر دائمًا: “الكسل منيح، بس مش جوا حلقة تكرار!” (الـ Lazy loading مفيد، لكنه كارثي داخل الـ loops).

الخلاصة: لا تخلي استعلاماتك تتكاثر زي الأرانب! 🐰

مشكلة N+1 هي واحدة من أكثر مشاكل الأداء شيوعًا وأسهلها حلًا في تطبيقات الويب. بمجرد أن تفهم المبدأ، يصبح اكتشافها وتصحيحها باستخدام Eager Loading أمرًا روتينيًا.

في النهاية، تعاملك مع قاعدة البيانات يحدد مصير أداء تطبيقك. فكر دائمًا بكمية الشغل اللي بتطلبه منها. عاملها كشريك غالي وعزيز، مش كخادم تحت الطلب بتثقل عليه بدون تفكير. تغيير بسيط في سطر واحد يمكن أن ينقل تطبيقك من حافة الانهيار إلى قمة الأداء.

أتمنى أن تكون هذه القصة والتفاصيل التقنية قد أفادتكم. دمتم مبدعين! 😉

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

الشبكات والـ APIs

كانت تطبيقاتنا تعتمد على التحديث اليدوي: كيف أنقذتنا WebSockets من جحيم ‘الاستقصاء المستمر’ (Polling)؟

مقالة تستعرض تجربة عملية في الانتقال من تقنية الاستقصاء المستمر (Polling) المرهقة إلى استخدام WebSockets لتطبيقات الوقت الحقيقي. اكتشف كيف يمكن لهذا التغيير أن يحسّن...

26 مايو، 2026 قراءة المزيد
الحوسبة السحابية

كانت خوادمنا تلتهم الميزانية وهي خاملة: كيف أنقذتنا الحوسبة بدون خوادم (Serverless) من جحيم الفواتير؟

أنا أبو عمر، مبرمج فلسطيني، وأروي لكم قصتي مع الفواتير السحابية المرتفعة وكيف كانت "الحوسبة بدون خوادم" أو Serverless هي طوق النجاة. اكتشفوا معي كيف...

26 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كان ملفي على GitHub مقبرة للمشاريع: كيف أنقذتني المصادر المفتوحة من جحيم “ليس لديك خبرة عملية”؟

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

26 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

خدماتنا كانت تنتظر في طابور طويل: كيف أنقذتنا ‘طوابير الرسائل’ من جحيم ‘الرجاء الانتظار’؟

أشارككم قصة حقيقية من تجربتي كمبرمج، وكيف كاد مشروعنا أن يفشل بسبب بطء الاستجابة. اكتشفوا معنا كيف غيّرت "طوابير الرسائل" (Message Queues) طريقة عملنا، وحوّلت...

26 مايو، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

من كابوس “أرسل هويتك مجدداً” إلى التحقق الفوري: كيف أنقذنا الذكاء الاصطناعي في عالم الـFintech

كان التحقق من هوية العميل (KYC) عملية يدوية مرهقة تسببت في إحباط العملاء والموظفين. في هذه المقالة، أسرد لكم قصة واقعية من تجربتي كمطور وكيف...

26 مايو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كانت تطبيقاتنا تموت بصمت في الليل: كيف أنقذنا Kubernetes من جحيم ‘إعادة التشغيل اليدوية’؟

أشارككم قصتي كـ"أبو عمر"، مبرمج فلسطيني، وكيف انتقلنا من ليالي الرعب وإعادة تشغيل السيرفرات يدوياً إلى عالم الأتمتة والشفاء الذاتي للتطبيقات باستخدام Kubernetes. مقالة عملية...

26 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

كان كل خطأ كارثة شخصية: كيف أنقذتنا ‘السلامة النفسية’ من جحيم ‘إخفاء الأخطاء’؟

أنا أبو عمر، مبرمج فلسطيني، وأروي لكم كيف انتقلنا من بيئة عمل كان فيها الخطأ البرمجي وصمة عار، إلى ثقافة "السلامة النفسية" التي حولت الأخطاء...

26 مايو، 2026 قراءة المزيد
اختبارات الاداء والجودة

كان إطلاقنا رهاناً محفوفاً بالمخاطر: كيف أنقذتنا اختبارات التحمل (Load Testing) من جحيم ‘هل سيصمد الخادم؟’

أشارككم قصة حقيقية من قلب المعركة التقنية، حيث كان إطلاق منتجنا الجديد على المحك. لولا اختبارات التحمل (Load Testing) وأدوات مثل k6، لكنا غرقنا في...

26 مايو، 2026 قراءة المزيد
البودكاست