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

صباح يوم ثلاثاء عادي، فنجان القهوة على مكتبي قد برد، وأنا أغوص في الشيفرة المصدرية لمشروع جديد. فجأة، يرن هاتفي. على الطرف الآخر، صوت “خليل”، المبرمج الشاب في فريقنا، يملؤه التوتر: “أبو عمر، الحقنا! في مصيبة!”.

سألته بهدوء، وأنا أحاول أن أمتص حالة الهلع التي هو فيها: “خيْر يا خليل؟ شو في؟ روّق واحكيلي”.

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

أغمضت عيني للحظة، وابتسمت ابتسامة خفيفة. لقد مررت بهذا الفيلم من قبل. قلت له: “اسمعني يا خليل، جيب كاسة شاي وتعال على مكتبي. شكلي عارف شو القصة… هاي شكلها قصة ‘سباق بيانات’ من اللي بحبها القلب، ورهاني على صحن كنافة نابلسية إنه الحل في قاعدة البيانات نفسها”.

وبالفعل، كانت تلك الحادثة هي المدخل الذي جعل فريقنا بأكمله يدرك أهمية وحشٍ خفيّ يعيش في قواعد البيانات اسمه “مستويات العزل” أو Isolation Levels. دعوني أحكي لكم ما تعلمناه في ذلك اليوم العصيب.

ما هو جحيم “سباق البيانات” أو الـ Race Condition؟

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

ماذا يحدث في الكواليس؟

  1. تطبيق أحمد يقرأ من قاعدة البيانات: “كم قطعة متبقية؟”. الجواب: 1.
  2. في نفس الملي-ثانية، تطبيق فاطمة يقرأ من قاعدة البيانات: “كم قطعة متبقية؟”. الجواب: 1.
  3. تطبيق أحمد يقول: “ممتاز، قطعة واحدة تكفي”، فيقوم بإنقاص العدد إلى 0 ويُتمم عملية الشراء.
  4. تطبيق فاطمة يقول: “رائع، قطعة واحدة متاحة”، فيقوم هو الآخر بإنقاص العدد إلى -1 ويُتمم عملية الشراء!

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


-- الكود الكارثي الذي كان لدينا (مثال مبسط)
-- العملية رقم 1 (أحمد)
BEGIN;
SELECT stock_count FROM products WHERE id = 123; -- يقرأ القيمة 1
-- هنا، في هذه اللحظة، تبدأ العملية رقم 2
UPDATE products SET stock_count = stock_count - 1 WHERE id = 123; -- يصبح الرصيد 0
COMMIT;

-- العملية رقم 2 (فاطمة)
BEGIN;
SELECT stock_count FROM products WHERE id = 123; -- تقرأ القيمة 1 أيضاً!
UPDATE products SET stock_count = stock_count - 1 WHERE id = 123; -- يصبح الرصيد -1
COMMIT;

المنقذ: المعاملات أو الـ Transactions

قبل أن نتحدث عن الحل، يجب أن نفهم الأداة الأساسية التي نستخدمها: المعاملات (Transactions). المعاملة هي ببساطة مجموعة من الأوامر التي يجب تنفيذها معًا كوحدة واحدة. القاعدة بسيطة: “يا بتنفذ كل الأوامر بنجاح، يا ولا إشي منهم بتنفذ”. هذا المبدأ يسمى Atomicity أو “الذرية”، وهو أحد أعمدة خصائص ACID الأربعة التي تضمن موثوقية قواعد البيانات.

لكن المعاملات وحدها لا تكفي لحل مشكلة التزامن (Concurrency). نعم، هي تضمن أن عملية أحمد إما أن تكتمل أو تفشل بالكامل، وكذلك عملية فاطمة. لكنها لا تمنعهما من التداخل بطريقة فوضوية كما رأينا. هنا يأتي دور بطل قصتنا الحقيقي.

لب المشكلة: ظواهر التزامن الشاذة

“العزل” (Isolation) هو الحرف ‘I’ في كلمة ACID. وهو يعني أن كل معاملة يجب أن تعمل بمعزل عن الأخرى، كما لو كانت هي الوحيدة التي تعمل على قاعدة البيانات في تلك اللحظة. لكن تحقيق العزل الكامل له تكلفة في الأداء. لذلك، توفر لنا أنظمة قواعد البيانات “مستويات” مختلفة من العزل، كل مستوى يمنع أنواعًا معينة من “الظواهر الشاذة” ولكنه قد يسمح بأنواع أخرى.

لنفهم هذه الظواهر:

h3: القراءات القذرة (Dirty Reads)

تحدث عندما تقرأ معاملةٌ بياناتٍ قامت معاملةٌ أخرى بتعديلها ولكنها لم تقم بتثبيت التغيير بعد (لم تعمل COMMIT). لو قامت المعاملة الثانية بالتراجع (ROLLBACK)، ستكون المعاملة الأولى قد قرأت بيانات “وهمية” لم تكن موجودة أبدًا. مثل أن تقرأ مسودة رسالة قبل إرسالها ثم يتم حذفها.

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

تحدث عندما تقرأ معاملةٌ صفًا معينًا، ثم بعد قليل، تقرأ نفس الصف مرة أخرى فتجده قد تغير، لأن معاملة أخرى قامت بتعديله وتثبيت التغيير. هذا يكسر مبدأ “تكرار القراءة” داخل نفس المعاملة.

h3: القراءات الشبحية (Phantom Reads)

تشبه السابقة، لكنها أكثر خبثًا. هنا، تقوم معاملة بتنفيذ استعلام يعيد مجموعة من الصفوف (مثلاً، “كل المنتجات التي سعرها أقل من 100 دولار”). ثم بعد قليل، تعيد تنفيذ نفس الاستعلام بالضبط، فتجد صفوفًا جديدة “شبحية” قد ظهرت، لأن معاملة أخرى أضافت منتجات جديدة تطابق هذا الشرط.

الحل السحري: مستويات العزل في خدمتك!

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

h3: المستوى الأول: Read Uncommitted (القراءة غير الملتزم بها)

هذا هو “الغرب المتوحش”. لا حماية تقريبًا. يسمح بحدوث كل الظواهر الشاذة التي ذكرناها (Dirty, Non-repeatable, Phantom reads).
متى نستخدمه؟ نادرًا جدًا. ربما في عمليات الإحصاء التي لا تتطلب دقة متناهية، حيث الأداء هو الأهم.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

h3: المستوى الثاني: Read Committed (القراءة الملتزم بها)

هذا هو المستوى الافتراضي في معظم قواعد البيانات الشهيرة مثل PostgreSQL و SQL Server. إنه حل وسط جيد.
ماذا يمنع؟ يمنع الـ Dirty Reads. لن تقرأ أبدًا بيانات لم يتم تثبيتها.
ماذا يسمح؟ لا يزال يسمح بـ Non-repeatable و Phantom Reads.
نصيحة أبو عمر: هذا هو صديقك الصدوق ونقطة البداية في 90% من الحالات. لا تغيره إلا لسبب وجيه.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

h3: المستوى الثالث: Repeatable Read (القراءة القابلة للتكرار)

هنا تبدأ الأمور بالتحسن بشكل ملحوظ. يضمن لك هذا المستوى أن أي صف تقرأه سيبقى كما هو طوال مدة معاملتك.
ماذا يمنع؟ يمنع الـ Dirty Reads والـ Non-repeatable Reads.
ماذا يسمح؟ لا يزال قد يسمح بـ Phantom Reads في بعض الأنظمة.
متى نستخدمه؟ عندما تحتاج معاملتك لقراءة نفس البيانات عدة مرات وتحتاج ضمان أنها لم تتغير.

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

h3: المستوى الرابع: Serializable (التسلسلي)

هذا هو “الحصن المنيع” وأعلى مستويات العزل. يضمن أن نتيجة تنفيذ المعاملات المتزامنة هي نفسها كما لو تم تنفيذها واحدة تلو الأخرى (بشكل متسلسل).
ماذا يمنع؟ يمنع كل الظواهر الشاذة: Dirty, Non-repeatable, و Phantom Reads.
ماذا يسمح؟ لا يسمح بأي شذوذ، لكن هذا يأتي على حساب الأداء. تستخدم قاعدة البيانات تقنيات قفل (Locking) صارمة، مما يقلل من قدرة النظام على تنفيذ عمليات متزامنة.
متى نستخدمه؟ في العمليات الحرجة التي لا تحتمل أي خطأ. مثل قصتنا في بداية المقال: تحديث المخزون، التحويلات المالية، أي عملية “اقرأ ثم عدّل” حرجة.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

في قصتنا، مشكلة “سباق البيانات” على المخزون كانت مثالاً كلاسيكيًا يتطلب مستوى عزل يمنع حدوثه. مستوى `Read Committed` الافتراضي لم يكن كافيًا. كان الحل هو رفع مستوى العزل للمعاملة التي تحدث المخزون إلى `Repeatable Read` أو الأفضل من ذلك، `Serializable`.

نصائح أبو عمر العملية

  • لا تستخدم المطرقة لقتل ذبابة: لا تذهب مباشرة إلى `Serializable` لكل شيء. ابدأ دائمًا بالمستوى الافتراضي لقاعدة بياناتك (غالبًا `Read Committed`). قم بتحليل عملياتك، وإذا وجدت حالة سباق بيانات حقيقية، عندها فقط ارفع مستوى العزل لتلك المعاملة المحددة. “ما تروحش ترمي حالك على الـ Serializable من أولها، روّق يا خال!”.
  • اعرف عدوك (وقاعدة بياناتك): ليست كل قواعد البيانات تطبق هذه المستويات بنفس الطريقة. على سبيل المثال، MySQL باستخدام محرك InnoDB، مستواه الافتراضي هو `Repeatable Read`، والذي يمنع أيضًا Phantom Reads في معظم الحالات بفضل آلية تسمى (MVCC). اقرأ توثيق قاعدة البيانات التي تستخدمها جيدًا.
  • قِس، لا تخمّن: قبل وبعد تغيير مستوى العزل، قم بقياس أداء النظام تحت ضغط. قد يحل تغيير المستوى مشكلة دقة البيانات، ولكنه قد يخلق مشكلة اختناق في الأداء. الموازنة هي مفتاح النجاح.
  • فكر في حلول أخرى: أحيانًا، يمكن حل المشكلة بطرق أخرى غير رفع مستوى العزل، مثل استخدام `SELECT … FOR UPDATE` الذي يقوم بقفل الصفوف التي تقرأها لمنع التعديل عليها، أو استخدام تقنيات القفل المتفائل (Optimistic Locking).

الخلاصة… والزبدة ☕️

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

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

أبو عمر

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

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

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

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

آخر المدونات

تسويق رقمي

ميزانيتنا التسويقية كانت ثقباً أسود: كيف أنقذنا ‘نموذج الإحالة المبني على البيانات’ من جحيم إهدار المال؟

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

2 مايو، 2026 قراءة المزيد
الشبكات والـ APIs

كانت نقرة المستخدم المزدوجة تكلفنا آلاف الدولارات: كيف أنقذتنا مفاتيح ‘Idempotency’ من جحيم الطلبات المكررة؟

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

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

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

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

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

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

أشارككم تجربتي كـ "أبو عمر"، مبرمج فلسطيني، مع الفوضى التي تسببها إدارة السيرفرات اليدوية. سنكتشف معًا كيف حولت أداة Terraform بنيتنا التحتية من قصر ورقي...

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