בדף הזה מתוארות עסקאות ב-Spanner ומוצגים ממשקי העסקאות של Spanner ל-DML עם הרשאות קריאה וכתיבה, עם הרשאות קריאה בלבד ועם חלוקה למחיצות.
טרנזקציה ב-Spanner היא קבוצה של פעולות קריאה וכתיבה. כל הפעולות בעסקה הן אטומיות, כלומר כולן מצליחות או כולן נכשלות.
סשן משמש לביצוע טרנזקציות במסד נתונים של Spanner. סשן מייצג ערוץ תקשורת לוגי עם שירות מסד הנתונים של Spanner. במהלך ביקורים אפשר לבצע עסקה אחת או כמה עסקאות בו-זמנית. מידע נוסף זמין במאמר בנושא סשנים.
סוגי עסקאות
Spanner תומך בסוגי העסקאות הבאים, שכל אחד מהם מיועד לדפוסי אינטראקציה ספציפיים עם נתונים:
קריאה-כתיבה: העסקאות האלה משמשות לפעולות קריאה וכתיבה, ואחריהן מתבצעת פעולת אישור (commit). יכול להיות שהם יקבלו גישה למנעולים. אם הן ייכשלו, יהיה צורך לנסות שוב. הן מוגבלות למסד נתונים יחיד, אבל הן יכולות לשנות נתונים בכמה טבלאות במסד הנתונים הזה.
קריאה בלבד: העסקאות האלה מבטיחות עקביות של הנתונים בכמה פעולות קריאה, אבל לא מאפשרות שינויים בנתונים. הן מופעלות בחותמת זמן שנקבעת על ידי המערכת כדי לשמור על עקביות, או בחותמת זמן מהעבר שהמשתמש הגדיר. בניגוד לעסקאות קריאה-כתיבה, הן לא דורשות פעולת אישור (commit) או נעילות. עם זאת, יכול להיות שהם יושהו כדי להמתין לסיום של פעולות כתיבה שמתבצעות כרגע.
חלוקת DML: סוג העסקה הזה מבצע פקודות DML כפעולות של חלוקת DML. היא מותאמת לביצוע הצהרות DML בהיקף גדול, אבל עם הגבלות כדי להבטיח שההצהרה תהיה אידמפוטנטית וניתנת לחלוקה באופן שיאפשר לה לפעול באופן עצמאי ממחיצות אחרות. אם אתם צריכים לבצע הרבה פעולות כתיבה שלא דורשות טרנזקציה אטומית, כדאי להשתמש בכתיבה של קבוצות. מידע נוסף מופיע במאמר שינוי נתונים באמצעות כתיבה של קבוצות.
עסקאות קריאה וכתיבה
עסקת קריאה-כתיבה מורכבת מאפס או יותר קריאות או הצהרות של שאילתות שאחריהן בקשת ביצוע (commit). בכל שלב לפני בקשת השמירה, הלקוח יכול לשלוח בקשת ביטול כדי להפסיק את הטרנזקציה.
בידוד ניתן לסריאליזציה
בשימוש ברמת הבידוד הניתנת לסריאליזציה שמוגדרת כברירת מחדל, טרנזקציות של קריאה-כתיבה קוראות, משנות וכותבות נתונים באופן אטומי. סוג העסקה הזה הוא עקבי חיצונית.
כשמשתמשים בעסקאות קריאה-כתיבה, מומלץ לצמצם את משך הזמן שבו העסקה פעילה. משך העסקאות הקצר יותר גורם לכך שהנעילות נשמרות למשך זמן קצר יותר, מה שמגדיל את הסבירות לביצוע מוצלח של פעולת commit ומפחית את המחלוקות. הסיבה לכך היא שנְעילות שנשמרות לאורך זמן עלולות להוביל לקיפאון ולביטול עסקאות. מערכת Spanner מנסה לשמור על נעילות קריאה פעילות כל עוד העסקאות ממשיכות לבצע קריאות, והעסקאות לא הסתיימו באמצעות אישור או ביטול. אם הלקוח נשאר לא פעיל למשך תקופות ארוכות, יכול להיות ש-Spanner ישחרר את הנעילות של העסקה ויבטל אותה.
כדי לבצע פעולת כתיבה שתלויה בפעולת קריאה אחת או יותר, צריך להשתמש בעסקת קריאה-כתיבה:
- אם אתם צריכים לבצע פעולות כתיבה אטומיות, אתם צריכים לבצע את פעולות הכתיבה האלה באותה טרנזקציה של קריאה וכתיבה. לדוגמה, אם אתם מעבירים 200$ מחשבון א' לחשבון ב', המערכת מבצעת את שתי פעולות הכתיבה (הפחתה של 200 $מחשבון א' והגדלה של 200 $בחשבון ב') וגם את פעולות הקריאה של יתרות החשבונות ההתחלתיות באותה טרנזקציה.
- אם רוצים להכפיל את היתרה בחשבון א', צריך לבצע את פעולות הקריאה והכתיבה באותה טרנזקציה. כך המערכת קוראת את היתרה לפני שהיא מכפילה אותה ומעדכנת אותה.
- אם פעולות הכתיבה תלויות בפעולות הקריאה, צריך לבצע את שתיהן באותה טרנזקציה של קריאה-כתיבה, גם אם פעולות הכתיבה לא מבוצעות. לדוגמה, אם רוצים להעביר 200 $מחשבון א' לחשבון ב' רק אם היתרה בחשבון א' גבוהה מ-500$, צריך לכלול את הקריאה של היתרה בחשבון א' ואת פעולות הכתיבה המותנות באותה עסקה, גם אם ההעברה לא מתבצעת.
כדי לבצע פעולות קריאה, צריך להשתמש בשיטת קריאה יחידה או בעסקה לקריאה בלבד:
- אם אתם מבצעים רק פעולות קריאה, ואתם יכולים לבטא את פעולת הקריאה באמצעות שיטת קריאה יחידה, השתמשו בשיטת הקריאה היחידה או בעסקה לקריאה בלבד. בניגוד לעסקאות של קריאה וכתיבה, קריאות בודדות לא מקבלות נעילות.
בידוד קריאות חוזרות
ב-Spanner, בידוד קריאה חוזרת מיושם באמצעות טכניקה שנקראת בידוד snapshot. בידוד של קריאה חוזרת מבטיח שכל פעולות הקריאה בתוך טרנזקציה יהיו עקביות עם מסד הנתונים כפי שהיה בתחילת הטרנזקציה. היא גם מבטיחה שפעולות כתיבה בו-זמניות לאותם נתונים יצליחו רק אם אין התנגשויות.
בנעילה אופטימית שמוגדרת כברירת מחדל, לא מתבצעת נעילה עד לזמן השמירה אם צריך לכתוב נתונים. אם יש סתירה בין הנתונים שנכתבו או בגלל אירועים חולפים ב-Spanner, כמו הפעלה מחדש של שרת, יכול להיות ש-Spanner עדיין יבטל את העסקאות. מכיוון שפעולות קריאה בטרנזקציות עם הרשאת קריאה וכתיבה לא מקבלות נעילות בבידוד קריאה חוזרת, אין הבדל בין ביצוע פעולות קריאה בלבד בטרנזקציה עם הרשאת קריאה בלבד לבין ביצוע פעולות קריאה בלבד בטרנזקציה עם הרשאת קריאה וכתיבה.
כדאי להשתמש בעסקאות עם הרשאות קריאה וכתיבה בבידוד קריאה חוזרת בתרחישים הבאים:
- עומס העבודה הוא בעיקר של קריאה, ויש מעט התנגשויות בכתיבה.
- האפליקציה חווה צווארי בקבוק בביצועים בגלל עיכובים שנובעים ממחלוקות על נעילה וביטולים של טרנזקציות שנגרמים על ידי טרנזקציות ישנות יותר עם עדיפות גבוהה יותר, שפוגעות בטרנזקציות חדשות יותר עם עדיפות נמוכה יותר כדי למנוע מצבים פוטנציאליים של חסימה הדדית (wound-wait).
- האפליקציה לא דורשת את ההבטחות המחמירות יותר שניתנות על ידי רמת הבידוד הניתנת לסריאליזציה.
כשמבצעים פעולת כתיבה שתלויה בפעולת קריאה אחת או יותר, יכול להיות שיהיה הטיה בכתיבה בבידוד קריאה חוזרת. הטיה בכתיבה נובעת מסוג מסוים של עדכון מקביל, שבו כל עדכון מתקבל באופן עצמאי, אבל ההשפעה המשולבת שלהם פוגעת בתקינות הנתונים של האפליקציה.
לכן, חשוב לוודא שפעולות קריאה שמתבצעות בחלק הקריטי של טרנזקציה כוללות את הסעיף FOR UPDATE או את הרמז lock_scanned_ranges=exclusive כדי להימנע משינוי נתונים לא עקבי. מידע נוסף זמין במאמרים בנושא קונפליקטים של קריאה-כתיבה ונכונות ובדוגמה שמוסברת במאמר בנושא סמנטיקה של קריאה-כתיבה.
ממשק
ספריות הלקוח של Spanner מספקות ממשק להרצת קבוצת פעולות בתוך טרנזקציה של קריאה וכתיבה, עם ניסיונות חוזרים לביטול טרנזקציות. יכול להיות שיהיה צורך לנסות לבצע עסקה כמה פעמים לפני שהיא תאושר.
יש כמה מצבים שבהם העסקה עלולה להיכשל. לדוגמה, אם שתי טרנזקציות מנסות לשנות נתונים בו-זמנית, יכול להיות שייווצר מצב של חסימה הדדית. במקרים כאלה, Spanner מבטל עסקה אחת כדי לאפשר לעסקה השנייה להתבצע. לעיתים רחוקות יותר, אירועים חולפים ב-Spanner יכולים גם לגרום לביטול עסקאות.
כל העסקאות מסוג קריאה-כתיבה מספקות את מאפייני ה-ACID של מסדי נתונים רלציוניים.
מכיוון שעסקאות הן אטומיות, ביטול של עסקה לא משפיע על מסד הנתונים. ספריות הלקוח של Spanner מנסות לבצע שוב עסקאות כאלה באופן אוטומטי, אבל אם אתם לא משתמשים בספריות הלקוח, כדאי לנסות לבצע שוב את העסקה באותה סשן כדי לשפר את שיעורי ההצלחה. כל ניסיון חוזר שמסתיים בשגיאה ABORTED מגדיל את עדיפות הנעילה של הטרנזקציה. בנוסף, מנהלי ההתקנים של לקוח Spanner כוללים לוגיקה פנימית לניסיון חוזר של עסקאות, שמסתירה שגיאות זמניות על ידי הפעלה מחדש של העסקה.
כשמשתמשים בעסקה בספריית לקוח של Spanner, מגדירים את גוף העסקה כאובייקט פונקציה. הפונקציה הזו מכילה את פעולות הקריאה והכתיבה שמתבצעות בטבלה אחת או יותר במסד הנתונים. ספריית הלקוח של Spanner מפעילה את הפונקציה הזו שוב ושוב עד שהטרנזקציה מתבצעת בהצלחה או שמתרחשת שגיאה שלא ניתן לנסות שוב.
דוגמה
נניח שיש לכם עמודה MarketingBudget בטבלה Albums:
CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), MarketingBudget INT64 ) PRIMARY KEY (SingerId, AlbumId);
מחלקת השיווק מבקשת להעביר 200,000 $מהתקציב של Albums
(2, 2) אל Albums (1, 1), אבל רק אם הסכום הזה זמין בתקציב של האלבום. כדאי להשתמש בטרנזקציה של קריאה וכתיבה עם נעילה לפעולה הזו, כי הטרנזקציה עשויה לבצע כתיבות בהתאם לתוצאה של קריאה.
בדוגמאות הבאות של ספריות לקוח מוצג איך לבצע טרנזקציה של קריאה וכתיבה באמצעות רמת הבידוד הסדרתית שמוגדרת כברירת מחדל:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
דוגמאות לאופן הביצוע של טרנזקציית קריאה-כתיבה באמצעות בידוד קריאה חוזרת מופיעות במאמר שימוש ברמת בידוד של קריאה חוזרת.
סמנטיקה
בקטע הזה מוסבר על הסמנטיקה של עסקאות קריאה-כתיבה ב-Spanner.
מאפיינים
בידוד ניתן לסדרות הוא רמת הבידוד שמוגדרת כברירת מחדל ב-Spanner. במסגרת בידוד ניתן לסדר את הפעולות בסדר מסוים, ו-Spanner מספק ללקוחות את ההבטחות המחמירות ביותר לגבי בקרת מקביליות של עסקאות, עקביות חיצונית. עסקת קריאה-כתיבה מבצעת קבוצה של קריאות וכתיבות באופן אטומי. פעולות כתיבה יכולות להמשיך בלי להיחסם על ידי עסקאות לקריאה בלבד. חותמת הזמן שבה מתבצעות טרנזקציות של קריאה וכתיבה תואמת לזמן שחלף. סדר הסריאליזציה תואם לסדר חותמות הזמן.
בגלל המאפיינים האלה, מפתחי אפליקציות יכולים להתמקד בנכונות של כל עסקה בנפרד, בלי לדאוג לגבי האופן שבו אפשר להגן על הביצוע שלה מפני עסקאות אחרות שעשויות להתבצע באותו זמן.
אפשר גם להריץ את עסקאות הקריאה והכתיבה באמצעות בידוד קריאה חוזרת. בידוד של קריאה חוזרת מבטיח שכל פעולות הקריאה בתוך טרנזקציה יראו תמונה עקבית וחזקה של מסד הנתונים כפי שהיה בתחילת הטרנזקציה. מידע נוסף זמין במאמר בנושא בידוד של קריאות חוזרות.
עסקאות קריאה-כתיבה עם בידוד שניתן לסדר
אחרי ביצוע מוצלח של טרנזקציה שמכילה סדרה של פעולות קריאה וכתיבה בבידוד ברירת המחדל שניתן לסדר, מתרחשים הדברים הבאים:
- העסקה מחזירה ערכים שמשקפים תמונת מצב עקבית בחותמת הזמן של אישור העסקה.
- שורות או טווחים ריקים יישארו ריקים בזמן השמירה.
- העסקה מבצעת את כל פעולות הכתיבה בחותמת הזמן של אישור העסקה.
- אף עסקה לא יכולה לראות את הפעולות עד שהעסקה מתבצעת.
מנהלי התקנים של לקוחות Spanner כוללים לוגיקה של ניסיון חוזר של עסקאות שמסתירה שגיאות זמניות על ידי הפעלה מחדש של העסקה ואימות הנתונים שהלקוח רואה.
התוצאה היא שכל פעולות הקריאה והכתיבה נראות כאילו הן התרחשו בנקודת זמן אחת, גם מנקודת המבט של העסקה עצמה וגם מנקודת המבט של קוראים וכותבים אחרים במסד הנתונים של Spanner. כלומר, פעולות הקריאה והכתיבה מתרחשות באותה חותמת זמן. לדוגמה, ראו Serializability and external consistency.
עסקאות קריאה-כתיבה עם בידוד קריאה חוזרת
אחרי שמאשרים עסקה עם בידוד של קריאה חוזרת, מתקיימים התנאים הבאים:
- הטרנזקציה מחזירה ערכים שמשקפים תמונת מצב עקבית של מסד הנתונים. תמונת המצב נוצרת בדרך כלל במהלך פעולת הטרנזקציה הראשונה, ויכול להיות שהיא לא תהיה זהה לחותמת הזמן של השמירה.
- מאחר שקריאה חוזרת מיושמת באמצעות בידוד snapshot, העסקה מאשרת את כל הכתיבות בחותמת הזמן של אישור העסקה רק אם קבוצת הכתיבה לא השתנתה בין חותמת הזמן של תמונת המצב של העסקה לבין חותמת הזמן של אישור העסקה.
- עסקאות אחרות לא רואות את הפעולות עד שהעסקה מתבצעת.
בידוד של טרנזקציות לקריאה וכתיבה עם פעולות לקריאה בלבד
כשעסקה עם הרשאת קריאה וכתיבה מבצעת רק פעולות קריאה, היא מספקת ערבויות עקביות דומות לעסקה עם הרשאת קריאה בלבד. כל הקריאות בתוך העסקה מחזירות נתונים מחותמת זמן עקבית, כולל אישור של שורות שלא קיימות.
הבדל אחד הוא מתי מתבצעת פעולת אישור של טרנזקציה עם הרשאות קריאה וכתיבה בלי לבצע פעולת כתיבה. בתרחיש הזה, אין ערובה לכך שהנתונים שנקראו במסגרת העסקה לא השתנו במסד הנתונים בין פעולת הקריאה לבין אישור העסקה.
כדי לוודא את עדכניות הנתונים ולאמת שהנתונים לא השתנו מאז האחזור האחרון שלהם, נדרשת קריאה נוספת. אפשר לבצע את הקריאה מחדש במסגרת טרנזקציה אחרת של קריאה וכתיבה, או באמצעות קריאה חזקה.
כדי להשיג יעילות אופטימלית, אם טרנזקציה מבצעת קריאות בלבד, כדאי להשתמש בטרנזקציה לקריאה בלבד במקום בטרנזקציה לקריאה ולכתיבה, במיוחד כשמשתמשים בבידוד ניתן לסדר.
מה ההבדל בין סריאליזציה ועקביות חיצונית לבין קריאה חוזרת
כברירת מחדל, Spanner מציע ערבויות חזקות לעסקאות, כולל אפשרות לסדר את העסקאות בסדר מסוים ועקביות חיצונית. המאפיינים האלה מבטיחים שהנתונים יישארו עקביים והפעולות יתבצעו בסדר צפוי, גם בסביבה מבוזרת.
התכונה 'ניתנות לסידור בסדרות' מבטיחה שכל הטרנזקציות יתבצעו אחת אחרי השנייה בסדר רציף, גם אם הן מעובדות בו-זמנית. ב-Spanner, כדי להשיג את זה, מוקצים חותמות זמן של ביצוע (commit) לעסקאות, שמשקפות את הסדר שבו הן בוצעו.
Spanner מספק ערבות חזקה עוד יותר שנקראת עקביות חיצונית. המשמעות היא שלא רק שהטרנזקציות מתבצעות בסדר שמשתקף בחותמות הזמן שלהן, אלא שחותמות הזמן האלה גם תואמות לזמן בעולם האמיתי. כך תוכלו להשוות בין חותמות הזמן של השליחות לבין הזמן האמיתי, ולקבל תצוגה עקבית ומסודרת גלובלית של הנתונים.
במילים אחרות, אם טרנזקציה Txn1 מתבצעת לפני טרנזקציה אחרת Txn2 בזמן אמת, חותמת הזמן של ביצוע Txn1 מוקדמת יותר מחותמת הזמן של ביצוע Txn2.
דוגמה:
בתרחיש הזה, במהלך ציר הזמן t:
- העסקה
Txn1קוראת נתוניםA, מעבירה כתיבה ל-Aואז מתבצעת בהצלחה. - העסקה
Txn2מתחילה אחרי שTxn1מתחיל. הוא קורא נתוניםBואז קורא נתוניםA.
למרות ש-Txn2 התחיל לפני ש-Txn1 הסתיים, Txn2 רואה את השינויים ש-Txn1 ביצע ב-A. הסיבה לכך היא ש-Txn2 קורא את A אחרי ש-Txn1 מבצע את הכתיבה שלו ל-A.
יכול להיות שזמן הביצוע של Txn1 ושל Txn2 יחפוף, אבל חותמות הזמן של השמירה שלהם, c1 ו-c2 בהתאמה, מבטיחות סדר לינארי של העסקאות. המשמעות היא:
- נראה שכל פעולות הקריאה והכתיבה ב-
Txn1התרחשו בנקודה אחת בזמן,c1. - נראה שכל פעולות הקריאה והכתיבה ב-
Txn2התרחשו בנקודה אחת בזמן,c2. - חשוב לציין ש-
c1מוקדם יותר מ-c2לכתיבות מחויבות, גם אם הכתיבות התרחשו במכונות שונות. אםTxn2מבצע רק קריאות,c1מוקדם מ-c2או באותו זמן.
הסדר החזק הזה אומר שאם פעולת קריאה עוקבת מבחינה בהשפעות של Txn2, היא מבחינה גם בהשפעות של Txn1. הערך של המאפיין הזה הוא true לכל העסקאות שאושרו בהצלחה.
לעומת זאת, אם משתמשים בבידוד קריאה חוזרת, התרחיש הבא מתרחש עבור אותן טרנזקציות:
-
Txn1מתחיל לקרוא נתוניםA, ויוצר תמונת מצב משלו של מסד הנתונים באותו רגע. -
Txn2מתחיל לקרוא נתוניםBויוצר תמונת מצב משלו. - לאחר מכן,
Txn1משנה את הנתוניםAומבצע את השינויים בהצלחה. Txn2ניסיונות לקריאת נתוניםA. חשוב לזכור: מכיוון שהיא פועלת על סמך תמונת מצב מוקדמת יותר,Txn2לא רואה את העדכון שבוצע כרגע ב-A.Txn1Txn2קוראת את הערך הישן.-
Txn2modifies dataBand commits.
בתרחיש הזה, כל עסקה פועלת על תמונת מצב עקבית משלה של מסד הנתונים, שנוצרה מהרגע שבו העסקה מתחילה. הרצף הזה יכול להוביל לחריגה של הטיה בכתיבה אם הכתיבה אל B על ידי Txn2 הייתה תלויה באופן לוגי בערך שהיא קראה מ-A. במילים אחרות, Txn2 ביצע את העדכונים שלו על סמך מידע לא עדכני, והפעולה הבאה שלו עלולה להפר את הכלל הבלתי משתנה ברמת האפליקציה. כדי למנוע את התרחיש הזה, אפשר להשתמש ב-SELECT...FOR UPDATE כדי לבודד קריאות חוזרות או ליצור אילוצי בדיקה בסכימה.
קריאה וכתיבה של ערבויות במקרה של כשל בעסקה
אם קריאה להפעלת טרנזקציה נכשלת, ערבויות הקריאה והכתיבה שאתם מקבלים תלויות בשגיאה שגרמה לכישלון של קריאת ה-commit הבסיסית.
יכול להיות ש-Spanner יבצע את הפעולות של עסקה מסוימת כמה פעמים באופן פנימי. אם ניסיון ההפעלה נכשל, השגיאה שמוחזרת מציינת את התנאים שהתרחשו ומציינת את ההתחייבויות שאתם מקבלים. עם זאת, אם Spanner ינסה שוב לבצע את העסקה, יכול להיות שיהיו כמה תופעות לוואי מהפעולות שלה (לדוגמה, שינויים במערכות חיצוניות או במצב המערכת מחוץ למסד נתונים של Spanner).
כשטרנזקציה ב-Spanner נכשלת, ההבטחות שאתם מקבלים לגבי פעולות קריאה וכתיבה תלויות בשגיאה הספציפית שנתקלתם בה במהלך פעולת ה-commit.
לדוגמה, הודעת שגיאה כמו 'השורה לא נמצאה' או 'השורה כבר קיימת' מציינת בעיה במהלך הכתיבה של שינויים זמניים שנשמרו בזיכרון. מצב כזה יכול לקרות אם, למשל, שורה שהלקוח מנסה לעדכן לא קיימת. במקרים הבאים:
- הקריאות עקביות: כל נתון שנקרא במהלך העסקה מובטח להיות עקבי עד לנקודה שבה מתרחשת השגיאה.
- פעולות הכתיבה לא מוחלות: המוטציות שהעסקה ניסתה לבצע לא נשמרות במסד הנתונים.
- עקביות השורה: אי-הקיום (או מצב הקיום) של השורה שהפעילה את השגיאה עקבי עם פעולות הקריאה שבוצעו בתוך העסקה.
אתם יכולים לבטל פעולות קריאה אסינכרוניות ב-Spanner בכל שלב, בלי להשפיע על פעולות אחרות שמתבצעות באותה טרנזקציה. הגמישות הזו שימושית אם פעולה ברמה גבוהה יותר מבוטלת, או אם מחליטים לבטל קריאה על סמך התוצאות הראשוניות.
עם זאת, חשוב להבין ששליחת בקשה לביטול קריאה לא מבטיחה שהיא תופסק באופן מיידי. אחרי בקשת ביטול, יכול להיות שפעולת הקריאה עדיין:
- השלמה מוצלחת: יכול להיות שעיבוד הקריאה יסתיים והתוצאות יוחזרו לפני שהביטול ייכנס לתוקף.
- הקריאה נכשלה מסיבה אחרת: הקריאה יכולה להסתיים בגלל שגיאה אחרת, כמו ביטול.
- החזרת תוצאות לא מלאות: יכול להיות שהקריאה תחזיר תוצאות חלקיות, שייבדקו במסגרת תהליך אישור העסקה.
ביטול פעולת אישור מבטל את כל העסקה, אלא אם העסקה כבר אושרה או נכשלה מסיבה אחרת.
אטומיות, עקביות ועמידות
בנוסף לבידוד, Spanner מספק את שאר ההבטחות של מאפייני ACID:
- אטומיות: עסקה נחשבת אטומית אם כל הפעולות שלה הושלמו בהצלחה, או אם אף אחת מהן לא הושלמה. אם פעולה כלשהי בתוך עסקה נכשלת, העסקה כולה מוחזרת למצב המקורי שלה, וכך נשמרת תקינות הנתונים.
- עקביות: העסקה צריכה לשמור על התקינות של הכללים והאילוצים של מסד הנתונים. אחרי השלמת העסקה, מסד הנתונים צריך להיות במצב תקין, בהתאם לכללים שהוגדרו מראש.
- עמידות: אחרי שמאשרים עסקה, השינויים שלה נשמרים באופן קבוע במסד הנתונים, והם נשמרים גם במקרה של כשלים במערכת, הפסקות חשמל או שיבושים אחרים.
ביצועים
בקטע הזה מוסבר על בעיות שמשפיעות על הביצועים של טרנזקציות עם הרשאות קריאה וכתיבה.
נעילה של בקרת בו-זמניות
כברירת מחדל, Spanner מאפשר לכמה לקוחות ליצור אינטראקציה עם אותו מסד נתונים בו-זמנית ברמת הבידוד הסדרתית שמוגדרת כברירת מחדל. כדי לשמור על עקביות הנתונים בעסקאות מקבילות כאלה, ל-Spanner יש מנגנון נעילה שמשתמש גם בנעילות משותפות וגם בנעילות בלעדיות. נעילת קריאה כזו מתבצעת רק בטרנזקציות שניתנות לסדרות, ולא בטרנזקציות שמשתמשות בבידוד קריאה חוזרת.
כשעסקה שניתן לסדר אותה מבצעת פעולת קריאה, מערכת Spanner מקבלת נעילות קריאה משותפות על הנתונים הרלוונטיים. המנעולים המשותפים האלה מאפשרים לפעולות קריאה מקבילות אחרות לגשת לאותם נתונים. הבו-זמניות (concurrency) הזו נשמרת עד שהעסקה מוכנה לאשר את השינויים שלה.
בבידוד ניתן לסריאליזציה, במהלך שלב השמירה, כשפעולות הכתיבה מוחלות, העסקה מנסה לשדרג את הנעילות שלה לנעילות בלעדיות. כדי לעשות זאת, Spanner:
- חסימת בקשות חדשות לנעילת קריאה משותפת בנתונים המושפעים.
- המערכת מחכה שכל נעילות הקריאה המשותפות הקיימות של הנתונים האלה יוסרו.
- אחרי שכל נעילות הקריאה המשותפות מוסרות, היא מציבה נעילה בלעדית ומעניקה לה גישה בלעדית לנתונים למשך הכתיבה.
כשמבצעים קומיט של טרנזקציה ברמת בידוד של קריאה חוזרת, הטרנזקציה מקבלת נעילות בלעדיות של הנתונים שנכתבו. יכול להיות שהעסקה תצטרך לחכות לנעילות אם עסקה מקבילה גם מבצעת פעולות כתיבה לאותם נתונים.
הערות לגבי נעילות:
- גרנולריות: מערכת Spanner מחילה נעילות ברמת השורה והעמודה. המשמעות היא שאם טרנזקציה
T1מחזיקה נעילה בעמודהAשל שורהalbumid, טרנזקציהT2עדיין יכולה לכתוב בו-זמנית בעמודהBשל אותה שורהalbumidללא התנגשות. פעולות כתיבה ללא פעולות קריאה:
- אם אין קריאות בעסקה, יכול להיות ש-Spanner לא ידרוש נעילה בלעדית לכתיבות ללא קריאות. במקום זאת, יכול להיות שהיא תשתמש בנעילה משותפת של הכותב. הסיבה לכך היא שסדר ההחלה של פעולות כתיבה ללא קריאה נקבע לפי חותמות הזמן של ביצוע הפעולות, כך שכמה פעולות כתיבה יכולות להתבצע על אותו פריט בו-זמנית ללא התנגשות. נעילה בלעדית נדרשת רק אם העסקה קוראת קודם את הנתונים שהיא מתכוונת לכתוב.
- בבידוד של קריאה חוזרת, טרנזקציות בדרך כלל מקבלות נעילות בלעדיות לתאים שנכתבו בזמן ביצוע השינויים.
אינדקסים משניים לחיפושים בשורות: בבידוד ניתן לסריאליזציה, כשמבצעים קריאות בתוך טרנזקציה של קריאה-כתיבה, שימוש באינדקסים משניים יכול לשפר משמעותית את הביצועים. באמצעות אינדקסים משניים כדי להגביל את השורות שנסרקות לטווח קטן יותר, Spanner נועל פחות שורות בטבלה, וכך מאפשר שינוי מקביל גדול יותר של שורות מחוץ לטווח הספציפי הזה.
גישה בלעדית למשאב חיצוני: נעילות פנימיות של Spanner מיועדות לשמירה על עקביות הנתונים בתוך מסד הנתונים של Spanner עצמו. אל תשתמשו בהם כדי להבטיח גישה בלעדית למשאבים מחוץ ל-Spanner. מערכת Spanner יכולה לבטל טרנזקציות מסיבות שונות, כולל אופטימיזציות פנימיות של המערכת כמו העברת נתונים בין משאבי מחשוב. אם מתבצע ניסיון חוזר לביצוע טרנזקציה (באופן מפורש על ידי קוד האפליקציה או באופן מרומז על ידי ספריות לקוח כמו Spanner JDBC driver), מובטח שהנעילות יוחזקו רק במהלך ניסיון השמירה המוצלח.
נתוני נעילה: כדי לאבחן ולחקור התנגשויות נעילה במסד הנתונים, אפשר להשתמש בכלי נתוני נעילה.
זיהוי מצב של חסימה הדדית
מערכת Spanner מזהה מצבים שבהם יכול להיות שיהיה קיפאון בין כמה טרנזקציות, ומבטלת את כל הטרנזקציות חוץ מאחת. תארו לעצמכם את התרחיש הבא:
תהליך Txn1 מחזיק נעילה ברשומה A וממתין לנעילה ברשומה B, ותהליך Txn2 מחזיק נעילה ברשומה B וממתין לנעילה ברשומה A. כדי לפתור את הבעיה, אחת מהעסקאות צריכה להתבטל, כך שהנעילה שלה תוסר והעסקה השנייה תוכל להתבצע.
Spanner משתמש באלגוריתם הסטנדרטי wound-wait לזיהוי מצב של חסימה הדדית. מתחת לפני השטח, מערכת Spanner עוקבת אחרי הגיל של כל טרנזקציה שמבקשת נעילות מתנגשות. הוא מאפשר לעסקאות ישנות יותר לבטל עסקאות חדשות יותר. עסקה ישנה יותר היא עסקה שהפעולה הכי מוקדמת שלה (קריאה, שאילתה או ביצוע) התרחשה מוקדם יותר.
על ידי מתן עדיפות לעסקאות ישנות יותר, Spanner מבטיח שכל עסקה תקבל בסופו של דבר נעילות אחרי שהיא תהיה ישנה מספיק כדי לקבל עדיפות גבוהה יותר. לדוגמה, עסקה ישנה יותר שזקוקה לנעילה משותפת של כתיבה יכולה לבטל עסקה חדשה יותר שמחזיקה בנעילה משותפת של קריאה.
הרצה מבוזרת
Spanner יכול לבצע טרנזקציות על נתונים שמאוחסנים בכמה שרתים, אבל היכולת הזו כרוכה בפגיעה בביצועים בהשוואה לטרנזקציות בשרת יחיד.
אילו סוגי עסקאות עשויים להיות מופצים? Spanner יכול לחלק את האחריות לשורות במסד הנתונים בין שרתים רבים. בדרך כלל, שורה ושורות הטבלה המקבילות שלה שמשולבות זו בזו מוגשות על ידי אותו שרת, וכך גם שתי שורות באותה טבלה עם מפתחות סמוכים. Spanner יכול לבצע טרנזקציות בשורות בשרתים שונים. עם זאת, ככלל, טרנזקציות שמשפיעות על הרבה שורות שמוצבות זו לצד זו הן מהירות וזולות יותר מאלה שמשפיעות על הרבה שורות שמפוזרות במסד הנתונים או בטבלה גדולה.
העסקאות הכי יעילות ב-Spanner כוללות רק את פעולות הקריאה והכתיבה שצריך להחיל באופן אטומי. העסקאות מהירות יותר כשכל פעולות הקריאה והכתיבה ניגשות לנתונים באותו חלק של מרחב המפתחות.
עסקאות לקריאה בלבד
בנוסף לנעילת עסקאות עם הרשאת קריאה וכתיבה, Spanner מציע עסקאות עם הרשאת קריאה בלבד.
משתמשים בעסקת קריאה בלבד כשצריך לבצע יותר מקריאה אחת באותה חותמת זמן. אם אפשר לבצע את הקריאה באמצעות אחת משיטות הקריאה היחידה של Spanner, כדאי להשתמש בה במקום זאת. הביצועים של שימוש בקריאת קריאה יחידה כזו צריכים להיות דומים לביצועים של קריאה יחידה שמתבצעת בעסקת קריאה בלבד.
אם אתם קוראים כמות גדולה של נתונים, כדאי להשתמש במחיצות כדי לקרוא את הנתונים במקביל.
מכיוון שעסקאות לקריאה בלבד לא כותבות, הן לא מחזיקות נעילות ולא חוסמות עסקאות אחרות. עסקאות לקריאה בלבד מתבססות על קידומת עקבית של היסטוריית אישור העסקאות, כך שהאפליקציה תמיד מקבלת נתונים עקביים.
ממשק
Spanner מספק ממשק להרצת קבוצת פעולות בהקשר של טרנזקציה לקריאה בלבד, עם ניסיונות חוזרים לביטול טרנזקציות.
דוגמה
בדוגמה הבאה אפשר לראות איך משתמשים בעסקת קריאה בלבד כדי לקבל נתונים עקביים לשתי קריאות עם אותה חותמת זמן:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
סמנטיקה
בקטע הזה מתוארת הסמנטיקה של טרנזקציות לקריאה בלבד.
תמונת מצב של עסקאות לקריאה בלבד
כשעסקה לקריאה בלבד מבוצעת ב-Spanner, כל הקריאות שלה מתבצעות בנקודה לוגית אחת בזמן. המשמעות היא שגם העסקה לקריאה בלבד וגם כל קורא או כותב אחרים שפועלים במקביל רואים תמונה עקבית של מסד הנתונים באותו רגע ספציפי.
הטרנזקציות האלה של תמונת מצב לקריאה בלבד מציעות גישה פשוטה יותר לקריאות עקביות בהשוואה לטרנזקציות של קריאה וכתיבה עם נעילה. אלו הסיבות לכך:
- ללא נעילות: טרנזקציות לקריאה בלבד לא מקבלות נעילות. במקום זאת, הם פועלים על ידי בחירת חותמת זמן של Spanner והפעלת כל פעולות הקריאה מול הגרסה ההיסטורית של הנתונים. הם לא משתמשים בנעילות, ולכן הם לא יחסמו עסקאות קריאה-כתיבה בו-זמניות.
- ללא ביטולים: העסקאות האלה אף פעם לא מתבטלות. יכול להיות שהן ייכשלו אם חותמת הזמן של הקריאה שנבחרה תעבור איסוף אשפה, אבל מדיניות איסוף האשפה שמוגדרת כברירת מחדל ב-Spanner בדרך כלל נדיבה מספיק כך שרוב האפליקציות לא ייתקלו בבעיה הזו.
- אין ביצועים או ביטולים: עסקאות לקריאה בלבד לא דורשות קריאות ל-
sessions.commitאו ל-sessions.rollback, ולמעשה נמנעות מלעשות זאת.
כדי לבצע טרנזקציה של תמונת מצב, הלקוח מגדיר חותמת זמן של גבול, שמורה ל-Spanner איך לבחור חותמת זמן של קריאה. סוגי הגבולות של חותמות הזמן כוללים את האפשרויות הבאות:
- קריאות חזקות: קריאות כאלה מבטיחות שתראו את ההשפעות של כל העסקאות שבוצעו לפני שהקריאה התחילה. כל השורות בקריאה אחת עקביות. עם זאת, קריאות חזקות לא ניתנות לחזרה, למרות שהן מחזירות חותמת זמן, וקריאה חוזרת באותה חותמת זמן אפשרית. יכול להיות ששתי טרנזקציות עוקבות של קריאה בלבד יניבו תוצאות שונות בגלל פעולות כתיבה מקבילות. שאילתות על סנכרון שינויים בזרמי נתונים חייבות להשתמש בגבול הזה. פרטים נוספים זמינים במאמר בנושא TransactionOptions.ReadOnly.strong.
- רמת עדכניות מדויקת: האפשרות הזו מבצעת קריאות בחותמת זמן שאתם מציינים, או כחותמת זמן מוחלטת או כמשך זמן של חוסר עדכניות ביחס לזמן הנוכחי. היא מוודאת שאתם רואים קידומת עקבית של היסטוריית העסקאות הגלובלית עד לחותמת הזמן הזו, וחוסמת עסקאות סותרות שעשויות להתבצע עם חותמת זמן שקטנה מחותמת הזמן של הקריאה או שווה לה. הוא קצת יותר מהיר ממצבי חוסר עדכניות מוגבל, אבל יכול להיות שהוא יחזיר נתונים ישנים יותר. פרטים נוספים זמינים במאמרים בנושא TransactionOptions.ReadOnly.read_timestamp ו-TransactionOptions.ReadOnly.exact_staleness.
- Bounded staleness: Spanner בוחר את חותמת הזמן החדשה ביותר במסגרת מגבלת ה-staleness שהוגדרה על ידי המשתמש, ומאפשר הרצה בשכפול הזמין הקרוב ביותר בלי חסימה. כל השורות שמוחזרות עקביות. בדומה לקריאות חזקות, אי אפשר לחזור על קריאות עם חוסר עדכניות מוגבל, כי קריאות שונות עשויות להתבצע בחותמות זמן שונות גם אם המגבלה זהה. הקריאות האלה פועלות בשני שלבים (משא ומתן על חותמת זמן, ואז קריאה) ובדרך כלל הן קצת יותר איטיות מאשר קריאות עם רמת טריות מדויקת, אבל הן לרוב מחזירות תוצאות עדכניות יותר ויש סיכוי גבוה יותר שהן יתבצעו בעותק מקומי. המצב הזה זמין רק לעסקאות חד-פעמיות לקריאה בלבד, כי כדי לנהל משא ומתן על חותמת זמן צריך לדעת מראש אילו שורות ייקראו. פרטים נוספים זמינים במאמרים בנושא TransactionOptions.ReadOnly.max_staleness ו-TransactionOptions.ReadOnly.min_read_timestamp.
עסקאות DML עם חלוקה למחיצות
אתם יכולים להשתמש ב-partitioned DML כדי להריץ הצהרות UPDATE ו-DELETE בקנה מידה גדול בלי להיתקל במגבלות על טרנזקציות או לנעול טבלה שלמה. כדי להשיג את זה, Spanner מחלק את מרחב המפתחות ומריץ את הצהרות ה-DML בכל מחיצה בתוך טרנזקציית קריאה-כתיבה נפרדת.
כדי להשתמש ב-DML לא מחולק, צריך להריץ הצהרות בתוך טרנזקציות לקריאה ולכתיבה שיוצרים באופן מפורש בקוד. פרטים נוספים מופיעים במאמר בנושא שימוש ב-DML.
ממשק
Spanner מספק את הממשק TransactionOptions.partitionedDml להרצת פקודת DML מחולקת אחת.
דוגמאות
בדוגמה הבאה של קוד מתבצע עדכון של העמודה MarketingBudget בטבלה Albums.
C++
משתמשים בפונקציה ExecutePartitionedDml() כדי להפעיל פקודת DML עם חלוקה למחיצות.
C#
משתמשים ב-ExecutePartitionedUpdateAsync() method כדי להריץ פקודת DML עם חלוקה למחיצות.
Go
משתמשים ב-PartitionedUpdate() method כדי להריץ פקודת DML עם חלוקה למחיצות.
Java
משתמשים ב-executePartitionedUpdate() method כדי להריץ פקודת DML עם חלוקה למחיצות.
Node.js
משתמשים ב-runPartitionedUpdate() method כדי להריץ פקודת DML עם חלוקה למחיצות.
PHP
משתמשים ב-executePartitionedUpdate() method כדי להריץ פקודת DML עם חלוקה למחיצות.
Python
משתמשים ב-execute_partitioned_dml() method כדי להריץ פקודת DML עם חלוקה למחיצות.
Ruby
משתמשים ב-execute_partitioned_update() method כדי להריץ פקודת DML עם חלוקה למחיצות.
בדוגמה הבאה של הקוד מוצגת מחיקה של שורות מהטבלה Singers, על סמך העמודה SingerId.
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
סמנטיקה
בקטע הזה מתוארת הסמנטיקה של DML עם חלוקה למחיצות.
הסבר על ביצוע של DML עם חלוקה למחיצות
אפשר להריץ רק פקודת DML מחולקת אחת בכל פעם, בין אם משתמשים בשיטה של ספריית לקוח או ב-Google Cloud CLI.
עסקאות עם חלוקה למחיצות לא תומכות בפעולות commit או rollback. מערכת Spanner מריצה את פקודת ה-DML ומחילה אותה באופן מיידי. אם מבטלים את הפעולה או שהיא נכשלת, Spanner מבטל את כל המחיצות הפעילות ולא מתחיל את אלה שנותרו. עם זאת, מחיצות שכבר בוצעו לא יבוטלו ב-Spanner.
אסטרטגיה להשגת נעילה של DML עם חלוקה למחיצות
כדי לצמצם את התחרות על נעילה, פקודות DML מחולקות למחיצות ומקבלות נעילות קריאה רק בשורות שתואמות לסעיף WHERE. גם עסקאות קטנות ועצמאיות שמשמשות לכל מחיצה מחזיקות נעילות לפרק זמן קצר יותר.
חותמות זמן ישנות של קריאה ו-garbage collection של גרסאות
מערכת Spanner מבצעת garbage collection של גרסאות כדי לאסוף נתונים שנמחקו או נכתבו מחדש, ולפנות מקום באחסון. כברירת מחדל, נתונים מלפני יותר משעה
נמחקים. Spanner לא יכול לבצע קריאות בחותמות זמן ישנות יותר מVERSION_RETENTION_PERIOD שהוגדר, שמוגדר כברירת מחדל לשעה אחת אבל אפשר להגדיר אותו עד לשבוע אחד. אם הקריאות ישנות מדי במהלך הביצוע, הן נכשלות ומוחזרת השגיאה FAILED_PRECONDITION.
שאילתות לגבי סנכרון שינויים בזרמי נתונים
מקור נתונים לשינויים הוא אובייקט סכימה שאפשר להגדיר כדי לעקוב אחרי שינויים בנתונים במסד נתונים שלם, בטבלאות ספציפיות או בקבוצה מוגדרת של עמודות במסד נתונים.
כשיוצרים זרם שינויים, Spanner מגדיר פונקציה תואמת של SQL עם ערך טבלה (TVF). אפשר להשתמש בפונקציה הזו כדי לשלוח שאילתות לרשומות השינויים בזרם השינויים המשויך באמצעות השיטה sessions.executeStreamingSql. השם של TVF נוצר מהשם של זרם השינויים ותמיד מתחיל ב-READ_.
כל השאילתות בפונקציות TVF של שינוי הנתונים חייבות להתבצע באמצעות
sessions.executeStreamingSql API בעסקה לקריאה בלבד לשימוש חד-פעמי
עם timestamp_bound חזק לקריאה בלבד. הפונקציה TVF של שינוי הזרם מאפשרת לציין את start_timestamp ואת end_timestamp לטווח הזמן. אפשר לגשת לכל רשומות השינויים בתוך תקופת השמירה באמצעות timestamp_bound חזק לקריאה בלבד. כל שאר TransactionOptions לא תקפים לשאילתות של שינוי נתונים.
בנוסף, אם הערך של TransactionOptions.read_only.return_read_timestamp מוגדר כ-true, ההודעה Transaction שמתארת את העסקה מחזירה ערך מיוחד של 2^63 - 2 במקום חותמת זמן קריאה תקינה. צריך להתעלם מהערך המיוחד הזה ולא להשתמש בו בשום שאילתה בהמשך.
מידע נוסף זמין במאמר בנושא תהליך העבודה של שאילתות בשינויים בסטרימינג.
עסקאות במצב המתנה
טרנזקציה נחשבת ללא פעילות אם אין בה קריאות או שאילתות SQL ממתינות, והיא לא התחילה אף אחת ב-10 השניות האחרונות. מערכת Spanner יכולה לבטל טרנזקציות לא פעילות כדי למנוע מהן להחזיק נעילות ללא הגבלת זמן. אם עסקה במצב המתנה מבוטלת, הפעולה commit נכשלת ומוחזרת שגיאת ABORTED.
ביצוע שאילתה קטנה באופן תקופתי, כמו SELECT 1, בתוך העסקה יכול למנוע את מצב חוסר הפעילות.