بنناجح
المرحلة 2: الآليات · الدرس 4 من 4 · 30 دقيقة

Optimistic و Timestamps و SSI

مش كل Concurrency Control بيستنى. فيه عيلة كاملة بتفترض إن التعارض نادر: سيب الكل يشتغل، واتأكد في الآخر، ولو فيه مشكلة الغي وابدأ تاني. الدرس ده بيشرح الـ Timestamp Ordering، والـ OCC، والـ Optimistic Locking اللي بتعمله بإيدك بعمود version، والـ SSI اللي خلّى PostgreSQL يدّيك SERIALIZABLE حقيقي من غير Locks على القراية.

في الدرس ده هتتعلم
  • تشرح Timestamp Ordering وقاعدة Thomas Write Rule
  • تفهم مراحل OCC الـ 3: Read و Validation و Write
  • تعمل Optimistic Locking بعمود version وتعرف إمتى يكسب
  • تفهم إزاي SSI في PostgreSQL بيكتشف الـ Write Skew
  • تختار بين Pessimistic و Optimistic حسب نوع الضغط

1. Timestamp Ordering

بدل ما نستنى، نقرر الترتيب من الأول: كل ترانزاكشن بتاخد Timestamp أول ما تبدأ، TS(T). والقاعدة: الترتيب Serial المكافئ هو ترتيب الـ Timestamps. أي عملية تخالف الترتيب ده، الترانزاكشن بتاعتها تتلغى وتبدأ بـ Timestamp جديد.

عشان كده كل عنصر x بيحتفظ برقمين:

  • R-TS(x): أكبر Timestamp لترانزاكشن قرت x.
  • W-TS(x): أكبر Timestamp لترانزاكشن كتبت x.
العملية الشرط لو اتكسر لو تمام
T عايزة تقرا x TS(T) < W-TS(x)؟ يعني حد “من المستقبل” كتب x Abort: T كانت المفروض تقرا القيمة قبل الكتابة دي، وهي اتمسحت اقرا، و R-TS(x) = max(R-TS(x), TS(T))
T عايزة تكتب x TS(T) < R-TS(x)؟ يعني حد “من المستقبل” قرا x خلاص Abort: حد أحدث بنى قراره على القيمة القديمة —
TS(T) < W-TS(x)؟ يعني حد أحدث كتب x Abort (أو Thomas Write Rule) اكتب، و W-TS(x) = TS(T)

مزايا: مفيش Deadlocks خالص، لأن مفيش حد بيستنى حد. عيوب: Aborts كتير لو فيه تعارض، والترانزاكشن الطويلة بتتلغى كتير (أي حد أحدث يلمس داتاها)، ومحتاج يحتفظ بـ Timestamps لكل عنصر، ومن غير إضافات ممكن يدّي Schedules مش Recoverable.

في الشكل الخام ده مش مستخدم كتير في الداتابيز العلائقية، بس الفكرة (الترتيب بالوقت) هي أساس الـ MVCC: كل نسخة عليها رقم ترانزاكشن، والـ Snapshot بتختار النسخة حسب الرقم.

Timestamp Ordering: الترتيب متحدد من البداية بالوقت، وأي حد يخالفه يتلغى بدل ما يستنى. فمفيش Deadlocks، بس فيه Aborts.

2. Optimistic Concurrency Control (OCC)

3 مراحل:

  1. Read Phase: اقرا من الداتابيز، واكتب في مساحة خاصة (Workspace). سجّل كل اللي قريته (Read Set) وكل اللي كتبته (Write Set). مفيش أي Locks.
  2. Validation Phase: قبل الـ COMMIT، اتأكد: هل أي ترانزاكشن عملت COMMIT أثناء ما أنا شغالة، كتبت حاجة أنا قريتها؟ (يعني Write Set بتاعها ∩ Read Set بتاعي ≠ ∅)
  3. Write Phase: لو التأكيد نجح، انقل الكتابات من الـ Workspace للداتابيز. لو فشل، Abort وابدأ تاني.
Pessimistic (2PL) Optimistic (OCC)
التعارض بيتكشف قبل ما يحصل (وقت الـ Lock) بعد ما يحصل (وقت الـ Validation)
الاستنا فيه (Blocking) مفيش
Deadlocks ممكن مستحيل
تمن التعارض استنا الشغل كله بيترمي ويتعاد
أحسن لما التعارض كتير، والترانزاكشنز تقيلة التعارض نادر، والقراية كتير

تحت ضغط عالي على نفس الصفوف (Hot Rows، زي عداد المشاهدات أو آخر قطعة في Flash Sale)، الـ OCC بيدخل في دوامة: الكل بيتلغى ويعيد ويتلغى تاني. هنا الـ Locks أحسن بكتير.

3. Optimistic Locking في الأبلكيشن: عمود version

أشهر تطبيق للـ OCC مش جوه الداتابيز، ده في الكود بتاعك. خصوصاً لما الترانزاكشن “المنطقية” أطول من ترانزاكشن الداتابيز: المستخدم فتح صفحة تعديل المنتج، وقعد 5 دقايق، ودوس حفظ. مينفعش تمسك Lock 5 دقايق.

الفكرة: كل صف فيه عمود version بيزيد مع كل تعديل. ولما تكتب، تشترط إن الـ version لسه زي ما قريته.

اتنين بيعدّلوا نفس المنتج من لوحة الأدمنجرّب بنفسك
في الداتابيزprice500descriptionقديمversion7
منى (عايزة تغيّر السعر)⏱كريم (عايز يغيّر الوصف)
  1. SELECT * FROM products WHERE id = 7;price 500 · وصف قديم1
  2. 2SELECT * FROM products WHERE id = 7;price 500 · وصف قديم
  3. 3UPDATE products SET price = 500, description = 'جديد' WHERE id = 7;1 row
  4. UPDATE products SET price = 450, description = 'قديم' WHERE id = 7;1 row4
▶

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

بدل version ممكن تستخدم updated_at (بشرط دقة كافية، لأن تعديلين في نفس الثانية ممكن يعدّوا)، أو تشترط على القيمة القديمة نفسها (WHERE id = 7 AND stock = 10)، وده اسمه Compare-And-Swap. والـ HTTP عنده نفس الفكرة جاهزة: ETag في الرد، و If-Match في الطلب، والسيرفر يرد 412 Precondition Failed لو اتغير.

Optimistic Locking بعمود version هو الحل الوحيد للتعديلات اللي المستخدم بياخد فيها وقت، لأنك مينفعش تمسك Lock والمستخدم بيفكر. والـ “0 rows affected” هي إشارة التعارض.

4. SSI: الـ SERIALIZABLE بتاع PostgreSQL

الـ Snapshot Isolation ممتازة، بس بتعدّي Write Skew. والحل الكلاسيك لـ SERIALIZABLE هو 2PL على القراية والكتابة، يعني رجعنا للاستنا. سنة 2008 Cahill وزمايله اقترحوا Serializable Snapshot Isolation (SSI)، و PostgreSQL طبّقها في إصدار 9.1 (2011) كـ SERIALIZABLE.

الفكرة من النظرية: تحت SI، أي Schedule مش Serializable لازم يبقى فيه في الـ Graph “بنية خطيرة”: ترانزاكشن في النص (Pivot) فيها سهمين rw-antidependency ورا بعض: داخل وخارج.

في مثال الدكاترة: T1 (علي) قرت صف منى، و T2 (منى) كتبته: سهم T1 → T2. و T2 قرت صف علي، و T1 كتبته: سهم T2 → T1. دايرة من rw-antidependencies. SSI بيشوفها.

PostgreSQL بيعمل ده بـ SIREAD Locks: “Locks” مش بتمنع حد ولا بتخلي حد يستنى، هي بس سجل بإن الترانزاكشن دي قرت الصفوف (أو الصفحات، أو الجدول، حسب الذاكرة) دي. ولما حد يكتب حاجة عليها SIREAD من ترانزاكشن تانية متداخلة، بيتسجل سهم.

الدكاترة على SERIALIZABLE في PostgreSQLجرّب بنفسك
في الداتابيزali.on_calltruemona.on_calltrue
علي عايز يخرج⏱منى عايزة تخرج
  1. BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT COUNT(*) FROM doctors WHERE on_call;2 · SIREAD1
  2. 2BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT COUNT(*) FROM doctors WHERE on_call;2 · SIREAD
  3. UPDATE doctors SET on_call = false WHERE name = 'Ali';1 row3
  4. 4UPDATE doctors SET on_call = false WHERE name = 'Mona';1 row
  5. COMMIT;تم5
  6. 6COMMIT;ERROR 40001
  7. 7-- Retry: تقرا تاني1 → متقدريش تخرجي
▶

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

حاجات لازم تعرفها عن SSI:

  • فيه False Positives: ممكن يلغي ترانزاكشن والنتيجة كانت هتطلع Serializable فعلاً، لأنه بيدوّر على “البنية الخطيرة” مش على الدايرة الكاملة. وكمان لو الذاكرة قلّت، بيحوّل الـ SIREAD من صفوف لصفحات أو جدول كامل، فالـ False Positives تزيد.
  • الـ Indexes بتفرق: شرط من غير Index = Sequential Scan = SIREAD على الجدول كله = أي كتابة في الجدول بتعمل تعارض.
  • READ ONLY DEFERRABLE: ترانزاكشن قراية بس بتستنى لحد ما تلاقي Snapshot آمنة، وبعدها تشتغل من غير أي فرصة إنها تتلغى. ممتازة للتقارير الطويلة.
  • كل الترانزاكشنز لازم تبقى SERIALIZABLE عشان الضمان يشتغل. ترانزاكشن على READ COMMITTED بتكتب جنبهم مش بتتراقب.
  • الـ Retry إجباري: أي كود بيشتغل على SERIALIZABLE لازم يتعامل مع 40001.

وفي MySQL، SERIALIZABLE مختلف تماماً: مفيش SSI، هو بيحوّل كل SELECT عادي لـ SELECT ... FOR SHARE (لو الـ autocommit مقفول)، يعني رجع لـ 2PL: قراية بتقفل، واستنا، و Deadlocks أكتر.

SSI بيدّي SERIALIZABLE حقيقي بأداء قريب من الـ Snapshot Isolation، لأن القراية مش بتستنى. وبدل الاستنا فيه Aborts، فالـ Retry مش اختياري.

5. تختار إيه؟

الموقف الأنسب ليه
خصم من مخزون أو رصيد UPDATE ذري بشرط أسرع حاجة، والداتابيز بتعمل كل حاجة في جملة واحدة
Flash Sale على منتج واحد (Hot Row) Pessimistic (FOR UPDATE) أو UPDATE ذري الـ Optimistic هيلف في Retries
تعديل من فورم بياخد دقايق Optimistic (version) مينفعش Lock والمستخدم بيفكر
قاعدة بتشمل كذا صف (Write Skew) Constraint، أو SERIALIZABLE في PostgreSQL، أو FOR UPDATE على اللي اتقرا الـ Snapshot لوحده مش هيشوفها
تقرير طويل لازم يبقى متسق REPEATABLE READ (و READ ONLY DEFERRABLE في PostgreSQL) Snapshot واحدة من غير ما توقف حد
تعارض نادر وقراية كتير Optimistic / SSI مفيش استنا خالص في الحالة العادية

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

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

01اشرح Timestamp Ordering. وإيه هي Thomas Write Rule؟

كل ترانزاكشن تاخد Timestamp، والترتيب Serial = ترتيب الـ Timestamps. كل عنصر عليه أكبر Timestamp قرا وكتب. لو ترانزاكشن عايزة تقرا حاجة كتبها حد أحدث، أو تكتب حاجة قراها حد أحدث، تتلغى. Thomas Write Rule: لو كتابة قديمة جت بعد كتابة أحدث ومحدش أحدث قرا، تتجاهل بدل الإلغاء لأنها كده كده هتتغطى.

02مراحل الـ OCC إيه؟ وإمتى يكسب وإمتى يخسر؟

Read: تشتغل على نسخة خاصة وتسجل اللي قريته وكتبته. Validation: تتأكد إن محدش عمل COMMIT لكتابة على حاجة إنت قريتها. Write: تطبق الكتابات. بيكسب لما التعارض نادر لأنه مفيش استنا ولا Deadlocks. وبيخسر تحت تعارض عالي لأن الشغل بيترمي ويتعاد باستمرار.

03إزاي تعمل Optimistic Locking بعمود version؟ وإمتى تحتاجه؟

كل UPDATE يزود version ويشترط WHERE version = القيمة اللي قريتها. لو رجع 0 rows يبقى حد عدّل قبلك: اقرا تاني واعرض التعارض أو أعد. بتحتاجه في التعديلات اللي المستخدم بياخد فيها وقت، زي فورم تعديل، لأن مينفعش تمسك Lock طول ما هو بيفكر.

04إزاي SSI في PostgreSQL بيكتشف الـ Write Skew من غير Locks على القراية؟

بيسجل SIREAD locks: علامات بمين قرا إيه من غير ما تمنع حد. لما ترانزاكشن تكتب حاجة قرتها ترانزاكشن تانية متداخلة بيتسجل rw-antidependency. ولو ترانزاكشن بقى ليها سهمين rw داخل وخارج (بنية خطيرة)، بيلغي واحدة بـ Error 40001. ممكن يبقى فيه False Positives.

05الفرق بين SERIALIZABLE في PostgreSQL و MySQL؟

PostgreSQL بيستخدم SSI: القراية من Snapshot من غير Locks، والتعارضات بتتكشف وبتلغي ترانزاكشن بـ 40001. MySQL بيستخدم Locks: كل SELECT عادي بيتحول لـ FOR SHARE لو الـ autocommit مقفول، فالقراية بتقفل وبتستنى، وده 2PL كلاسيك بـ Deadlocks أكتر.

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