الـ Isolation Levels في MySQL و PostgreSQL
أسماء المستويات واحدة في الاتنين، بس السلوك مختلف جداً. الدرس ده بيمشي على كل مستوى في PostgreSQL و MySQL: بيقرا إمتى، وبيقفل إيه، وبيرمي Errors إمتى، وإيه اللي بيعدّي منه. وفي الآخر إزاي تختار المستوى الصح لكل حالة.
- تعرف الافتراضي في كل داتابيز وتغيّره على مستوى الـ Session أو الترانزاكشن
- تفهم سلوك
READ COMMITTEDفي PostgreSQL مع الـ UPDATE (الـ Re-check) - تفهم الـ Gap Locks والـ Semi-consistent Read في MySQL، وليه الافتراضي REPEATABLE READ
- تقارن المستويات في الداتابيزين جنب بعض
- تختار المستوى المناسب عن فهم
1. الافتراضي وإزاي تغيّره
| MySQL (InnoDB) | PostgreSQL | |
|---|---|---|
| الافتراضي | REPEATABLE READ |
READ COMMITTED |
| تشوفه | SELECT @@transaction_isolation; |
SHOW transaction_isolation; |
| للـ Session | SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; |
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ...; |
| لترانزاكشن واحدة | SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; قبل START TRANSACTION |
BEGIN ISOLATION LEVEL SERIALIZABLE; أو SET TRANSACTION ... كأول جملة جوه الترانزاكشن |
خد بالك من الفرق في آخر صف: في MySQL، SET TRANSACTION من غير SESSION بيأثر على الترانزاكشن الجاية بس، ولو اتنفذ جوه ترانزاكشن مفتوحة بيرمي Error. في PostgreSQL العكس: لازم يبقى جوه الترانزاكشن وقبل أي استعلام.
2. PostgreSQL
READ UNCOMMITTED = READ COMMITTED
PostgreSQL مش بيدّيك Dirty Reads أبداً. لو طلبت READ UNCOMMITTED هيشتغل READ COMMITTED. ده مسموح في المعيار، لأن المعيار بيحدد الحد الأدنى من الحماية، مش الحد الأقصى.
READ COMMITTED (الافتراضي)
- كل جملة بتاخد Snapshot جديدة لحظة ما تبدأ. فجملتين ورا بعض ممكن يشوفوا داتا مختلفة (Non-repeatable Reads و Phantoms و Read Skew كلهم ممكنين).
- جوه الجملة الواحدة، الصورة ثابتة.
- الكتابة: لو
UPDATEأوDELETEلقى صف بيتعدّل من ترانزاكشن تانية، بيستنى. ولو التانية عملت COMMIT، بياخد النسخة الجديدة من الصف ده، ويعيد التحقق من الـ WHERE عليها، ولو لسه مطابقة يكمل عليها. ده اسمه Re-check (داخلياً EvalPlanQual).
الـ Re-check ده بيخلّي UPDATE ... SET x = x + 1 صح دايماً (مفيش Lost Update في الـ UPDATE الذري). بس عنده حالات غريبة، والمثال ده من الـ Documentation الرسمي:
BEGIN; UPDATE website SET hits = hits + 1;2 rows1- 2
DELETE FROM website WHERE hits = 10;⏳ مستنية COMMIT;تم3- 4
-- Re-check على النسخة الجديدة من B: hits = 110 rows
دوس ▶ شغّل، أو امشي خطوة خطوة بالأسهم.
REPEATABLE READ = Snapshot Isolation
- Snapshot واحدة من أول جملة في الترانزاكشن لحد آخرها. فمفيش Non-repeatable Reads ولا Phantoms ولا Read Skew.
- لو حاولت تعدّل (أو تقفل بـ
FOR UPDATE) صف اتعدّل واتعمله COMMIT بعد الـ Snapshot بتاعتك:ERROR: could not serialize access due to concurrent update(SQLSTATE40001). ده بيمنع الـ Lost Update، بس الكود لازم يعمل Retry. - Write Skew بتعدّي.
SERIALIZABLE = SSI
- كل ضمانات
REPEATABLE READ+ مراقبة SIREAD، فحتى الـ Write Skew بيتمنع بـ40001. - أداؤه قريب من
REPEATABLE READلأن القراية مش بتقفل. بس Aborts أكتر، ومحتاج Retry دايماً.
3. MySQL (InnoDB)
في InnoDB لازم تفرّق بين نوعين قراية:
- Consistent Read (Nonlocking): أي
SELECTعادي. بيقرا من الـ Read View (MVCC)، مش بيقفل ومش بيستنى. - Locking Read / Current Read:
SELECT ... FOR UPDATE، وFOR SHARE، وUPDATE، وDELETE. بيقرا آخر نسخة متثبتة (مش الـ Snapshot)، وبياخد Locks.
READ UNCOMMITTED
القراية العادية بتشوف آخر نسخة حتى لو مش متثبتة (Dirty Reads). نادراً ما له استخدام صح.
READ COMMITTED
- كل Consistent Read بتاخد Read View جديدة.
- الـ Locking Reads و UPDATE و DELETE بياخدوا Record Locks بس، من غير Gap Locks (إلا في فحص الـ Foreign Keys والـ Duplicate Keys). فالـ Phantoms ممكنة، بس الـ Deadlocks أقل بكتير.
- Semi-consistent Read: في
UPDATE، لو صف مقفول، InnoDB بيقرا آخر نسخة متثبتة منه ويشوف هل مطابق للـ WHERE أصلاً. لو مش مطابق، بيعدّيه من غير ما يستنى. والصفوف اللي اتمسحت ومش مطابقة بيفك الـ Lock بتاعها فوراً.
REPEATABLE READ (الافتراضي)
- كل Consistent Reads في الترانزاكشن من Read View واحدة (من أول قراية). فالـ
SELECTالعادي مش بيشوف Phantoms. - الـ Locking Reads و UPDATE و DELETE بيعملوا Current Read وبياخدوا Next-Key Locks على الـ Range اللي اتمسح (أو Record Lock بس لو بحث بـ Unique Index عن صف واحد).
- مفيش First-Updater-Wins: الـ Read-Modify-Write بـ
SELECTعادي بيعمل Lost Update من غير أي Error (شفناها في درس الـ MVCC). - Write Skew بتعدّي برضه.
SERIALIZABLE
- زي
REPEATABLE READ، بس InnoDB بيحوّل كلSELECTعادي لـSELECT ... FOR SHAREلو الـ autocommit مقفول (يعني جوه ترانزاكشن). ولو الـ autocommit شغال، كلSELECTبيبقى ترانزاكشن لوحدها بـ Consistent Read. - يعني 2PL كلاسيك: القراية بتقفل وبتستنى، والـ Deadlocks بتزيد (خصوصاً الـ Lock Upgrade من S لـ X).
4. جنب بعض
| PostgreSQL | MySQL (InnoDB) | |
|---|---|---|
| READ UNCOMMITTED | = READ COMMITTED | Dirty Reads ممكنة |
| READ COMMITTED | Snapshot لكل جملة · Re-check في الـ UPDATE | Read View لكل قراية · مفيش Gap Locks · Semi-consistent Read |
| REPEATABLE READ | Snapshot Isolation · Lost Update ← Error 40001 · Write Skew بتعدّي | Read View واحدة للقراية العادية · الكتابة على آخر نسخة · Lost Update بتعدّي · Next-Key Locks |
| SERIALIZABLE | SSI · من غير Locks على القراية · Write Skew ← 40001 | 2PL · كل SELECT بيبقى FOR SHARE · استنا و Deadlocks |
| الـ Retry لازم في | REPEATABLE READ و SERIALIZABLE (40001) + الـ Deadlocks (40P01) | الـ Deadlocks (1213) و Lock wait timeouts (1205) |
5. تختار إيه؟
قاعدة عملية: ابدأ بالافتراضي، واحمي الأماكن الحساسة بالأداة المناسبة، بدل ما ترفع مستوى الداتابيز كلها.
- أغلب الشغل (CRUD عادي):
READ COMMITTEDفي الاتنين كفاية، بشرط إن أي Read-Modify-Write يبقى UPDATE ذري أوFOR UPDATE. - تقرير أو Export لازم يبقى متسق: ترانزاكشن على
REPEATABLE READ، وفي PostgreSQL ممكنSERIALIZABLE READ ONLY DEFERRABLE. - قواعد بتشمل كذا صف ومش ممكن تتعمل Constraint: في PostgreSQL
SERIALIZABLEمع Retry (أنضف حل). في MySQLFOR UPDATEعلى كل الصفوف اللي القرار اتبنى عليها، أو صف تقفل عليه. - منطق فلوس حساس: Constraints في الداتابيز (
CHECK (balance >= 0)) + UPDATE ذري بشرط + Ledger (هتشوفه في درس الـ Patterns).
اسم المستوى مش كفاية تعرف إنت محمي من إيه. REPEATABLE READ في PostgreSQL بيرمي Error على الـ Lost Update، وفي MySQL بيعدّيه بصمت. اعرف داتابيزك، واحمي الأماكن الحساسة بشكل صريح.
6. أسئلة الإنترفيو
فكّر في إجابتك الأول، وبعدين افتح السؤال.
01إيه الـ Isolation Level الافتراضي في MySQL و PostgreSQL؟
MySQL (InnoDB): REPEATABLE READ. PostgreSQL: READ COMMITTED.
02في PostgreSQL على READ COMMITTED، إيه اللي بيحصل لو UPDATE لقى صف اتعدّل من ترانزاكشن تانية؟
بيستنى لحد ما التانية تخلص. لو عملت COMMIT، بياخد النسخة الجديدة من الصف ويعيد التحقق من الـ WHERE عليها، ولو مطابقة يكمل عليها، ولو لأ يعدّيه. علشان كده UPDATE x = x + 1 آمن، بس منطق الشروط المعقدة ممكن يطلع نتايج غريبة.
03ليه الافتراضي في MySQL هو REPEATABLE READ؟ وليه شركات كتير بتغيّره؟
تاريخياً عشان Statement-based Replication: الـ Gap Locks كانت بتضمن إن الـ Replica تنفّذ الجمل بنفس النتيجة. مع Row-based Binlog المشكلة اتحلت، فشركات كتير بتنزل لـ READ COMMITTED عشان Gap Locks و Deadlocks أقل وتوازي أعلى.
04الفرق بين Consistent Read و Locking Read في InnoDB؟
Consistent Read هو SELECT عادي بيقرا من الـ Read View بالـ MVCC من غير Locks. Locking Read هو FOR UPDATE و FOR SHARE وكمان UPDATE و DELETE: بيقرا آخر نسخة متثبتة وبياخد Locks. علشان كده في REPEATABLE READ ممكن SELECT عادي يشوف قيمة و UPDATE في نفس الترانزاكشن يشتغل على قيمة تانية.
05MySQL SERIALIZABLE بيعمل إيه بالظبط؟
زي REPEATABLE READ، بس كل SELECT عادي بيتحول لـ SELECT ... FOR SHARE لو الـ autocommit مقفول. يعني القراية بتاخد Shared Locks وبتستنى الكتابات، وده 2PL بتوازي أقل و Deadlocks أكتر. ومفيش SSI زي PostgreSQL.