נתונים סטטיסטיים של נעילה

במאמר הזה מוסבר איך להשתמש בטבלאות המובנות של נתוני הנעילה ב-Spanner כדי לזהות את מפתחות השורות ואת עמודות הטבלה שגרמו להתנגשויות של נעילת טרנזקציות במסד הנתונים במהלך פרק זמן מסוים. אפשר לאחזר נתונים סטטיסטיים מהטבלאות האלה באמצעות הצהרות SQL.SPANNER_SYS.LOCK_STATS*

אם הטרנזקציות שלכם משתמשות בבקרת בו-זמניות אופטימית, הנתונים הסטטיסטיים של הנעילות לא זמינים כי הנעילות לא מתבצעות במהלך ההרצה של הטרנזקציה.

סטטיסטיקות של נעילת הגישה

‫Spanner מספק את נתוני הנעילה בסכימה SPANNER_SYS. אפשר לגשת לנתוני SPANNER_SYS בדרכים הבאות:

  • דף Spanner Studio של מסד נתונים במסוף Google Cloud

  • הפקודה gcloud spanner databases execute-sql.

  • מרכז הבקרה Lock insights.

  • השיטה executeSql או השיטה executeStreamingSql.

    ה-methods הבאות לקריאה יחידה ש-Spanner מספק לא תומכות ב-SPANNER_SYS:

    • ביצוע קריאה חזקה משורה אחת או מכמה שורות בטבלה.
    • ביצוע קריאה לא עדכנית משורה אחת או מכמה שורות בטבלה.
    • קריאה משורה אחת או מכמה שורות באינדקס משני.

נעילת נתונים סטטיסטיים לפי מפתח שורה

בטבלאות הבאות אפשר לעקוב אחרי מפתח השורה עם זמן ההמתנה הכי ארוך:

  • SPANNER_SYS.LOCK_STATS_TOP_MINUTE: מפתחות שורה עם זמני ההמתנה הגבוהים ביותר לנעילה במרווחי זמן של דקה אחת.

  • SPANNER_SYS.LOCK_STATS_TOP_10MINUTE: מפתחות שורה עם זמני המתנה לנעילה הכי ארוכים במרווחי זמן של 10 דקות.

  • SPANNER_SYS.LOCK_STATS_TOP_HOUR: מפתחות שורות עם זמני ההמתנה הגבוהים ביותר לנעילה במרווחי זמן של שעה

הטבלאות האלה כוללות את המאפיינים הבאים:

  • כל טבלה מכילה נתונים של מרווחי זמן לא חופפים באורך שצוין בשם הטבלה.

  • המרווחים מבוססים על שעות. מרווחי זמן של דקה אחת מסתיימים בתחילת הדקה, מרווחי זמן של 10 דקות מסתיימים כל 10 דקות החל מתחילת השעה, ומרווחי זמן של שעה אחת מסתיימים בתחילת השעה. אחרי כל מרווח זמן, מערכת Spanner אוספת נתונים מכל השרתים, ואז הנתונים האלה זמינים בטבלאות SPANNER_SYS זמן קצר לאחר מכן.

    לדוגמה, בשעה 11:59:30, המרווחים האחרונים שזמינים לשאילתות SQL הם:

    • דקה אחת: 11:58:00 עד 11:58:59
    • 10 דקות: 11:40:00–11:49:59
    • שעה אחת: 10:00:00–10:59:59 AM
  • מערכת Spanner מקבצת את הנתונים הסטטיסטיים לפי טווח מפתחות שורות התחלתי.

  • כל שורה מכילה נתונים סטטיסטיים לגבי זמן ההמתנה הכולל לנעילה של טווח מסוים של מפתח שורה התחלתי, ש-Spanner אוסף לגביו נתונים סטטיסטיים במהלך המרווח שצוין.

  • אם Spanner לא מצליח לאחסן מידע על כל טווח של מפתחות שורות להמתנה לנעילה במהלך המרווח, המערכת מתעדפת את טווח מפתחות השורות עם זמן ההמתנה לנעילה הגבוה ביותר במהלך המרווח שצוין.

  • כל העמודות בטבלאות הן nullable.

סכמת טבלאות

שם עמודה סוג תיאור
INTERVAL_END TIMESTAMP סוף מרווח הזמן שבו התרחשו ההתנגשויות של הנעילה שנכללו.
ROW_RANGE_START_KEY BYTES(MAX) מפתח השורה שבה התרחשה התנגשות הנעילה. אם הקונפליקט כולל טווח של שורות, הערך הזה מייצג את מפתח ההתחלה של הטווח הזה. סימן פלוס, +, מציין טווח. מידע נוסף זמין במאמר מהו מפתח התחלה של טווח שורות.
LOCK_WAIT_SECONDS FLOAT64 משך ההמתנה המצטבר לנעילה של קונפליקטים של נעילה שנרשמו עבור כל העמודות בטווח מפתחות השורה, בשניות.
SAMPLE_LOCK_REQUESTS ARRAY<STRUCT<
  column STRING,
  lock_mode STRING,
   transaction_tag STRING>>
כל רשומה במערך הזה תואמת לבקשת נעילה לדוגמה שתרמה להתנגשות הנעילה, על ידי המתנה לנעילה או חסימת טרנזקציות אחרות מלקבל את הנעילה, במפתח השורה (הטווח) הנתון. מספר הדגימות המקסימלי במערך הזה הוא 20.

כל דוגמה מכילה את שלושת השדות הבאים:
  • lock_mode: מצב הנעילה שהתבקש. מידע נוסף זמין במאמר בנושא מצבי נעילה .
  • column: העמודה שבה התרחשה הכפילות של הנעילה. הפורמט של הערך הזה הוא tablename.columnname.
  • transaction_tag: התג של העסקה שבה הונפקה הבקשה. מידע נוסף על השימוש בתגים זמין במאמר פתרון בעיות באמצעות תגי טרנזקציות.
כל הבקשות לנעילה שגרמו להתנגשויות בנעילה נדגמות באופן אחיד ואקראי, כך שיכול להיות שרק חצי מההתנגשות (המחזיק או הממתין) יתועד במערך הזה.
SAMPLE_LOCK_REQUESTS_JSON_STRING STRING ייצוג מחרוזת תואמת ל-JSON של העמודה SAMPLE_LOCK_REQUESTS. מחרוזת ה-JSON היא אובייקט JSON עם אותו מבנה כמו STRUCT שמוגדר בעמודה SAMPLE_LOCK_REQUESTS.

העמודה הזו נתמכת במסדי נתונים של GoogleSQL-dialect ו-PostgreSQL-dialect.

מצבי נעילה

פעולות ב-Spanner מקבלות נעילות כשהן חלק מעסקת קריאה-כתיבה. עסקאות עם הרשאת קריאה בלבד לא מקבלות נעילות. ב-Spanner נעשה שימוש במצבי נעילה שונים כדי למקסם את מספר העסקאות שיש להן גישה לתא נתונים מסוים בזמן נתון. למנעולים שונים יש מאפיינים שונים. לדוגמה, יש נעילות שאפשר לשתף בין כמה עסקאות, ויש כאלה שלא.

התנגשות נעילה יכולה להתרחש כשמנסים להשיג אחד ממצבי הנעילה הבאים בטרנזקציה.

  • ReaderShared נעילה – נעילה שמאפשרת לגישות קריאה אחרות לגשת לנתונים עד שהעסקה מוכנה להתחייבות. הנעילה המשותפת הזו מתבצעת כשעסקת קריאה-כתיבה קוראת נתונים.

  • WriterShared נעילה – הנעילה הזו מתבצעת כשטרנזקציית קריאה-כתיבה מנסה לבצע כתיבה.

  • Exclusive נעילה – נעילה בלעדית מתבצעת כשטרנזקציה של קריאה וכתיבה, שכבר קיבלה נעילה מסוג ReaderShared, מנסה לכתוב נתונים אחרי השלמת הקריאה. נעילה בלעדית היא שדרוג מנעילת ReaderShared. נעילה בלעדית היא מקרה מיוחד של טרנזקציה שבה מוחזקות בו-זמנית גם נעילת ReaderShared וגם נעילת WriterShared. אף טרנזקציה אחרת לא יכולה לקבל נעילה באותו תא.

  • WriterSharedTimestamp נעילה – סוג מיוחד של WriterShared נעילה שמתקבלת כשמוסיפים שורות חדשות לטבלה שיש בה חותמת זמן של ביצוע פעולת commit כחלק מהמפתח הראשי. סוג הנעילה הזה מונע ממשתתפים בעסקה ליצור בדיוק את אותה שורה, ולכן מונע מהם להתנגש זה עם זה. מערכת Spanner מעדכנת את המפתח של השורה שנוספה כך שיתאים לחותמת הזמן של האישור של הטרנזקציה שביצעה את ההוספה.

מידע נוסף על סוגי עסקאות ועל סוגי הנעילות שזמינים מופיע במאמר עסקאות.

בעיות שקשורות למצב נעילה

בטבלה הבאה מוצגים העימותים האפשריים בין מצבי נעילה שונים.

מצבי נעילה ReaderShared WriterShared Exclusive WriterSharedTimestamp
ReaderShared לא כן כן כן
WriterShared כן לא כן לא רלוונטי
Exclusive כן כן כן לא רלוונטי
WriterSharedTimestamp כן לא רלוונטי לא רלוונטי כן

נעילות WriterSharedTimestamp משמשות רק כשמוסיפים שורות חדשות עם חותמת זמן כחלק מהמפתח הראשי. הנעילות WriterShared ו-Exclusive משמשות לכתיבה לתאים קיימים או להוספת שורות חדשות בלי חותמות זמן. כתוצאה מכך, WriterSharedTimestamp לא יכול להתנגש עם סוגים אחרים של נעילות, והתרחישים האלה מוצגים כלא רלוונטי בטבלה הקודמת.

החריג היחיד הוא ReaderShared, שאפשר להחיל על שורות שלא קיימות, ולכן הוא עלול להתנגש עם WriterSharedTimestamp. לדוגמה, סריקה מלאה של טבלה נועלת את כל הטבלה, גם שורות שלא נוצרו, ולכן יכול להיות ש-ReaderShared יתנגש עם WriterSharedTimestamp.

מהו מפתח התחלה של טווח שורות?

העמודה ROW_RANGE_START_KEY מזהה את המפתח הראשי המורכב, או את המפתח הראשי ההתחלתי של טווח שורות, שיש לו התנגשויות נעילה. הסכימה הבאה משמשת להמחשת דוגמה.

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE TABLE Songs (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  TrackId      INT64 NOT NULL,
  SongName     STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE TABLE Users (
  UserId     INT64 NOT NULL,
  LastAccess TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
  ...
) PRIMARY KEY (UserId, LastAccess);

כפי שאפשר לראות בטבלה הבאה של מפתחות שורות וטווחים של מפתחות שורות, טווח מיוצג באמצעות סימן פלוס, '+', במפתח. במקרים כאלה, המפתח מייצג את מפתח ההתחלה של טווח מפתחות שבו התרחש עימות נעילה.

ROW_RANGE_START_KEY הסבר
זמרים(2) טבלת Singers במפתח SingerId=2
albums(2,1) טבלת אלבומים במפתח SingerId=2,AlbumId=1
songs(2,1,5) טבלת השירים במפתח SingerId=2,AlbumId=1,TrackId=5
שירים(2,1,5+) טווח המפתחות בטבלת השירים מתחיל ב-SingerId=2,‏ AlbumId=1,‏ TrackId=5
אלבומים(2 ומעלה) ‫Albums table key range starting at SingerId=2,AlbumId=1
users(3, 2020-11-01 12:34:56.426426+00:00) טבלת המשתמשים במפתח UserId=3, ‏ LastAccess=commit_timestamp

נתונים סטטיסטיים מצטברים

SPANNER_SYS כולל גם טבלאות לאחסון נתונים מצטברים של נתונים סטטיסטיים לגבי נעילות שנאספו על ידי Spanner בתקופה מסוימת:

  • SPANNER_SYS.LOCK_STATS_TOTAL_MINUTE: נתונים סטטיסטיים מצטברים של כל ההמתנות לנעילה במרווחי זמן של דקה אחת.

  • SPANNER_SYS.LOCK_STATS_TOTAL_10MINUTE: נתונים סטטיסטיים מצטברים של כל ההמתנות לנעילה במרווחי זמן של 10 דקות.

  • SPANNER_SYS.LOCK_STATS_TOTAL_HOUR: נתונים סטטיסטיים מצטברים לכל ההמתנות לנעילה במרווחי זמן של שעה.

לטבלאות של נתונים סטטיסטיים מצטברים יש את המאפיינים הבאים:

  • כל טבלה מכילה נתונים של מרווחי זמן לא חופפים באורך שצוין בשם הטבלה.

  • המרווחים מבוססים על שעות. מרווחי זמן של דקה אחת מסתיימים בתחילת הדקה, מרווחי זמן של 10 דקות מסתיימים כל 10 דקות החל מתחילת השעה, ומרווחי זמן של שעה אחת מסתיימים בתחילת השעה.

    לדוגמה, בשעה 11:59:30, המרווחים האחרונים שזמינים לשאילתות SQL לגבי נתוני נעילה מצטברים הם:

    • דקה אחת: 11:58:00 עד 11:58:59
    • 10 דקות: 11:40:00–11:49:59
    • שעה אחת: 10:00:00–10:59:59 AM
  • כל שורה מכילה נתונים סטטיסטיים של כל ההמתנות לנעילה במסד הנתונים במהלך המרווח שצוין, כשהם מצטברים יחד. לכל מרווח זמן יש רק שורה אחת.

  • הנתונים הסטטיסטיים שנאספים בטבלאות SPANNER_SYS.LOCK_STATS_TOTAL_* כוללים המתנה לנעילה שלא נאספה בטבלאות SPANNER_SYS.LOCK_STATS_TOP_* ב-Spanner.

  • חלק מהעמודות בטבלאות האלה מוצגות כמדדים ב-Cloud Monitoring. אלה המדדים שמוצגים:

    • זמן ההמתנה לנעילה

    מידע נוסף מופיע במאמר בנושא מדדים של Spanner.

סכמת טבלאות

שם עמודה סוג תיאור
INTERVAL_END TIMESTAMP סיום מרווח הזמן שבו התרחש העימות על הנעילה.
TOTAL_LOCK_WAIT_SECONDS FLOAT64 הזמן הכולל של ההמתנה לנעילה בגלל התנגשויות נעילה שנרשמו עבור כל מסד הנתונים, בשניות.

שאילתות לדוגמה

הדוגמה הבאה היא של הצהרת SQL שאפשר להשתמש בה כדי לאחזר נתונים סטטיסטיים של נעילה: אפשר להריץ את הצהרות ה-SQL האלה באמצעות ספריות הלקוח, gcloud spanner או Google Cloud המסוף.

הצגת נתונים סטטיסטיים לגבי הנעילה של המרווח הקודם של דקה אחת

השאילתה הבאה מחזירה את פרטי ההמתנה לנעילה של כל מפתח שורה עם התנגשות נעילה, כולל החלק היחסי מתוך סך התנגשויות הנעילה, במהלך מרווח הזמן האחרון של דקה אחת.

הפונקציה CAST() ממירה את השדה row_range_start_key [BYTES] למחרוזת.

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_minute t, spanner_sys.lock_stats_top_minute s
WHERE t.interval_end =
  (SELECT MAX(interval_end)
   FROM spanner_sys.lock_stats_total_minute)
AND s.interval_end = t.interval_end
ORDER BY s.lock_wait_seconds DESC;
פלט השאילתה
row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
שירים(2,1,1) 2.37 1.76 0.7426 LOCK_MODE: ReaderShared

עמודה: Singers.SingerInfo

LOCK_MODE: WriterShared

עמודה: Singers.SingerInfo
משתמשים(3, 2020-11-01 12:34:56.426426+00:00) 2.37 0.61 0.2573 LOCK_MODE: ReaderShared

עמודה: users._exists1

LOCK_MODE: WriterShared

עמודה: users._exists1

1 _exists הוא שדה פנימי שמשמש לבדיקה אם שורה מסוימת קיימת או לא.

שמירת נתונים

לפחות, Spanner שומר נתונים לכל טבלה למשך תקופות הזמן הבאות:

  • SPANNER_SYS.LOCK_STATS_TOP_MINUTE ו-SPANNER_SYS.LOCK_STATS_TOTAL_MINUTE: מרווחי זמן שמתייחסים ל-6 השעות האחרונות.

  • SPANNER_SYS.LOCK_STATS_TOP_10MINUTE ו-SPANNER_SYS.LOCK_STATS_TOTAL_10MINUTE: מרווחי זמן שכוללים את 4 הימים הקודמים.

  • SPANNER_SYS.LOCK_STATS_TOP_HOUR ו-SPANNER_SYS.LOCK_STATS_TOTAL_HOUR: מרווחי זמן שכוללים את 30 הימים האחרונים.

פתרון בעיות של התנגשויות נעילה במסד הנתונים באמצעות נתונים סטטיסטיים של נעילות

אתם יכולים להשתמש ב-SQL או בלוח הבקרה תובנות לגבי נעילות כדי לראות התנגשויות נעילה במסד הנתונים.

בנושאים הבאים מוסבר איך אפשר לחקור את העימותים האלה של נעילות באמצעות קוד SQL.

בחירת תקופת זמן לבדיקה

אתם בודקים את מדדי זמן האחזור של מסד הנתונים שלכם ב-Spanner ומגלים תקופה שבה האפליקציה חווה זמן אחזור גבוה ושימוש גבוה במעבד. לדוגמה, הבעיה התחילה ב-12 בנובמבר 2020 בסביבות השעה 22:50.

בודקים אם זמן האחזור של אישור העסקה גדל יחד עם זמן ההמתנה לנעילה במהלך התקופה שנבחרה

העסקאות מקבלות נעילות, ולכן אם עימותי נעילות גורמים לזמני המתנה ארוכים, אמורה להיות אפשרות לראות את העלייה בחביון של אישור העסקה יחד עם העלייה בזמן ההמתנה של הנעילה.

אחרי שבחרנו תקופת זמן להתחיל בה את הבדיקה, נצרף את נתוני העסקאות TXN_STATS_TOTAL_10MINUTE עם נתוני הנעילה LOCK_STATS_TOTAL_10MINUTE מאותה תקופה כדי להבין אם העלייה בחביון הממוצע של השלמת הפעולות נובעת מהעלייה בזמן ההמתנה לנעילה.

SELECT t.interval_end, t.avg_commit_latency_seconds, l.total_lock_wait_seconds
FROM spanner_sys.txn_stats_total_10minute t
LEFT JOIN spanner_sys.lock_stats_total_10minute l
ON t.interval_end = l.interval_end
WHERE
  t.interval_end >= "2020-11-12T21:50:00Z"
  AND t.interval_end <= "2020-11-12T23:50:00Z"
ORDER BY interval_end;

הנתונים הבאים הם דוגמה לתוצאות שמתקבלות מהשאילתה שלנו.

interval_end avg_commit_latency_seconds total_lock_wait_seconds
2020-11-12 21:40:00-07:00 0.002 0.090
2020-11-12 21:50:00-07:00 0.003 0.110
2020-11-12 22:00:00-07:00 0.002 0.100
2020-11-12 22:10:00-07:00 0.002 0.080
2020-11-12 22:20:00-07:00 0.030 0.240
2020-11-12 22:30:00-07:00 0.034 0.220
2020-11-12 22:40:00-07:00 0.034 0.218
2020-11-12 22:50:00-07:00 3.741 780.193
2020-11-12 23:00:00-07:00 0.042 0.240
2020-11-12 23:10:00-07:00 0.038 0.129
2020-11-12 23:20:00-07:00 0.021 0.128
2020-11-12 23:30:00-07:00 0.038 0.231

התוצאות הקודמות מראות עלייה דרמטית בavg_commit_latency_seconds ובtotal_lock_wait_seconds באותו פרק זמן מ2020-11-12 22:40:00 עד 2020-11-12 22:50:00, ולאחר מכן ירידה. חשוב לציין שהערך avg_commit_latency_seconds הוא משך הזמן הממוצע שבוצע רק בשלב השליחה. לעומת זאת, total_lock_wait_seconds הוא זמן הנעילה המצטבר לתקופה, ולכן הזמן נראה ארוך בהרבה מזמן אישור העסקה.

עכשיו, אחרי שאישרנו שזמן ההמתנה לנעילה קשור קשר הדוק לעלייה בחביון הכתיבה, נבדוק בשלב הבא אילו שורות ועמודות גורמות להמתנה הארוכה.

אפשר לראות אילו מפתחות שורות ועמודות היו עם זמני המתנה ארוכים לנעילה במהלך התקופה שנבחרה

כדי לגלות אילו מפתחות שורה ועמודות חוו זמני המתנה ארוכים לנעילה במהלך התקופה שאנחנו בודקים, אנחנו שולחים שאילתה לטבלה LOCK_STAT_TOP_10MINUTE, שמפרטת את מפתחות השורה והעמודות שתרמו הכי הרבה לזמן ההמתנה לנעילה.

הפונקציה CAST() בשאילתה הבאה ממירה את השדה row_range_start_key BYTES למחרוזת.

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_10minute t, spanner_sys.lock_stats_top_10minute s
WHERE
  t.interval_end = "2020-11-12T22:50:00Z" and s.interval_end = t.interval_end;
row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
זמרים(32) 780.193 780.193 1 LOCK_MODE: WriterShared

עמודה: Singers.SingerInfo

LOCK_MODE: ReaderShared

עמודה: Singers.SingerInfo

מטבלת התוצאות הזו אפשר לראות שההתנגשות התרחשה בטבלה Singersבמפתח SingerId=32. ‫Singers.SingerInfo היא העמודה שבה התרחש העימות של הנעילה בין ReaderShared לבין WriterShared.

זה סוג נפוץ של התנגשות שמתרחש כשטרנזקציה אחת מנסה לקרוא תא מסוים וטרנזקציה אחרת מנסה לכתוב לאותו תא. עכשיו אנחנו יודעים מה התא המדויק של הנתונים שעליו מתחרות הטרנזקציות על הנעילה, ולכן בשלב הבא נזהה את הטרנזקציות שמתחרות על הנעילות.

איך אפשר לדעת לאילו עסקאות יש גישה לעמודות שמעורבות בקונפליקט הנעילה

כדי לזהות את העסקאות שבהן יש השהיה משמעותית של ביצוע פעולות (commit) בפרק זמן מסוים בגלל התנגשויות נעילה, צריך להריץ שאילתה על העמודות הבאות מהטבלה SPANNER_SYS.TXN_STATS_TOTAL_10MINUTE:

  • fprint
  • read_columns
  • write_constructive_columns
  • avg_commit_latency_seconds

צריך לסנן את העמודות הנעולות שמופיעות בטבלה SPANNER_SYS.LOCK_STATS_TOP_10MINUTE:

  • עסקאות שקוראות עמודה כלשהי שגרמה להתנגשות נעילה כשניסו לקבל את נעילת ReaderShared.

  • עסקאות שכותבות לכל עמודה שגרמה להתנגשות נעילה כשניסו להשיג נעילה מסוג WriterShared.

SELECT
  fprint,
  read_columns,
  write_constructive_columns,
  avg_commit_latency_seconds
FROM spanner_sys.txn_stats_top_10minute t2
WHERE (
  EXISTS (
    SELECT * FROM t2.read_columns columns WHERE columns IN (
      SELECT DISTINCT(req.COLUMN)
      FROM spanner_sys.lock_stats_top_10minute t, t.SAMPLE_LOCK_REQUESTS req
      WHERE req.LOCK_MODE = "ReaderShared" AND t.interval_end ="2020-11-12T23:50:00Z"))
OR
  EXISTS (
    SELECT * FROM t2.write_constructive_columns columns WHERE columns IN (
      SELECT DISTINCT(req.COLUMN)
      FROM spanner_sys.lock_stats_top_10minute t, t.SAMPLE_LOCK_REQUESTS req
      WHERE req.LOCK_MODE = "WriterShared" AND t.interval_end ="2020-11-12T23:50:00Z"))
)
AND t2.interval_end ="2020-11-12T23:50:00Z"
ORDER BY avg_commit_latency_seconds DESC;

תוצאת השאילתה ממוינת לפי העמודה avg_commit_latency_seconds, כך שהעסקה עם זמן האחזור הכי גבוה של השלמת הפעולה מוצגת ראשונה.

fprint read_columns write_constructive_columns avg_commit_latency_seconds
1866043996151916800


‪['Singers.SingerInfo',
'Singers.FirstName',
'Singers.LastName',
'Singers._exists']
['Singers.SingerInfo'] 4.89
4168578515815911936 [] ['Singers.SingerInfo'] 3.65

תוצאות השאילתה מראות ששתי טרנזקציות ניסו לגשת לעמודה Singers.SingerInfo, שהיא העמודה שהיו בה התנגשויות נעילה במהלך התקופה. אחרי שמזהים את העסקאות שגורמות להתנגשויות הנעילה, אפשר לנתח את העסקאות באמצעות טביעת האצבע שלהן, fprint, כדי לזהות בעיות פוטנציאליות שתרמו להתנגשות הנעילה.

אחרי שבודקים את העסקה עם fprint=1866043996151916800, אפשר להשתמש בעמודות read_columns ו-write_constructive_columns כדי לזהות איזה חלק מקוד האפליקציה הפעיל את העסקה. אחר כך תוכלו לראות את ה-DML הבסיסי שלא מסנן לפי המפתח הראשי, SingerId. הפעולה הזו גרמה לסריקה מלאה של הטבלה ולנעילת הטבלה עד שהטרנזקציה אושרה.

כדי לפתור את הבעיה של נעילת הקובץ, אפשר:

  1. כדי לזהות את הערכים הנדרשים של SingerId, אפשר להשתמש בעסקה לקריאה בלבד.
  2. משתמשים בעסקת קריאה-כתיבה נפרדת כדי לעדכן את השורות של הערכים הנדרשים SingerId.

שימוש בשיטות מומלצות לצמצום התחרות על נעילה

בתרחיש לדוגמה שלנו, השתמשנו בנתונים סטטיסטיים של נעילות ושל עסקאות כדי לצמצם את הבעיה לעסקה שלא השתמשה במפתח הראשי של הטבלה שלנו כשביצעה עדכונים. הצענו רעיונות לשיפור העסקה, בהתאם לכך שידענו מראש את המפתחות של השורות שרצינו לעדכן או שלא ידענו.

כשבודקים בעיות פוטנציאליות בפתרון, או אפילו כשמתכננים את הפתרון, כדאי לפעול לפי השיטות המומלצות האלה כדי להפחית את מספר ההתנגשויות של נעילות במסד הנתונים.

  • מומלץ להימנע מקריאות גדולות בתוך עסקאות קריאה-כתיבה.

  • מומלץ להשתמש בעסקאות לקריאה בלבד כשאפשר, כי הן לא רוכשות נעילות.

  • מומלץ להימנע מסריקות מלאות של טבלאות בעסקאות קריאה-כתיבה. זה כולל כתיבת DML על תנאי במפתח הראשי או הקצאת טווח מפתחות ספציפי כשמשתמשים ב-Read API.

  • כדי שהתקופה שבה הנתונים נעולים תהיה קצרה, צריך לבצע את השינוי בעסקה לקריאה ולכתיבה כמה שיותר מהר אחרי שקוראים את הנתונים. טרנזקציה מסוג Read-write מבטיחה שהנתונים יישארו ללא שינוי אחרי שקוראים את הנתונים, עד שהשינוי יבוצע בהצלחה. כדי לעשות את זה, צריך לנעול את תאי הנתונים במהלך הקריאה ובמהלך השמירה של הנתונים. לכן, אם אפשר לקצר את תקופת הנעילה, הסיכוי שיהיו התנגשויות נעילה בעסקאות קטן יותר.

  • עדיף להשתמש בעסקאות קטנות במקום בעסקאות גדולות, או לשקול להשתמש ב-Partitioned DML לעסקאות DML ארוכות. טרנזקציה שפועלת במשך זמן רב מקבלת נעילה למשך זמן רב, ולכן מומלץ לפצל טרנזקציה שמשפיעה על אלפי שורות לכמה טרנזקציות קטנות יותר שמעדכנות מאות שורות, בכל הזדמנות שאפשר.

  • אם לא צריך את ההבטחה שניתנת על ידי טרנזקציית קריאה-כתיבה, כדאי להימנע מקריאת נתונים בטרנזקציית קריאה-כתיבה לפני ביצוע השינוי, למשל על ידי קריאת הנתונים בטרנזקציית קריאה בלבד נפרדת. רוב העימותים של נעילות מתרחשים בגלל ההבטחה החזקה, כדי להבטיח שהנתונים יישארו ללא שינוי בין הקריאה לבין השמירה. לכן, אם העסקה לקריאה ולכתיבה לא קוראת נתונים, היא לא צריכה לנעול את התאים למשך זמן רב.

  • צריך לציין רק את קבוצת העמודות המינימלית שנדרשת בעסקת קריאה-כתיבה. הנעילות ב-Spanner הן ברמת תא הנתונים. לכן, כשטרנזקציית קריאה-כתיבה קוראת יותר מדי עמודות, היא מקבלת ReaderSharedנעילה על התאים האלה. זה עלול לגרום להתנגשויות נעילה כשטרנזקציות אחרות מקבלות נעילת WriterShared על פעולות כתיבה בעמודות העודפות. לדוגמה, במקום * בקריאה, אפשר לציין קבוצה של עמודות.

  • צמצום הקריאות ל-API בעסקת קריאה-כתיבה. ההשהיה של קריאות ל-API עלולה לגרום למאבק על נעילה ב-Spanner, כי קריאות ל-API כפופות לעיכובים ברשת וגם לעיכובים בצד השרת. מומלץ לבצע קריאות ל-API מחוץ לעסקאות קריאה-כתיבה, כשהדבר אפשרי. אם אתם חייבים להפעיל קריאות ל-API בתוך טרנזקציה של קריאה וכתיבה, הקפידו לעקוב אחרי זמן האחזור של הקריאות ל-API כדי לצמצם את ההשפעה על תקופת ההמתנה לקבלת נעילה.

  • עובדים בהתאם לשיטות המומלצות לעיצוב סכימה.

  • אם האפשרות של הטיה בכתיבה לא רלוונטית לאפליקציה שלכם, כדאי להשתמש בבידוד של קריאה חוזרת עם ברירת המחדל האופטימית של מקביליות כדי למנוע התנגשויות של קריאה-כתיבה. מידע נוסף זמין במאמר בנושא בידוד של קריאה חוזרת.

  • עבור עומסי עבודה עם מחלוקת נמוכה על קריאה וכתיבה, כדאי להשתמש בבידוד ניתן לסדר עם בקרת בו-זמניות אופטימית כדי להימנע מנעילת טרנזקציות עד לזמן השמירה, תוך הקפדה על כך שכל הקריאות של הטרנזקציה יהיו תקפות בזמן השמירה. מידע נוסף על האפשרות הזו מופיע במאמר שליטה בו-זמנית אופטימית.

המאמרים הבאים