كانت بياناتنا تتلف في سباق محموم: كيف أنقذتنا ‘مستويات عزل المعاملات’ من جحيم القراءات الشبحية؟

السلام عليكم يا جماعة الخير، معكم أخوكم أبو عمر.

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

فجأة، بلشت توصلنا شكاوى غريبة: عميل بحكي إنه حجز آخر غرفة في فندق، ودفع وكل شي تمام، وبعدها بشوي يرجع يلاقي الغرفة متاحة للحجز مرة تانية! وعميل تاني بحكي إنه نظامنا سمحله يحجز تذكرة طيران على مقعد كان أصلاً محجوز. صارت “عجقة” ما يعلم فيها إلا ربنا. البيانات كانت بتتلف قدام عينينا، والتقارير بتطلع أرقامها غلط، وإحنا مش فاهمين شو اللي بصير. الكود منطقي، والعمليات الحسابية صحيحة، طيب وين المشكلة؟

قعدنا ليلة كاملة، والقهوة شغالة والعيون حمرا من التعب، بنحلل سجلات قاعدة البيانات (Logs). لاحظنا إنه المشاكل بتصير لما يكون في أكثر من مستخدم بحاولوا يحجزوا بنفس اللحظة تقريباً. هنا ضربت معي! تذكرت كابوس قديم واجهته في بداية مسيرتي البرمجية. جمعت الشباب وحكيتلهم: “يا جماعة، مشكلتنا مش في الكود، مشكلتنا في السباق! سباق محموم بين المعاملات (Transactions) داخل قاعدة البيانات، وفي واحد منهم دايماً بيخسر وبيخرب الدنيا كلها”.

هذا الموقف علّمني درس قاسي لكنه ثمين: فهم كيفية تعامل قاعدة البيانات مع الطلبات المتزامنة (Concurrency) هو أساس بناء أي تطبيق ناجح. ومن قلب هذه المعركة، وُلدت هذه المقالة لأشارككم السلاح السري الذي أنقذنا: مستويات عزل المعاملات (Transaction Isolation Levels).

ما هي المعاملات (Transactions) أصلاً؟ وليش لازم نهتم فيها؟

قبل ما نغوص في الأعماق، خلينا نرجع خطوة للأساس. تخيل إنك بدك تحوّل مبلغ من حسابك البنكي لحساب شخص آخر. العملية هاي بتتكون من خطوتين رئيسيتين:

  1. خصم المبلغ من حسابك.
  2. إضافة نفس المبلغ لحساب الشخص الآخر.

شو بصير لو النظام خصم المبلغ من حسابك، وقبل ما يضيفه للحساب التاني، انقطعت الكهربا أو صار عطل في الخادم؟ مصيبة! الفلوس اختفت في الهواء. هنا بيجي دور “المعاملة” أو الـ Transaction.

المعاملة هي مجموعة من العمليات اللي لازم تتنفذ كلها كوحدة واحدة، أو ما يتنفذ منها ولا إشي. يا إما “الكل أو لا شيء” (All or Nothing). هذا المبدأ هو جزء من مجموعة مبادئ مقدسة في عالم قواعد البيانات اسمها ACID:

  • Atomicity (الذرية): المعاملة وحدة لا تتجزأ. يا كلها بتنجح، يا كلها بتفشل وبترجع الأمور زي ما كانت.
  • Consistency (الاتساق): المعاملة لازم تنقل قاعدة البيانات من حالة سليمة إلى حالة سليمة أخرى. ما بصير المعاملة تترك وراها بيانات “خربانة” أو غير منطقية.
  • Isolation (العزل): وهذا هو بطل قصتنا اليوم. المعاملات اللي بتشتغل بنفس الوقت لازم تكون معزولة عن بعضها، كأن كل وحدة فيهم شغالة لحالها في عالم موازي.
  • Durability (الاستمرارية): لما المعاملة تنجح وتتثبت (commit)، نتايجها لازم تكون دائمة ومحفوظة حتى لو صار فشل في النظام بعدها.

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

الكابوس: لما تتداخل المعاملات وتصير “العجقة”

لما يكون العزل مش مثالي، بتظهر مشاكل بنسميها “ظواهر التزامن” (Concurrency Phenomena). خلينا نتعرف على أشهر ثلاث كوابيس ممكن تواجه أي مطور:

القراءات القذرة (Dirty Reads): لما تقرأ كلام لسا ما توافق عليه

تخيل معاملة (أ) بتعدّل سعر منتج من 100 إلى 80، لكنها لسا ما ثبتت التغيير (لم تقم بـ COMMIT). في نفس اللحظة، بتيجي معاملة (ب) وبتقرأ السعر الجديد (80). فجأة، معاملة (أ) بتقرر التراجع عن التغيير (ROLLBACK). الآن معاملة (ب) صار معها معلومة “قذرة” أو غير صحيحة، لأن السعر الحقيقي رجع 100. لو بنت (ب) قرار على هاي المعلومة، بتكون بنت قرارها على وهم.

القراءات غير القابلة للتكرار (Non-Repeatable Reads): لما تتغير الحقايق تحت رجليك

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

القراءات الشبحية (Phantom Reads): الأشباح اللي بتظهر فجأة!

وهذا هو الكابوس اللي واجهناه في مشروعنا. تخيل معاملة (أ) بتنفذ استعلام عشان تجيب “كل الغرف المتاحة في فندق معين”. النتيجة كانت 5 غرف. في نفس الوقت، بتيجي معاملة (ب) وبتضيف حجز جديد على نفس الفندق (يعني بتخلي غرفة من هدول مش متاحة)، أو بالعكس، عميل بيلغي حجزه فبتصير في غرفة جديدة متاحة. الآن لو رجعت معاملة (أ) ونفذت نفس الاستعلام مرة تانية، النتيجة رح تتغير! رح يظهر “شبح” (غرفة جديدة) أو يختفي صف كان موجود. مشكلتنا كانت إنه نظام التقارير عنا كان يقرأ عدد الغرف المتاحة، وبناءً عليه يسمح بالحجز، لكن قبل ما يكمل عملية الحجز، كان “شبح” جديد يظهر (عميل آخر يلغي حجزه)، فيصير عنا تقرير غلط وحجز غلط.

المنقذ: مستويات العزل (Isolation Levels) وكيف بتشتغل

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

1. Read Uncommitted: الفوضى الخلاقة (أو بس فوضى)

  • ماذا يمنع: لا شيء.
  • ماذا يسمح به: Dirty Reads, Non-Repeatable Reads, Phantom Reads.
  • شرح بسيط: هذا المستوى هو أضعفهم على الإطلاق. كأنك بتقول لقاعدة البيانات: “اسمعي، ما بدي أي نوع من الحماية، خلي كل المعاملات تشوف شغل بعض حتى لو لسا ما خلص”. هو الأسرع في الأداء لكنه الأخطر على سلامة البيانات.

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

2. Read Committed: الحل الشائع والمنطقي (بس مش دايماً)

  • ماذا يمنع: Dirty Reads.
  • ماذا يسمح به: Non-Repeatable Reads, Phantom Reads.
  • شرح بسيط: هذا هو المستوى الافتراضي في معظم قواعد البيانات الشهيرة (مثل PostgreSQL و SQL Server). فكرته بسيطة: ما بسمحلك تقرأ أي تغيير إلا بعد ما صاحبه يعمل COMMIT. هذا بحل مشكلة “القراءات القذرة”، لكنه ما بحل مشكلة إنه البيانات ممكن تتغير تحت رجليك (Non-Repeatable Read) أو تظهر أشباح (Phantom Read).

-- في SQL Server أو PostgreSQL، هذا هو الوضع الطبيعي
-- لكن لو أردت ضبطه بشكل صريح:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

BEGIN TRANSACTION;
-- عملياتك هنا...
COMMIT;

3. Repeatable Read: عشان تضمن قراءاتك ما تتغير

  • ماذا يمنع: Dirty Reads, Non-Repeatable Reads.
  • ماذا يسمح به: Phantom Reads.
  • شرح بسيط: هذا المستوى أقوى من اللي قبله. بيضمنلك إنه لو قرأت صف معين، رح يضل بنفس القيمة طول فترة معاملتك. قاعدة البيانات بتحقق هاد الشي عن طريق “قفل” (Lock) الصفوف اللي بتقرأها عشان ما حدا تاني يقدر يعدّل عليها لحد ما تخلص معاملتك. لكنه ما بحميك من الأشباح! لأنه ما بقفل “العدم”، فلو معاملة تانية أضافت صف جديد كلياً، رح يظهرلك في القراءة التالية.

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

BEGIN TRANSACTION;
SELECT * FROM products WHERE id = 10;
-- ... بعض العمليات الأخرى
-- لو نفذت نفس الاستعلام مرة أخرى، النتيجة مضمونة لن تتغير
SELECT * FROM products WHERE id = 10;
COMMIT;

4. Serializable: الحصن المنيع (بس إله ثمن)

  • ماذا يمنع: Dirty Reads, Non-Repeatable Reads, Phantom Reads.
  • ماذا يسمح به: لا شيء. إنه أقوى حصن.
  • شرح بسيط: هذا هو البطل اللي أنقذنا! هذا المستوى بيعزل المعاملات عن بعضها بشكل كامل، كأن كل معاملة بتشتغل بالدور، وحدة ورا التانية. بيمنع كل المشاكل، بما فيها “القراءات الشبحية”. قاعدة البيانات بتحقق هاد الشي عن طريق استخدام أقفال أكثر صرامة، ممكن توصل لقفل جداول كاملة أو نطاقات من الصفوف (Range Locks) عشان تمنع أي حدا يضيف “شبح” جديد في المنطقة اللي أنت بتشتغل عليها.
  • الثمن: الأداء. لأنه بيزيد من احتمالية الانتظار بين المعاملات، وممكن يسبب ما يسمى بـ “الجمود” (Deadlocks) لو ما انكتب الكود بحذر.

في حالتنا، الحل كان بسيط جداً في تطبيقه، لكنه غيّر كل شي. عدّلنا الكود اللي بينفذ عملية الحجز والتقرير ليصبح كالتالي:


-- استخدام PostgreSQL كمثال
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 1. اقرأ عدد الغرف المتاحة في الفندق X
SELECT count(*) FROM rooms WHERE hotel_id = 123 AND is_available = TRUE;

-- 2. قم ببعض العمليات المنطقية بناءً على العدد

-- 3. إذا كان هناك غرفة متاحة، قم بتحديثها لتصبح غير متاحة
UPDATE rooms SET is_available = FALSE WHERE id = (
    SELECT id FROM rooms 
    WHERE hotel_id = 123 AND is_available = TRUE 
    LIMIT 1 
    FOR UPDATE -- قفل الصف للحجز
);

-- 4. أضف سجل الحجز في جدول آخر
INSERT INTO bookings (room_id, customer_id) VALUES (45, 789);

COMMIT;

بمجرد تطبيق `ISOLATION LEVEL SERIALIZABLE`، قاعدة البيانات صارت تضمن إنه لو في معاملتين حاولوا يعملوا هاي العملية بنفس اللحظة، وحدة منهم رح تنجح، والتانية رح تفشل وتستقبل خطأ (Serialization Failure). وقتها، التطبيق عنا كان مبرمج إنه يعيد محاولة المعاملة الفاشلة تلقائياً. وبهيك، ضمنا إنه ما في أي حجوزات مزدوجة أو بيانات فاسدة. كانت لحظة انتصار حقيقية! ✅

نصائح من أبو عمر: كيف تختار المستوى المناسب؟

بعد كل هالحكي، السؤال اللي بيطرح نفسه: أي مستوى أختار لمشروعي؟

  • ابدأ بالافتراضي: القاعدة الذهبية هي: “ما تروحش للمدفع عشان تقتل ناموسة”. ابدأ دايماً بالمستوى الافتراضي لقاعدة بياناتك (غالباً `Read Committed`). هذا المستوى بيعطي توازن جيد جداً بين الأداء وسلامة البيانات لمعظم التطبيقات العادية (مدونات، مواقع محتوى، متاجر بسيطة).
  • حلل متطلباتك بدقة: إذا كان تطبيقك حساس جداً للبيانات (مثل التطبيقات المالية، أنظمة الحجوزات، إدارة المخزون)، رح تحتاج لمستوى عزل أعلى. اسأل نفسك: “هل من المقبول أن يقرأ المستخدم بيانات قد تتغير بعد ثانية؟” إذا كان الجواب لا، فأنت بحاجة على الأقل لـ `Repeatable Read`. “هل من المقبول أن تظهر صفوف جديدة في وسط عملية معقدة؟” إذا كان الجواب لا، فأنت بحاجة لـ `Serializable`.
  • استخدم العزل العالي للمعاملات الحرجة فقط: مش كل التطبيق لازم يشتغل على `Serializable`. يمكنك تحديد مستوى العزل لمعاملة معينة فقط، وترك باقي التطبيق على المستوى الافتراضي. هذا بيعطيك الحماية عند الحاجة، بدون ما تضحي بالأداء في كل مكان.
  • افهم كيف يتعامل نظامك مع الفشل: عند استخدام `Serializable`، لازم تكون مستعد للتعامل مع أخطاء `Serialization Failure`. تطبيقك لازم يكون قادر على “التقاط” هذا الخطأ وإعادة محاولة المعاملة مرة أخرى.
  • اختبر، ثم اختبر، ثم اختبر: لا تعتمد على التنظير. استخدم أدوات اختبار الضغط (Load Testing) لمحاكاة مئات أو آلاف المستخدمين المتزامنين وشوف كيف بيتصرف تطبيقك تحت كل مستوى عزل. الأرقام لا تكذب.

الخلاصة: الحكي اللي على بلاطة

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

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

خليكم فضوليين، ولا تتوقفوا عن التعلم. 🤔

أبو عمر

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

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

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

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

آخر المدونات

تجربة المستخدم والابداع البصري

كانت واجهاتنا خليطاً عشوائياً: كيف أنقذنا ‘نظام التصميم’ (Design System) من جحيم الفوضى البصرية؟

أسرد لكم قصتي كـ "أبو عمر"، مطور برمجيات فلسطيني، وكيف واجهنا فوضى بصرية عارمة في مشاريعنا. اكتشفوا معنا كيف كان "نظام التصميم" (Design System) هو...

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

كانت قاعدة بياناتنا تحتضر: كيف أنقذنا ‘التخزين المؤقت’ (Caching) من جحيم الاستعلامات المتكررة؟

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

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

كانت عمليات الاحتيال تسبقنا بخطوة: كيف أنقذتنا ‘نماذج اكتشاف الشذوذ’ من جحيم القواعد الثابتة؟

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

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

كانت بنيتنا التحتية قصرًا من ورق: كيف أنقذنا Terraform من جحيم التغييرات اليدوية وانحراف الإعدادات؟

قصة حقيقية من قلب المعركة التقنية، حيث كانت سيرفراتنا تتهاوى بسبب التعديلات اليدوية. اكتشف كيف انتقلنا من الفوضى إلى النظام باستخدام Terraform ومفهوم البنية التحتية...

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

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

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

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

كانت اختباراتنا تنهار عشوائياً: كيف أنقذنا Playwright من جحيم الاختبارات المتقشرة (Flaky Tests)؟

في هذه المقالة، أشارككم قصة حقيقية من خنادق البرمجة، حيث كانت اختباراتنا الآلية تنهار بشكل عشوائي ومحبط. سأغوص معكم في الأسباب الجذرية للاختبارات المتقشرة (Flaky...

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