בדף הזה מוסבר איך להשתמש בסעיף FOR UPDATE בבידוד ניתן לסריאליזציה.
מנגנון הנעילה של סעיף FOR UPDATE שונה עבור קריאה חוזרת ובידוד ניתן לסידור. כשמשתמשים בבידוד שניתן לסדר אותו בסדרות, כשמשתמשים בשאילתת SELECT כדי לסרוק טבלה, הוספה של פסקה FOR UPDATE מאפשרת נעילות בלעדיות בצומת של רמת הגרנולריות של השורה והעמודה, שנקראת גם רמת התא. הנעילה נשארת במקום למשך חיי העסקה לקריאה ולכתיבה. במהלך הזמן הזה, הסעיף FOR UPDATE מונע מעסקאות אחרות לשנות את התאים הנעולים עד שהעסקה הנוכחית תושלם.
מידע נוסף על השימוש בסעיף FOR UPDATE זמין במדריכי העיון של GoogleSQL ושל PostgreSQL בנושא FOR UPDATE.
למה כדאי להשתמש בסעיף FOR UPDATE
במסדי נתונים עם רמות בידוד פחות מחמירות, יכול להיות שיהיה צורך בסעיף FOR UPDATE כדי לוודא שעסקה מקבילה לא מעדכנת נתונים בין קריאת הנתונים לבין ביצוע העסקה. מכיוון ש-Spanner אוכף סריאליזציה כברירת מחדל, מובטח שהעסקה תתבצע בהצלחה רק אם הנתונים שאליהם ניגשים במסגרת העסקה לא יהיו ישנים בזמן הביצוע. לכן, הסעיף FOR UPDATE לא נחוץ כדי להבטיח את נכונות העסקה ב-Spanner.
עם זאת, בתרחישי שימוש עם מחלוקת גבוהה על גישת כתיבה, למשל כשכמה עסקאות קוראות וכותבות בו-זמנית לאותם נתונים, העסקאות הסימולטניות עלולות לגרום לעלייה בביטולים. הסיבה לכך היא שכמה עסקאות בו-זמניות מקבלות נעילות משותפות, ואז מנסות לשדרג לנעילות בלעדיות, והעסקאות גורמות לקיפאון. הקיפאון חוסם את העסקאות באופן קבוע כי כל אחת מהן ממתינה שהשנייה תשחרר את המשאב שהיא צריכה. כדי להתקדם, Spanner מבטל את כל העסקאות מלבד אחת כדי לפתור את הקיפאון. מידע נוסף מופיע במאמר בנושא נעילה.
עסקה שמשתמשת בסעיף FOR UPDATE מקבלת את הנעילה הבלעדית באופן יזום וממשיכה להתבצע, בזמן שעסקאות אחרות מחכות לתור שלהן לנעילה. יכול להיות ש-Spanner עדיין יגביל את קצב העברת הנתונים כי אפשר לבצע את העסקאות המתנגשות רק אחת בכל פעם, אבל מכיוון ש-Spanner מתקדם רק בעסקה אחת, הוא חוסך זמן שיוקדש אחרת לביטול עסקאות ולניסיון חוזר לבצע אותן.
לכן, אם חשוב לכם להפחית את מספר הטרנזקציות שבוטלו בתרחיש של בקשת כתיבה בו-זמנית, אתם יכולים להשתמש בסעיף FOR UPDATE כדי להפחית את המספר הכולל של הביטולים ולשפר את יעילות הביצוע של עומס העבודה.
השוואה לרמז LOCK_SCANNED_RANGES
הסעיף FOR UPDATE ממלא תפקיד דומה לזה של הרמז LOCK_SCANNED_RANGES=exclusive.
יש שני הבדלים עיקריים:
אם משתמשים ברמז
LOCK_SCANNED_RANGES, העסקה מקבלת נעילות בלעדיות בטווחים שנסרקו למשך כל ההצהרה. אי אפשר לקבל נעילות בלעדיות בשאילתת משנה. שימוש ברמז הנעילה עלול לגרום לרכישת נעילות רבות יותר מהנדרש ולתרום למאבק על נעילות בעומס העבודה. בדוגמה הבאה אפשר לראות איך משתמשים ברמז לנעילה:@{lock_scanned_ranges=exclusive} SELECT s.SingerId, s.FullName FROM Singers AS s JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000) AS a ON a.SingerId = s.SingerId;לעומת זאת, אפשר להשתמש בסעיף
FOR UPDATEבשאילתת משנה, כמו בדוגמה הבאה:SELECT s.SingerId, s.FullName FROM Singers AS s JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000) FOR UPDATE AS a ON a.SingerId = s.SingerId;אפשר להשתמש ברמז
LOCK_SCANNED_RANGESבהצהרות DML, אבל אפשר להשתמש רק בסעיףFOR UPDATEבהצהרותSELECT.
סמנטיקה של נעילה
כדי לצמצם את מספר בקשות הכתיבה בו-זמנית ואת העלות של טרנזקציות שבוטלו כתוצאה מקיפאון, Spanner נועל נתונים ברמת התא אם הדבר אפשרי. הרמה הכי מפורטת של נתונים בטבלה היא רמת התא – נקודת נתונים בצומת של שורה ועמודה. כשמשתמשים בסעיף FOR UPDATE, Spanner נועל תאים ספציפיים שנסרקים על ידי השאילתה SELECT.
בדוגמה הבאה, התא MarketingBudget בשורה SingerId = 1 ובשורה AlbumId = 1 נעול באופן בלעדי בטבלה Albums, כך שעסקאות מקבילות לא יכולות לשנות את התא הזה עד שהעסקה הזו תאושר או תבוטל. עם זאת, טרנזקציות מקבילות עדיין יכולות לעדכן את התא AlbumTitle בשורה הזו.
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId = 1
FOR UPDATE;
יכול להיות שטרנזקציות מקבילות ייחסמו בקריאת נתונים נעולים
אם עסקה אחת מקבלת נעילות בלעדיות בטווח שנסרק, יכול להיות שעסקאות מקבילות יחסמו את קריאת הנתונים האלה. ב-Spanner נאכפת סריאליזציה, כך שאפשר לקרוא נתונים רק אם מובטח שהם לא ישתנו על ידי טרנזקציה אחרת במהלך משך החיים של הטרנזקציה. יכול להיות שעסקאות מקבילות שמנסות לקרוא נתונים שכבר נעולים יצטרכו להמתין עד שהעסקה שנועלת את הנתונים תאושר, תבוטל או שתגיע לזמן קצוב לתפוגה.
בדוגמה הבאה, Transaction 1 נועל את התאים MarketingBudget עבור 1 <= AlbumId < 5.
-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 5
FOR UPDATE;
הפעולה Transaction 2, שמנסה לקרוא את MarketingBudget עבור AlbumId = 1, חסומה עד ש-Transaction 1 יבצע commit או יבוטל.
-- Transaction 2
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId = 1;
-- Blocked by Transaction 1
באופן דומה, טרנזקציה שמנסה לנעול טווח סרוק עם FOR UPDATE נחסמת על ידי טרנזקציה מקבילה שנועלת טווח סרוק חופף.
גם Transaction 3 בדוגמה הבאה חסום, כי Transaction 1 נעל את התאים MarketingBudget עבור 3 <= AlbumId < 5, שהוא טווח הסריקה החופף ל-Transaction 3.
-- Transaction 3
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 3 and AlbumId < 10
FOR UPDATE;
-- Blocked by Transaction 1
קריאת אינדקס
יכול להיות שקריאה מקבילה לא תיחסם אם השאילתה שנעלה את טווח הסריקה נועלת את השורות בטבלת הבסיס, אבל העסקה המקבילה קוראת מאינדקס.
הפקודה הבאה Transaction 1 נועלת את התאים SingerId ו-SingerInfo למשך SingerId = 1.
-- Transaction 1
SELECT SingerId, SingerInfo
FROM Singers
WHERE SingerId = 1
FOR UPDATE;
הגישה לקריאה בלבד Transaction 2 לא נחסמת על ידי הנעילות שהושגו ב-Transaction 1, כי היא שולחת שאילתה לטבלת אינדקס.
-- Transaction 2
SELECT SingerId FROM Singers;
עסקאות מקבילות לא חוסמות פעולות DML על נתונים שכבר ננעלו
כשטרנזקציה אחת מקבלת נעילות בטווח של תאים עם רמז נעילה בלעדי, טרנזקציות מקבילות שמנסות לבצע כתיבה בלי לקרוא קודם את הנתונים בתאים הנעולים יכולות להמשיך. העסקה נחסמת בביצוע עד שהעסקה שמחזיקה את הנעילות מתבצעת או מבוטלת.
הפקודה הבאה Transaction 1 נועלת את התאים MarketingBudget של 1 <= AlbumId < 5.
-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 5
FOR UPDATE;
אם Transaction 2 מנסה לעדכן את הטבלה Albums, הפעולה נחסמת עד ש-Transaction 1 מבצעת commit או rollback.
-- Transaction 2
UPDATE Albums
SET MarketingBudget = 200000
WHERE SingerId = 1 and AlbumId = 1;
> Query OK, 1 rows affected
COMMIT;
-- Blocked by Transaction 1
כשנועלים טווח סרוק, השורות והפערים הקיימים ננעלים
אם עסקה אחת קיבלה נעילות בלעדיות על טווח שנסרק, עסקאות מקבילות לא יכולות להוסיף נתונים בפערים בתוך הטווח הזה.
הפקודה הבאה Transaction 1 נועלת את התאים MarketingBudget של 1 <= AlbumId < 10.
-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 10
FOR UPDATE;
אם Transaction 2 מנסה להוסיף שורה בשביל AlbumId = 9 שלא קיימת עדיין, הפעולה נחסמת עד ש-Transaction 1 מבצעת commit או rollback.
-- Transaction 2
INSERT INTO Albums (SingerId, AlbumId, AlbumTitle, MarketingBudget)
VALUES (1, 9, "Hello hello!", 10000);
> Query OK, 1 rows affected
COMMIT;
-- Blocked by Transaction 1
הערות לגבי נעילת רכישה
הסמנטיקה של הנעילה שמתוארת כאן מספקת הנחיות כלליות, אבל לא מבטיחה איך בדיוק יתבצעו הנעילות כש-Spanner מריץ טרנזקציה שמשתמשת בסעיף FOR UPDATE. יכול להיות שמנגנוני האופטימיזציה של השאילתות ב-Spanner ישפיעו גם על הנעילות שמתבצעות. הסעיף מונע מעסקאות אחרות לשנות את התאים הנעולים עד שהעסקה הנוכחית תסתיים.
תחביר של שאילתות
בקטע הזה מוסבר על תחביר השאילתות כשמשתמשים בסעיף FOR UPDATE.
השימוש הנפוץ ביותר הוא במשפט SELECT ברמה העליונה. לדוגמה:
SELECT SingerId, SingerInfo
FROM Singers WHERE SingerID = 5
FOR UPDATE;
בדוגמה הזו מוצג שימוש בסעיף FOR UPDATE בהצהרת SELECT כדי לנעול באופן בלעדי את התאים SingerId ו-SingerInfo ב-WHERE SingerID = 5.
שימוש בהצהרות WITH
הסעיף FOR UPDATE לא מקבל נעילות עבור ההצהרה WITH כשמציינים FOR UPDATE בשאילתה ברמה החיצונית של ההצהרה WITH.
בשאילתה הבאה, לא נרכשו נעילות על הטבלה Singers, כי הכוונה לנעול לא מועברת לשאילתת הביטויים הנפוצים של הטבלה (CTE).
WITH s AS (SELECT SingerId, SingerInfo FROM Singers WHERE SingerID > 5)
SELECT * FROM s
FOR UPDATE;
אם סעיף FOR UPDATE מצוין בשאילתת ה-CTE, הטווח שנסרק של שאילתת ה-CTE מקבל את הנעילות.
בדוגמה הבאה, התאים SingerId ו-SingerInfo בשורות שבהן SingerId > 5 נעולים.
WITH s AS
(SELECT SingerId, SingerInfo FROM Singers WHERE SingerId > 5 FOR UPDATE)
SELECT * FROM s;
שימוש בשאילתות משנה
אפשר להשתמש בסעיף FOR UPDATE בשאילתה ברמה החיצונית שיש לה שאילתה אחת או יותר. הנעילות מתבצעות על ידי השאילתה ברמה העליונה ובתוך שאילתות משנה, למעט שאילתות משנה של ביטויים.
השאילתה הבאה נועלת את התאים SingerId ו-SingerInfo בשורות שבהן
SingerId > 5.
(SELECT SingerId, SingerInfo FROM Singers WHERE SingerId > 5) AS t
FOR UPDATE;
השאילתה הבאה לא נועלת תאים בטבלה Albums כי היא נמצאת בתוך שאילתת משנה של ביטוי. התאים SingerId ו-SingerInfo בשורות שמוחזרות על ידי שאילתת המשנה של הביטוי נעולים.
SELECT SingerId, SingerInfo
FROM Singers
WHERE SingerId = (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000)
FOR UPDATE;
שימוש בשאילתות לתצוגות
אפשר להשתמש בסעיף FOR UPDATE כדי לשלוח שאילתה לתצוגה, כמו בדוגמה הבאה:
CREATE VIEW SingerBio AS SELECT SingerId, FullName, SingerInfo FROM Singers;
SELECT * FROM SingerBio WHERE SingerId = 5 FOR UPDATE;
אי אפשר להשתמש בפסקה FOR UPDATE כשמגדירים תצוגה.
תרחישי שימוש שלא נתמכים
אין תמיכה בתרחישי השימוש הבאים של FOR UPDATE:
- כמנגנון הדדי למניעת הרצה של קוד מחוץ ל-Spanner: אל תשתמשו בנעילה ב-Spanner כדי להבטיח גישה בלעדית למשאב מחוץ ל-Spanner. יכול להיות ש-Spanner יבטל עסקאות. לדוגמה, אם מתבצע ניסיון חוזר לעסקה, באופן מפורש על ידי קוד האפליקציה או באופן מרומז על ידי קוד הלקוח, כמו מנהל ההתקנים של Spanner JDBC, מובטח שהנעילות יוחזקו רק במהלך הניסיון שאושר.
- בשילוב עם הרמז
LOCK_SCANNED_RANGES: אי אפשר להשתמש גם בסעיףFOR UPDATEוגם ברמזLOCK_SCANNED_RANGESבאותה שאילתה, אחרת Spanner יחזיר שגיאה. - בשאילתות של חיפוש טקסט מלא: אי אפשר להשתמש בפסקה
FOR UPDATEבשאילתות שמשתמשות באינדקסים של חיפוש טקסט מלא. - בעסקאות לקריאה בלבד: פסוקית
FOR UPDATEתקפה רק בשאילתות שמופעלות בעסקאות לקריאה וכתיבה. - בתוך הצהרות DDL: אי אפשר להשתמש בסעיף
FOR UPDATEבשאילתות בתוך הצהרות DDL, שמאוחסנות לביצוע מאוחר יותר. לדוגמה, אי אפשר להשתמש בסעיףFOR UPDATEכשמגדירים תצוגה. אם נדרשת נעילה, אפשר לציין את סעיףFOR UPDATEכשמבצעים שאילתה לתצוגה.
המאמרים הבאים
- GoogleSQL
PostgreSQL
FOR UPDATE - איך משתמשים ב-SELECT FOR UPDATE בבידוד קריאה חוזרת
- מידע נוסף על ההצעה
LOCK_SCANNED_RANGES - מידע על נעילה ב-Spanner
- מידע על סריאליזציה ב-Spanner