ה-InterSystems IRIS DBMS תומך במבנים מעניינים לאחסון נתונים - גלובליים. בעיקרו של דבר, מדובר במפתחות מרובי רמות עם כל מיני דברים טובים נוספים בצורת טרנזקציות, פונקציות מהירות למעבר עצי נתונים, מנעולים ושפת ObjectScript משלה.
קרא עוד על גלובלים בסדרת המאמרים "גלובלים הם חרבות אוצר לאחסון נתונים":
התעניינתי כיצד מבצעים עסקאות בגלובליות, אילו תכונות יש. אחרי הכל, מדובר במבנה שונה לחלוטין לאחסון נתונים מהטבלאות הרגילות. רמה הרבה יותר נמוכה.
כידוע מהתיאוריה של מסדי נתונים יחסיים, יישום טוב של טרנזקציות חייב לעמוד בדרישות :
א - אטומי (אטומיות). כל השינויים שנעשו בעסקה או לא נרשמו כלל.
ג - עקביות. לאחר השלמת עסקה, המצב הלוגי של מסד הנתונים חייב להיות עקבי פנימי. במובנים רבים דרישה זו נוגעת למתכנת, אך במקרה של מסדי נתונים של SQL היא נוגעת גם למפתחות זרים.
אני - לבודד. עסקאות הפועלות במקביל לא אמורות להשפיע זו על זו.
D - עמיד. לאחר השלמת עסקה מוצלחת, בעיות ברמות נמוכות יותר (הפסקת חשמל, למשל) לא אמורות להשפיע על הנתונים שהשתנו על ידי העסקה.
גלובלים הם מבני נתונים לא רציונליים. הם תוכננו לרוץ סופר מהיר על חומרה מוגבלת מאוד. בואו נסתכל על יישום עסקאות בגלובלים באמצעות .
כדי לתמוך בעסקאות ב- IRIS, נעשה שימוש בפקודות הבאות: , , .
1. אטומיות
הדרך הקלה ביותר לבדוק היא אטומיות. אנחנו בודקים ממסוף מסד הנתונים.
Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMITואז נסכם:
Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)אנחנו מקבלים:
1 2 3הכל בסדר. האטומיות נשמרת: כל השינויים מתועדים.
בואו נסבך את המשימה, נציג שגיאה ונראה איך העסקה נשמרת, חלקית או בכלל לא.
בוא נבדוק שוב את האטומיות:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3אז נעצור בכוח את המכולה, נשגר אותה ונראה.
docker kill my-irisפקודה זו כמעט שוות ערך לכיבוי כוח, מכיוון שהיא שולחת אות SIGKILL לעצור את התהליך באופן מיידי.
אולי העסקה נשמרה חלקית?
WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)- לא, זה לא שרד.
בוא ננסה את פקודת החזרה לאחור:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TROLLBACK
WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)גם שום דבר לא שרד.
2. עקביות
מכיוון שבבסיסי נתונים המבוססים על גלובלים, מפתחות נוצרים גם על גלובלים (להזכירך שגלובל הוא מבנה ברמה נמוכה יותר לאחסון נתונים מאשר טבלה רלציונית), כדי לעמוד בדרישת העקביות, יש לכלול שינוי במפתח באותה עסקה כמו שינוי בגלובלי.
לדוגמה, יש לנו אדם גלובלי, שבו אנו מאחסנים אישים ואנחנו משתמשים ב-TIN כמפתח.
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...על מנת לבצע חיפוש מהיר לפי שם משפחה ושם פרטי, יצרנו את מפתח ^index.
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1כדי שמסד הנתונים יהיה עקבי, עלינו להוסיף את הפרסונה כך:
TSTART
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
TCOMMITבהתאם לכך, בעת המחיקה עלינו להשתמש גם בעסקה:
TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMITבמילים אחרות, מילוי דרישת העקביות מושתת לחלוטין על כתפי המתכנת. אבל כשמדובר בגלובלים, זה נורמלי, בשל האופי הנמוך שלהם.
3. בידוד
כאן מתחילות הפרא. משתמשים רבים עובדים בו-זמנית על אותו מסד נתונים, ומשנים את אותם נתונים.
המצב דומה לזה שמשתמשים רבים עובדים בו-זמנית עם אותו מאגר קוד ומנסים לבצע שינויים בו-זמנית לקבצים רבים בו-זמנית.
מסד הנתונים צריך לסדר את הכל בזמן אמת. בהתחשב בכך שבחברות רציניות יש אפילו אדם מיוחד שאחראי על בקרת גרסאות (על מיזוג סניפים, פתרון סכסוכים וכו'), ואת כל זה על המאגר לעשות בזמן אמת, מורכבות המשימה ונכונות ה- עיצוב מסד נתונים וקוד שמשרת אותו.
מסד הנתונים אינו יכול להבין את משמעות הפעולות שמבצעים המשתמשים על מנת למנוע התנגשויות אם הם עובדים על אותם נתונים. זה יכול לבטל רק עסקה אחת שמתנגשת עם אחרת, או לבצע אותן ברצף.
בעיה נוספת היא שבמהלך ביצוע עסקה (לפני commit), מצב בסיס הנתונים עלול להיות לא עקבי, ולכן רצוי שלעסקאות אחרות לא תהיה גישה למצב הבלתי עקבי של בסיס הנתונים, שמושג במאגרי מידע יחסיים במובנים רבים: יצירת תצלומי מצב, שורות בריבוי גרסאות וכו'.
בביצוע עסקאות במקביל, חשוב לנו שהן לא יפריעו זו לזו. זו תכונת הבידוד.
SQL מגדיר 4 רמות בידוד:
- קרא ללא מחויבות
- קרא מחויב
- קריאה ניתנת לחזרה
- ניתן לסידרה
בואו נסתכל על כל רמה בנפרד. העלויות של יישום כל רמה גדלות כמעט באופן אקספוננציאלי.
קרא ללא מחויבות - זוהי רמת הבידוד הנמוכה ביותר, אך יחד עם זאת המהירה ביותר. עסקאות יכולות לקרוא שינויים שנעשו על ידי זה.
קרא מחויב היא הרמה הבאה של הבידוד, שהיא פשרה. עסקאות אינן יכולות לקרוא את השינויים של זה לפני ההתחייבות, אך הן יכולות לקרוא את כל השינויים שנעשו לאחר ההתחייבות.
אם יש לנו עסקה ארוכה T1, שבמהלכה התבצעו התחייבויות בטרנזקציות T2, T3 ... Tn, שעבדו עם אותם נתונים כמו T1, אז כשמבקשים נתונים ב-T1 נקבל בכל פעם תוצאה שונה. תופעה זו נקראת קריאה בלתי חוזרת.
קריאה ניתנת לחזרה - ברמת בידוד זו אין לנו תופעה של קריאה בלתי חוזרת, בשל העובדה שלכל בקשה לקריאת נתונים נוצרת תמונת מצב של נתוני התוצאה וכאשר נעשה שימוש חוזר באותה עסקה, הנתונים מתמונת המצב משמש. עם זאת, ניתן לקרוא נתוני פנטום ברמת בידוד זו. זה מתייחס לקריאת שורות חדשות שנוספו על ידי עסקאות מחויבות מקבילות.
ניתן לסידרה - רמת הבידוד הגבוהה ביותר. הוא מאופיין בעובדה שהנתונים המשמשים בכל דרך בעסקה (קריאה או שינוי) הופכים לזמינים לעסקאות אחרות רק לאחר השלמת העסקה הראשונה.
ראשית, בואו נבין אם יש בידוד של פעולות בעסקה מהשרשור הראשי. בואו נפתח 2 חלונות מסוף.
Kill ^t
Write ^t(1)
2
TSTART
Set ^t(1)=2אין בידוד. שרשור אחד רואה מה עושה השני שפתח את העסקה.
בואו נראה אם עסקאות של שרשורים שונים רואים מה קורה בתוכם.
בואו נפתח 2 חלונות מסוף ונפתח 2 עסקאות במקביל.
kill ^t
TSTART
Write ^t(1)
3
TSTART
Set ^t(1)=3
עסקאות מקבילות רואות את הנתונים זו של זו. אז, קיבלנו את רמת הבידוד הפשוטה ביותר, אך גם המהירה ביותר, READ UNCOMMITED.
באופן עקרוני, ניתן היה לצפות זאת עבור גלובלים, שעבורם ביצועים תמיד היו בראש סדר העדיפויות.
מה אם אנחנו צריכים רמה גבוהה יותר של בידוד בפעולות על גלובליות?
כאן אתה צריך לחשוב למה בכלל יש צורך ברמות בידוד וכיצד הן פועלות.
רמת הבידוד הגבוהה ביותר, SERIALIZE, פירושה שהתוצאה של עסקאות המבוצעות במקביל שווה ערך לביצוען ברצף, מה שמבטיח היעדר התנגשויות.
אנחנו יכולים לעשות זאת באמצעות מנעולים חכמים ב-ObjectScript, שיש להם הרבה שימושים שונים: אתה יכול לעשות נעילה רגילה, מצטברת, מרובה עם הפקודה .
רמות בידוד נמוכות יותר הן פשרות שנועדו להגביר את מהירות מסד הנתונים.
בואו נראה כיצד נוכל להשיג רמות שונות של בידוד באמצעות מנעולים.
מפעיל זה מאפשר לך לקחת לא רק מנעולים בלעדיים הדרושים לשינוי נתונים, אלא מה שנקרא מנעולים משותפים, שיכולים לקחת מספר שרשורים במקביל כאשר הם צריכים לקרוא נתונים שאסור לשנות על ידי תהליכים אחרים במהלך תהליך הקריאה.
מידע נוסף על שיטת החסימה הדו-שלבית ברוסית ובאנגלית:
→
→
הקושי הוא שבמהלך עסקה מצב מסד הנתונים עשוי להיות לא עקבי, אבל הנתונים הלא עקביים האלה גלויים לתהליכים אחרים. איך להימנע מכך?
באמצעות מנעולים ניצור חלונות נראות בהם מצב מסד הנתונים יהיה עקבי. וכל הגישה לחלונות ראות כאלה של המדינה המוסכמת תהיה נשלטת על ידי מנעולים.
מנעולים משותפים על אותם נתונים ניתנים לשימוש חוזר - מספר תהליכים יכולים לקחת אותם. מנעולים אלו מונעים מתהליכים אחרים לשנות נתונים, כלומר. הם משמשים ליצירת חלונות של מצב מסד נתונים עקבי.
מנעולים בלעדיים משמשים לשינויי נתונים - רק תהליך אחד יכול לקחת מנעול כזה. מנעול בלעדי ניתן לקחת על ידי:
- כל תהליך אם הנתונים חופשיים
- רק התהליך שיש לו נעילה משותפת על הנתונים הללו והיה הראשון שביקש נעילה בלעדית.

ככל שחלון הנראות צר יותר, תהליכים אחרים צריכים לחכות לו יותר, אך מצב מסד הנתונים בתוכו יכול להיות עקבי יותר.
READ_COMMITTED - המהות של רמה זו היא שאנו רואים רק נתונים מחויבים משרשורים אחרים. אם הנתונים בעסקה אחרת עדיין לא התחייבו, אז אנו רואים את הגרסה הישנה שלו.
זה מאפשר לנו להקביל את העבודה במקום לחכות לשחרור המנעול.
ללא טריקים מיוחדים, לא נוכל לראות את הגרסה הישנה של הנתונים ב-IRIS, ולכן נצטרך להסתפק במנעולים.
בהתאם לכך, נצטרך להשתמש במנעולים משותפים כדי לאפשר קריאת נתונים רק ברגעים של עקביות.
נניח שיש לנו בסיס משתמשים ^אדם שמעביר כסף אחד לשני.
רגע העברה מאדם 123 לאדם 242:
LOCK +^person(123), +^person(242)
Set ^person(123, amount) = ^person(123, amount) - amount
Set ^person(242, amount) = ^person(242, amount) + amount
LOCK -^person(123), -^person(242)רגע בקשת סכום הכסף מאדם 123 לפני החיוב חייב להיות מלווה בחסימה בלעדית (כברירת מחדל):
LOCK +^person(123)
Write ^person(123)ואם אתה צריך להציג את סטטוס החשבון בחשבון האישי שלך, אתה יכול להשתמש במנעול משותף או לא להשתמש בו בכלל:
LOCK +^person(123)#”S”
Write ^person(123)עם זאת, אם נניח שפעולות מסד נתונים מבוצעות כמעט באופן מיידי (תן לי להזכיר לך שגלובלים הם מבנה ברמה נמוכה בהרבה מטבלה רלציונית), אזי הצורך ברמה זו יורד.
קריאה ניתנת לחזרה - רמת בידוד זו מאפשרת קריאות מרובות של נתונים שניתן לשנות על ידי עסקאות במקביל.
בהתאם לכך, נצטרך לשים נעילה משותפת על קריאת הנתונים שאנו משנים ונעילה בלעדית על הנתונים שאנו משנים.
למרבה המזל, מפעיל LOCK מאפשר לך לרשום בפירוט את כל המנעולים הדרושים, מהם יכולים להיות הרבה, בהצהרה אחת.
LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)פעולות אחרות (בשלב זה שרשורים מקבילים מנסים לשנות את ^person(123, כמות), אך אינם יכולים)
LOCK +^person(123, amount)
изменение ^person(123, amount)
LOCK -^person(123, amount)
чтение ^person(123, amount)
LOCK -^person(123, amount)#”S”כשמפרטים מנעולים מופרדים בפסיקים, הם נלקחים ברצף, אבל אם תעשה זאת:
LOCK +(^person(123),^person(242))ואז הם נלקחים בבת אחת באופן אטומי.
סדר מחדש - נצטרך להגדיר מנעולים כך שבסופו של דבר כל העסקאות שיש להן נתונים משותפים יבוצעו ברצף. עבור גישה זו, רוב המנעולים צריכים להיות בלעדיים ולהיצמד לשטחים הקטנים ביותר בעולם לביצועים.
אם אנחנו מדברים על חיוב כספים באדם הגלובלי, אז רק רמת הבידוד של SERIALIZE מקובלת עבורו, שכן יש להוציא כסף ברצף, אחרת אפשר להוציא את אותו הסכום כמה פעמים.
4. עמידות
ערכתי בדיקות עם חיתוך קשה של המיכל באמצעות
docker kill my-irisהבסיס סבל אותם היטב. לא זוהו בעיות.
מסקנה
עבור גלובלים, ל-InterSystems IRIS יש תמיכה בעסקאות. הם באמת אטומיים ואמינים. כדי להבטיח עקביות של מסד נתונים המבוסס על גלובלים, נדרשים מאמצי מתכנת ושימוש בטרנזקציות, מכיוון שאין בו מבנים מובנים מורכבים כגון מפתחות זרים.
רמת הבידוד של גלובלים ללא שימוש במנעולים היא READ UNCOMMITED, ובעת שימוש במנעולים ניתן להבטיח זאת עד לרמת SERIALIZE.
הנכונות והמהירות של עסקאות בגלובליות תלויות מאוד במיומנות המתכנת: ככל שמשתמשים במנעולים משותפים יותר בקריאה, ככל שרמת הבידוד גבוהה יותר, וככל שנלקחים יותר מנעולים בלעדיים יותר, כך הביצועים מהירים יותר.
מקור: www.habr.com
