بنناجح
المرحلة 1: المشكلة والنظرية · الدرس 1 من 3 · 20 دقيقة

ليه التزامن مشكلة؟

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

في الدرس ده هتتعلم
  • تعرّف الـ Transaction والـ Isolation بدقة
  • تفهم يعني إيه Schedule، وليه الداتابيز مش بتشغّل الترانزاكشنز ورا بعض
  • تشوف بعينك أول مشكلة: Lost Update، وتعرف 3 طرق تمنعها
  • تفرّق بين العيلتين الكبار: Pessimistic و Optimistic

1. الـ Transaction

مثال: تحويل 100 جنيه من حساب علي لحساب منى عبارة عن عمليتين: خصم من علي، وإضافة لمنى. لو السيرفر وقع بينهم، الفلوس تختفي. فبنحطهم في Transaction:

transfer.sql
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- علي
UPDATE accounts SET balance = balance + 100 WHERE id = 2;  -- منى
COMMIT;

الـ Transaction ليها 4 ضمانات مشهورة باسم ACID:

الحرف الاسم الضمان مين بيحققه
A Atomicity كله يا ولا حاجة الـ Undo log / الـ Rollback
C Consistency الداتا تفضل ماشية على القواعد (Constraints) الداتابيز + الكود بتاعك
I Isolation كل Transaction كأنها شغالة لوحدها الـ Concurrency Control
D Durability اللي اتعمله COMMIT مايضيعش حتى لو الكهربا قطعت الـ WAL / الـ Redo log

الموضوع ده كله عن حرف واحد: الـ I.

الـ Transaction بتحل مشكلة “السيرفر وقع في النص”. الـ Isolation بتحل مشكلة “حد تاني شغال معايا في نفس الوقت”.

2. الـ Schedule: التنفيذ المتداخل

كل Transaction عبارة عن سلسلة عمليات صغيرة: قراية عنصر (R(x))، وكتابة عنصر (W(x))، وفي الآخر COMMIT أو ABORT. لما فيه أكتر من Transaction شغالين، الداتابيز بتنفّذ عملياتهم بترتيب معين.

مثال لترانزاكشنين، كل واحدة بتقرا x وتكتبه:

Serial Interleaved
R1(x) W1(x) C1 R2(x) W2(x) C2 R1(x) R2(x) W1(x) W2(x) C1 C2

السؤال المنطقي: طب ما نشغّلهم دايماً Serial ونرتاح؟ المشكلة إن ده بيقتل الأداء:

  • الـ I/O بطيء: Transaction مستنية قراية من الـ Disk (مللي ثواني) والـ CPU واقف. أثناء ما هي مستنية، Transaction تانية ممكن تشتغل.
  • الترانزاكشنز مش بتلمس نفس الداتا غالباً: واحد بيشتري موبايل وواحد بيشتري كتاب. مفيش أي سبب واحد فيهم يستنى التاني.
  • فيه CPUs كتير: سيرفر فيه 32 core. لو شغّلنا Transaction واحدة بس في الوقت، بنستخدم 1 من 32.

فالداتابيز لازم تداخل العمليات عشان تخدم آلاف الطلبات في الثانية. والتداخل ده هو مصدر كل المشاكل.

التداخل (Interleaving) مش اختيار، هو ضرورة عشان الأداء. والـ Concurrency Control هو اللي بيخلّي التداخل ده آمن.

3. أول مشكلة: Lost Update

متجر فيه موبايل واحد بس في المخزن (stock = 1). اتنين بيدوسوا “اشتري” في نفس اللحظة. كود الشراء بسيط ومنطقي: اقرا المخزون، لو أكبر من صفر اخصم واحد.

آخر موبايل في المخزنجرّب بنفسك
في الداتابيزstock1orders0
أحمد بيشتري⏱سارة بتشتري
  1. SELECT stock FROM products WHERE id = 7;stock = 11
  2. 2SELECT stock FROM products WHERE id = 7;stock = 1
  3. UPDATE products SET stock = 0 WHERE id = 7; INSERT INTO orders ...;1 row3
  4. 4UPDATE products SET stock = 0 WHERE id = 7; INSERT INTO orders ...;1 row
  5. COMMIT; COMMIT;الاتنين نجحوا
▶

دوس ▶ شغّل، أو امشي خطوة خطوة بالأسهم.

لاحظ إن كل Transaction لوحدها كودها صح 100%. المشكلة مش في الكود، المشكلة في التداخل: سارة بنت قرارها على قيمة بقت قديمة (Stale) قبل ما تكتب.

وفيه 3 طرق تمنعها، وهنشرح كل واحدة بالتفصيل في الموضوع:

3 حلول للـ Lost Update
-- 1) خلّي الداتابيز تعمل الحسبة بنفسها في UPDATE واحد (ذرّي)
UPDATE products SET stock = stock - 1 WHERE id = 7 AND stock > 0;
-- لو رجع 0 rows يبقى المخزون خلص

-- 2) اقفل الصف وإنت بتقراه، فأي حد تاني عايز يقفله يستنى (Pessimistic)
SELECT stock FROM products WHERE id = 7 FOR UPDATE;

-- 3) اكتب بشرط إن محدش غيّر الصف من ساعة ما قريته (Optimistic)
UPDATE products SET stock = 0, version = version + 1
WHERE id = 7 AND version = 41;  -- لو 0 rows يبقى حد سبقك: اقرا تاني وجرّب

الـ Lost Update بتحصل في أي كود بيقرا قيمة، ويحسب عليها في الأبلكيشن، وبعدين يكتبها. وده أكتر Pattern منتشر في أي Backend.

4. الـ Concurrency Control بيعمل إيه؟

يعني فيه هدفين بيشدّوا عكس بعض:

  • الصحة (Correctness): النتيجة لازم تساوي نتيجة تشغيل الترانزاكشنز بترتيب ما ورا بعض. ده اسمه Serializability، وليه درس كامل.
  • الأداء (Throughput): أكبر عدد ترانزاكشنز في الثانية، وأقل وقت استنا.

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

Pessimistic (متشائم) Optimistic (متفائل)
الفرضية التعارض هيحصل، فامنعه قبل ما يحصل التعارض نادر، فاشتغل عادي واتأكد في الآخر
الأداة الـ Locks: استنى لحد ما الداتا تبقى فاضية Validation وقت الـ COMMIT: لو فيه تعارض، الغي وابدأ تاني
تمنه استنا (Blocking) و Deadlocks شغل بيترمي (Aborts) وإعادة محاولة
يكسب لما التعارض كتير (Hot rows) التعارض قليل والقراية أكتر
أمثلة Two-Phase Locking، SELECT ... FOR UPDATE OCC، عمود version، الـ SSI في PostgreSQL

وفيه تقنية تالتة بتتحط فوق الاتنين وموجودة في كل الداتابيز الحديثة: MVCC. بدل ما الصف يبقى ليه نسخة واحدة، بيبقى ليه كذا نسخة، فاللي بيقرا بيشوف نسخة قديمة ثابتة ومش محتاج يستنى اللي بيكتب. MySQL (InnoDB) و PostgreSQL الاتنين بيستخدموا MVCC للقراية + Locks للكتابة.

الـ Concurrency Control بيدوّر على أكبر توازي ممكن من غير ما النتيجة تختلف عن تشغيل الترانزاكشنز ورا بعض. والفرق بين كل التقنيات هو: بتمنع التعارض قبل ما يحصل، ولا بتكتشفه بعد ما يحصل.

5. أسئلة الإنترفيو

فكّر في إجابتك الأول، وبعدين افتح السؤال.

01يعني إيه Isolation في ACID؟

إن كل Transaction تشتغل كأنها لوحدها على الداتابيز، حتى لو فيه ترانزاكشنز تانية شغالة في نفس الوقت. والمستوى المثالي ليها هو Serializability: النتيجة تساوي تشغيلهم ورا بعض بترتيب ما.

02ليه الداتابيز مش بتشغّل الترانزاكشنز ورا بعض وخلاص؟

عشان الأداء: الـ I/O بطيء والـ CPU هيقف مستني، والسيرفر فيه cores كتير، وأغلب الترانزاكشنز بتلمس داتا مختلفة أصلاً فمفيش داعي تستنى بعض.

03إيه هو الـ Lost Update؟ واديني مثال.

ترانزاكشنين يقروا نفس القيمة ويكتبوا قيمة محسوبة منها، فالتانية تمسح تحديث الأولى. مثال: اتنين يقروا stock = 1 وكل واحد يكتب stock = 0، فيتباع منتجين والمخزون ينقص واحد بس.

04اذكر 3 طرق تمنع الـ Lost Update.

UPDATE ذري بيعمل الحسبة جوه الداتابيز (SET stock = stock - 1 WHERE stock > 0)، أو قفل الصف وقت القراية (SELECT ... FOR UPDATE)، أو Optimistic Locking بعمود version والـ UPDATE يشترط إن الـ version ماتغيرش.

05الفرق بين Pessimistic و Optimistic Concurrency Control؟

Pessimistic بيمنع التعارض بالـ Locks قبل ما يحصل، وتمنه الاستنا والـ Deadlocks. Optimistic بيسيب الكل يشتغل ويتأكد وقت الـ COMMIT، ولو فيه تعارض بيلغي ويعيد، وتمنه الشغل اللي بيترمي. الأول أحسن لما التعارض كتير، والتاني لما التعارض نادر.

علّم عليه لما تقدر تجاوب أسئلة الإنترفيو من غير ما تبص.