המדריך הבסיסי לטרנזאקציות באורקל
הפוסט הזה הוא הכנה לפוסט נוסף על הפרמטר commit_write. מכוון שהמאמר יצא ארוך במיוחד, פיצלתי אותם לשניים. בפוסט הזה נדבר על איך טרנזאקציות עובדות ובפוסט הבא נדבר על איך לעקוף את זה…
אורקל, כמו בסיסי נתונים יחסיים אחרים, מאפשר לנו עבודה עם טרנזאקציות – זה הבסיס למרבית המערכות שמבוססות על בסיסי נתונים. כשאנחנו עובדים עם טרנזאקציה, אנחנו רוצים שהנתונים שלנו ישמרו כמו שצריך אבל איך אנחנו מגדרים "כמו שצריך"?
כדי לענות על השאלה הזו ישבו טובי המומחים באקדמיה והגדירו סטנדרט שנקרא ACID: Atomic, Consistent, Isolation, Durability. כדי להבין מה זה אומר, בואו ונפרק את ההגדרה:
- Atomic– טרנזאקציות ישמרו בצורה של הכול או כלום. אין חצי טרנזאקציה.
- Consistent– אם ביצענו מספר פעולות שתלויות אחת בשנייה אנחנו רוצים שהטרנזאקציה תשמור את הנתונים בצורה אמינה.
- Isolation– כל עוד אנחנו מבצעים טרנזאקציה, אף אחד לא צריך לדעת על זה.
- Durability– אם בסיס הנתונים שלנו נופל, אנחנו רוצים שכל המידע שביצענו לו commit יהיה שם גם אחרי שהוא יעלה בחזרה.
העניין הוא ש-ACID עולה לנו: הוא עולה לנו זמן והוא עולה לנו משאבים. מה קורה אם מה שחשוב לנו זה הביצועים ואנחנו מוכנים לוותר על אחד מעקרונות ה-ACID בתמורה לזמן ולמשאבים?
על פניו, לא מדובר במשהו חדש – כתבתי על זה גם בעבר בנושאים הקשורים ל-NoSQL. בפוסט הזה נדבר על התהליך שקורה מתחת לפני השטח כשעושים טרנזאקציה בבסיס הנתונים. הפוסט הבא יתמקד ב-Durability ואיך ניתן לשחרר קצת את החבל מהבחינה הזו באורקל (מבלי ללכת לפתרונות NoSQL).
לפני שנצלול לעסק, אזהרה: הפוסט הזה מדבר על נושאים שמשפיעים על האמינות של בסיס הנתונים. שינוי הפרמטרים וההתנהגות שיוזכרו בפוסט הזה ובפוסט הבא הן פעולות מסוכנות. אם אתם לא בטוחים במאה אחוזים שהבנתם את הסיכונים שקשורים לפרמטרים האלה, אל תשנו אותם ובכל מקרה אני לא אחראי לתוצאות.
מה קורה בזמן טרנזאקציה
כאשר אנחנו מבצעים טרנזאקציה, אנחנו בדרך כלל מריצים רצף של פקודת DML (כלומר insert, update, delete, merge). כשזה קורה, שאורקל משנה קודם כל זה את הבלוקים של המידע שבזיכרון. הבלוקים האלה נמצאים ב-buffer cache וכשהבלוקים האלה משתנים, הבלוקים הישנים הולכים ל-undo tablespace (או ל-rollback segment אם אנחנו עדיין גרים בתוך מערה ורודפים אחרי ממותה עם חנית בשעות הפנאי).
הפקודות עצמן (כולל ערכי ה"לפני" וה"אחרי") נכתבים לתוך ה-redo log buffer וכל כמה זמן הבלוקים יורדים מתוך הזיכרון לתוך קובץ ה-redo log. כל כמה זמן זה קורה? זה קורה כל פעם שעושים commit, כאשר ה-redo log buffer מתמלא עד השליש, לפני כתיבה של dbwr אבל בכל מקרה לא יותר מ-3 שניות מהפעם האחרונה שהוא כתב מידע לקובץ. חשוב – מכוון שיש פה גם טריגר של זמן וגם טריגר של זיכרון, אז יש סיכוי לא נמוך שלקובץ ה-redo ייכתב מידע שהוא uncommitted וזה בסדר גמור.
עכשיו, כל עוד זה תלוי במשתמשים – מבחינתם אם לא ביצענו commit, המידע לא שם ולכן אם מישהו צריך לקרוא מידע מטבלה הוא מצפה שהרשומות לא יהיו שם. איך זה קורה? מכוון ששמרנו עותק ישן של הבלוק שהשתנה, בסיס הנתונים קורא את העותק הזה מה-undo tablespace. אם לעומת זאת אנחנו מבצעים commit אז מבחינת המשתמשים המידע נשמר ויהיה שם בפעם הבאה שנשלוף את המידע, גם אם זה אחרי שבסיס הנתונים ירד. זה תמצית הרעיון של ה-isolation.
מצד שני, מבחינת בסיס הנתונים, ברגע שאנחנו מבצעים commit הזיכרון של ה-redo log buffer עובר flash ל-redo log file, קובץ ה-control מתעדכן שהטרנזאקציה נגמרה (SCN) והבלוקים ב-undo מסומנים כ-"מישהו אחד פחות צריך אותם" (אם הם המספר הזה מגיע ל-0 אפשר לדרוס אותם). בכך מסתיים התהליך של ה-commit.
רגע-רגע יקרא הקורא חד העין – מה זה הסתיים התהליך? בסוף אנחנו צריכים שהמידע יגיע לטבלאות! הטבלאות יושבות בתוך tablespace-ים ו-data file-ים, מתי זה קורה?
ובכן, התהליך של כתיבת המידע מה-buffer cache לתוך הקבצים נקרא dbwr (או dbwn בגרסאות חדשות יותר). זה תהליך א-סינכרוני שלוקח את הבלוקים שמסומנים כ-dirty ו-committed מהזיכרון וכותב אותם לדיסק. זה תהליך שעלול לקחת קצת זמן אבל הוא קורה ברקע ולא מפריע לעבודה של המשתמשים.
מכוון שהתהליך הוא א-סינכרוני, אם קורה שבזמן שבין ביצוע ה-commit ועד הזמן שבו ה-dbwr כותב את המידע לקבצים בסיס הנתונים נופל, המידע שלנו במצב בעייתי. מצד אחד, הבטחנו למשתמש שהמידע נרשם אבל מצד שני הוא עוד לא ב-data files.
בדיוק בשביל מצבים כאלה אורקל מתחזק את ה-redo log. ה-redo log כשמו כן הוא: הוא עושה מחדש את הפעולות במקרה של קריסה. כאשר בסיס הנתונים עולה, הוא בודק אם ה-SCN שב-control file ובכל ה-data files מסונכרן. אם הוא מוצא פער הוא מתחיל לחזור על כל הפעולות שבוצעו בין הנקודה הכי מוקדמת שהוא מוצא ב-dbf-ים לבין הנקודה הכי מאוחרת שהוא מוצא ב-control file. את הפעולות הוא לוקח מה-redo log files ואם הם לא שם, אז מקבצי ה-archive logs. הפעולות שיתבצעו הן כל הפעולות – בין הם הן עברו commit ובין אם לאו.
כאשר התהליך של ה-redo מסתיים (roll forward), בסיס הנתונים יפתח לעבודת המשתמשים וכל הטרנזאקציות הפתוחות (אלא שלא בוצע להם commit ואין להם בעלים) עוברות rollback.
בצורה כזו בסיס הנתונים מבטיח לנו ש-commit זה commitment (התחייבות) ומידע שבוצע לו commit ייכתב בסופו של דבר לקבצי ה-database files בין אם הוא קרס ובין אם הכול היה בסדר. זה התמצית של מנגנון ה-durability של אורקל.
הבעיה שלנו מתחילה במערכות שבהן יש הרבה מאוד טרנזאקציות קצרות שרצות במקביל. כשזה קורה העומס שנוצר על קובץ ה-redo log הופך להיות מאוד משמעותי ואנחנו מתחילים לחכות לפעולות שיקרו עליו. זו יכולה להיות המתנה לכתיבה לקובץ (לפני ה-commit) אבל זו יכולה להיות גם המתנה ל-commit עצמו. ההמתנה שנראה הרבה פעמים פה זה log file sync – כלומר המתנה לסנכרון קובץ ה-redo log וה-redo log buffer.
בפוסט הבא נדבר על פתרון אפשרי לבעיה על ידי שינוי פרמטרים ושינוי ההתנהגות של מנגנון ה-redo. שווה לחכות…
תודה, נושא חשוב מאוד!!