Atomicity: الـ Undo والـ Rollback
إزاي الداتابيز بتلغي ترانزاكشن عدّلت مليون صف كأنها ماحصلتش؟ الدرس ده بيوريك الطريقتين: InnoDB اللي بيكتب في الـ Undo Log قبل أي تعديل ويرجع منه، و PostgreSQL اللي بيعلّم الترانزاكشن إنها اتلغت وخلاص. وليه الفرق ده بيخلّي ROLLBACK في واحد ثانية وفي التاني ساعة.
- تفهم إزاي InnoDB بيلغي الترانزاكشن بالـ Undo Log
- تفهم إزاي PostgreSQL بيلغيها من غير ما يرجّع حاجة
- تعرف ليه الـ ROLLBACK ممكن ياخد وقت أطول من الترانزاكشن نفسها
- تفهم علاقة الـ Undo بالـ MVCC
1. المشكلة
ترانزاكشن عملت UPDATE على مليون صف، وبعدين حصل Error. الداتابيز لازم ترجّع المليون صف للقيم القديمة. بس القيم القديمة دي فين؟ الـ UPDATE كتب فوقها.
فيه طريقتين لحل المشكلة دي، وكل داتابيز اختارت طريقة:
| InnoDB | PostgreSQL | |
|---|---|---|
| الفكرة | عدّل الصف مكانه، واحفظ القديم في مكان تاني | متعدّلش الصف خالص، اعمل نسخة جديدة |
| القديم فين | الـ Undo Log | النسخة القديمة لسه في الجدول |
| الـ ROLLBACK | ارجع من الـ Undo صف صف | علّم الترانزاكشن “Aborted” |
2. InnoDB: الـ Undo Log
كل صف في InnoDB فيه عمود مخفي اسمه DB_ROLL_PTR بيشاور على سجل الـ Undo بتاع آخر تعديل عليه. فالتعديلات على صف بتعمل سلسلة: النسخة الحالية في الجدول، ووراها في الـ Undo النسخة اللي قبلها، واللي قبلها، وهكذا.
لما تعمل ROLLBACK، InnoDB بيمشي على سجلات الـ Undo بتاعة الترانزاكشن بالعكس (من آخر تعديل لأول تعديل) ويطبّق كل واحد: يرجّع القيم القديمة، ويمسح الصفوف اللي اتضافت، ويرجّع اللي اتمسحت. ودي عملية حقيقية فيها قراية وكتابة، وكمان بتتسجل في الـ Redo Log.
START TRANSACTION; UPDATE products SET price = price * 1.1 WHERE id = 1;1 row1- 2
undo: (id=1, price=100)سجل جديد UPDATE products SET price = price * 1.1 WHERE id = 2;1 row3ROLLBACK;بيطبّق الـ Undo بالعكس4- 5
apply undo → (id=2, price=200) apply undo → (id=1, price=100)رجعت
BEGIN; -- xid = 9001 UPDATE products SET price = price * 1.1 WHERE id = 1;UPDATE 11- 2
old: price=100 xmax=9001 new: price=110 xmin=9001نسختين في الجدول UPDATE products SET price = price * 1.1 WHERE id = 2;UPDATE 13ROLLBACK;ROLLBACK4- 5
xid 9001 = ABORTEDفوري
دوس ▶ شغّل، أو امشي خطوة خطوة بالأسهم.
3. ليه الـ ROLLBACK ممكن ياخد ساعات؟
في InnoDB، لو ترانزاكشن عدّلت 50 مليون صف في ساعتين ووقفتها (أو السيرفر وقع)، الـ ROLLBACK لازم يرجّع الـ 50 مليون صف واحد واحد. وغالباً بياخد أطول من الترانزاكشن نفسها، لأن الرجوع بيتعمل صف صف وعشوائي أكتر. وطول ما هو شغال:
- الـ Locks على الصفوف دي لسه موجودة، فأي حد عايزها مستني.
- لو السيرفر اتعمله Restart، InnoDB بيكمّل الـ ROLLBACK بعد الـ Recovery في الخلفية.
علشان كده أوعى تعمل Kill لترانزاكشن ضخمة وإنت فاكر إنها هتقف على طول. شوف هي عملت كام صف الأول:
-- MySQL: الترانزاكشنز الشغالة، وحالتها، وعدد الصفوف اللي عدّلتها
SELECT trx_id, trx_state, trx_started, trx_rows_modified
FROM information_schema.innodb_trx
ORDER BY trx_started;
-- trx_state = 'ROLLING BACK' و trx_rows_modified بيقل = الرجوع شغالوالحل في الحالتين واحد: قسّم الشغل الكبير لـ Batches (مثلاً 5,000 صف في كل ترانزاكشن). كل Batch بيتثبت لوحده، ولو حاجة حصلت بتخسر Batch واحد بس.
4. الـ Undo بيعمل حاجتين
الـ Undo Log في InnoDB مش للـ ROLLBACK بس، هو كمان مصدر الـ MVCC: لما ترانزاكشن قديمة تقرا صف اتعدّل بعدها، InnoDB بيمشي ورا DB_ROLL_PTR ويبني النسخة القديمة من الـ Undo. علشان كده سجلات الـ Undo مش بتتمسح مع الـ COMMIT. بتفضل لحد ما مفيش ترانزاكشن محتاجاها، وبعدين الـ Purge Thread بيمسحها.
وفي PostgreSQL نفس الفكرة بشكل تاني: النسخ القديمة في الجدول بتخدم الـ ROLLBACK (لو اتلغت الترانزاكشن الجديدة) والـ MVCC (للي بيقروا من Snapshot قديمة) مع بعض، والـ VACUUM بينضفها.
InnoDB بيدفع تمن الـ Atomicity في الـ ROLLBACK (بيرجع الشغل بالعكس). PostgreSQL بيدفعه في الـ VACUUM (بينضف النسخ اللي اتلغت بعدين). والاتنين بيحتفظوا بالقديم لنفس السببين: الـ ROLLBACK والـ MVCC.
5. أسئلة الإنترفيو
فكّر في إجابتك الأول، وبعدين افتح السؤال.
01إزاي InnoDB بيعمل ROLLBACK؟
قبل أي تعديل بيكتب في الـ Undo Log المعلومة اللي محتاجها عشان يرجع: القيم القديمة للـ UPDATE، وامسح للـ INSERT، ورجّع للـ DELETE. في الـ ROLLBACK بيطبّق السجلات دي بالعكس من الأحدث للأقدم، فالوقت بيتناسب مع حجم الترانزاكشن.
02ليه الـ ROLLBACK في PostgreSQL سريع حتى للترانزاكشنز الكبيرة؟
لأن PostgreSQL مابيعدّلش الصفوف القديمة، بيعمل نسخ جديدة عليها رقم الترانزاكشن. الـ ROLLBACK بيعلّم الترانزاكشن Aborted في pg_xact وخلاص، والقراية بعد كده بتتجاهل نسخها. والنسخ الميتة بيمسحها الـ VACUUM بعدين.
03عملت Kill لترانزاكشن ضخمة في MySQL ومازالت شغالة. ليه؟
لأنها دخلت في ROLLBACK لازم يرجّع كل الصفوف اللي عدّلتها من الـ Undo، وده ممكن ياخد وقت قد الترانزاكشن أو أكتر، والـ Locks لسه ماسكة. تتابعها من information_schema.innodb_trx (trx_state و trx_rows_modified). والحل من الأول هو الـ Batches.
04الـ Undo Log بيستخدم في إيه غير الـ ROLLBACK؟
في الـ MVCC: الترانزاكشن القديمة اللي محتاجة نسخة قديمة من صف بتبنيها من سلسلة الـ Undo عن طريق DB_ROLL_PTR. علشان كده الـ Undo مش بيتمسح مع الـ COMMIT، بيستنى الـ Purge لما مفيش Read View محتاجاه.