במסד נתונים של Spanner, Spanner יוצר באופן אוטומטי אינדקס לכל מפתח ראשי של טבלה. לדוגמה, לא צריך לעשות שום דבר כדי ליצור אינדקס למפתח הראשי של Singers, כי הוא נוצר אוטומטית.
אפשר גם ליצור אינדקסים משניים לעמודות אחרות. הוספת אינדקס משני לעמודה מייעלת את החיפוש של נתונים בעמודה הזו. לדוגמה, אם אתם צריכים לחפש במהירות אלבום לפי שם, כדאי ליצור אינדקס משני ב-AlbumTitle, כדי ש-Spanner לא יצטרך לסרוק את כל הטבלה.
אם החיפוש בדוגמה הקודמת מתבצע בתוך טרנזקציה של קריאה וכתיבה,
החיפוש היעיל יותר גם מונע נעילות של הטבלה כולה,
מה שמאפשר הוספות ועדכונים בו-זמניים בטבלה לשורות מחוץ לטווח החיפוש AlbumTitle.
בנוסף ליתרונות שהם מביאים לחיפושים, אינדקסים משניים יכולים גם לעזור ל-Spanner לבצע סריקות בצורה יעילה יותר, וכך לאפשר סריקות אינדקס במקום סריקות מלאות של טבלאות.
בכל אינדקס משני, Spanner שומר את הנתונים הבאים:
- כל עמודות המפתח מטבלת הבסיס
- כל העמודות שנכללות באינדקס
- כל העמודות שצוינו בסעיף
STORING(מסדי נתונים של ניב GoogleSQL) או בסעיףINCLUDE(מסדי נתונים של ניב PostgreSQL) בהגדרת האינדקס.
עם הזמן, מערכת Spanner מנתחת את הטבלאות כדי לוודא שהאינדקסים המשניים משמשים לשאילתות המתאימות.
הוספת אינדקס משני
הזמן היעיל ביותר להוספת אינדקס משני הוא בזמן יצירת הטבלה. כדי ליצור טבלה ואינדקסים שלה בו-זמנית, שולחים את הצהרות ה-DDL של הטבלה החדשה והאינדקסים החדשים בבקשה אחת ל-Spanner.
ב-Spanner, אפשר גם להוסיף אינדקס משני חדש לטבלה קיימת בזמן שמסד הנתונים ממשיך לשרת תנועה. בדומה לשינויים אחרים בסכימה ב-Spanner, הוספת אינדקס למסד נתונים קיים לא מחייבת להעביר את מסד הנתונים למצב אופליין, ולא נועלת עמודות או טבלאות שלמות.
בכל פעם שמוסיפים אינדקס חדש לטבלה קיימת, Spanner מבצע backfill באופן אוטומטי, כלומר מאכלס את האינדקס כדי לשקף תצוגה עדכנית של הנתונים שמתווספים לאינדקס. מערכת Spanner מנהלת את תהליך המילוי הזה בשבילכם, והתהליך פועל ברקע באמצעות משאבי צמתים בעדיפות נמוכה. מהירות מילוי החסר של האינדקס מותאמת לשינויים במשאבי הצומת במהלך יצירת האינדקס, ומילוי החסר לא משפיע באופן משמעותי על הביצועים של מסד הנתונים.
יצירת האינדקס יכולה להימשך כמה דקות עד כמה שעות. יצירת אינדקס היא עדכון סכימה, ולכן היא כפופה לאותם מגבלות ביצועים כמו כל עדכון סכימה אחר. הזמן שנדרש ליצירת אינדקס משני תלוי בכמה גורמים:
- גודל מערך הנתונים
- קיבולת החישוב של המכונה
- העומס על המופע
כדי לראות את ההתקדמות בתהליך מילוי חוסרים באינדקס, אפשר לעיין בקטע ההתקדמות.
חשוב לדעת שאם משתמשים בעמודה commit timestamp כחלק הראשון של האינדקס המשני, יכול להיות שייווצרו נקודות חמות והביצועים של פעולות הכתיבה ירדו.
משתמשים בהצהרת CREATE INDEX כדי להגדיר אינדקס משני בסכימה. הנה כמה דוגמאות:
כדי ליצור אינדקס לכל Singers במסד הנתונים לפי השם הפרטי ושם המשפחה:
GoogleSQL
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
PostgreSQL
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
כדי ליצור אינדקס של כל Songs במסד הנתונים לפי הערך של SongName:
GoogleSQL
CREATE INDEX SongsBySongName ON Songs(SongName);
PostgreSQL
CREATE INDEX SongsBySongName ON Songs(SongName);
כדי להוסיף לאינדקס רק את השירים של זמר מסוים, משתמשים בסעיף INTERLEAVE IN כדי לשלב את האינדקס בטבלה Singers:
GoogleSQL
CREATE INDEX SongsBySingerSongName ON Songs(SingerId, SongName),
INTERLEAVE IN Singers;
PostgreSQL
CREATE INDEX SongsBySingerSongName ON Songs(SingerId, SongName)
INTERLEAVE IN Singers;
כדי להוסיף לאינדקס רק את השירים באלבום מסוים:
GoogleSQL
CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName),
INTERLEAVE IN Albums;
PostgreSQL
CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName)
INTERLEAVE IN Albums;
כדי ליצור אינדקס לפי סדר יורד של SongName:
GoogleSQL
CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC),
INTERLEAVE IN Albums;
PostgreSQL
CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC)
INTERLEAVE IN Albums;
הערה: ההערה הקודמת DESC רלוונטית רק ל-SongName. כדי ליצור אינדקס לפי סדר יורד של מפתחות אינדקס אחרים, צריך להוסיף להם את ההערה DESC:
SingerId DESC, AlbumId DESC.
חשוב גם לזכור ש-PRIMARY_KEY היא מילה שמורה ואי אפשר להשתמש בה כשם של אינדקס. זה השם שניתן לאינדקס פסאודו שנוצר כשיוצרים טבלה עם הגדרת PRIMARY KEY.
לפרטים נוספים ולשיטות מומלצות לבחירת אינדקסים לא משולבים ואינדקסים משולבים, אפשר לעיין במאמרים בחירה בין אינדקסים משולבים ואינדקסים גלובליים, אפשרויות אינדקס ושימוש באינדקס משולב בעמודה שהערך שלה עולה או יורד באופן מונוטוני.
אינדקסים ושיבוץ
אפשר לשלב אינדקסים של Spanner עם טבלאות אחרות כדי למקם שורות אינדקס באותו מיקום עם שורות של טבלה אחרת. בדומה לשיטת השילוב של טבלאות ב-Spanner, העמודות של המפתח הראשי של ההורה של האינדקס חייבות להיות קידומת של העמודות באינדקס, עם התאמה בסוג ובסדר המיון. בשונה מטבלאות משולבות, לא נדרש להתאים את שמות העמודות. כל שורה באינדקס משולב מאוחסנת פיזית יחד עם שורת האב המשויכת.
לדוגמה, נניח את הסכימה הבאה:
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
SingerInfo PROTO<Singer>(MAX)
) PRIMARY KEY (SingerId);
CREATE TABLE Albums (
SingerId INT64 NOT NULL,
AlbumId INT64 NOT NULL,
AlbumTitle STRING(MAX),
PublisherId INT64 NOT NULL
) 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,
PublisherId INT64 NOT NULL,
SongName STRING(MAX)
) PRIMARY KEY (SingerId, AlbumId, TrackId),
INTERLEAVE IN PARENT Albums ON DELETE CASCADE;
CREATE TABLE Publishers (
Id INT64 NOT NULL,
PublisherName STRING(MAX)
) PRIMARY KEY (Id);
כדי ליצור אינדקס לכל Singers במסד הנתונים לפי השם הפרטי ושם המשפחה, צריך ליצור אינדקס. כך מגדירים את האינדקס SingersByFirstLastName:
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
אם רוצים ליצור אינדקס של Songs ב-(SingerId, AlbumId, SongName), אפשר לעשות את הפעולות הבאות:
CREATE INDEX SongsBySingerAlbumSongName
ON Songs(SingerId, AlbumId, SongName);
אפשר גם ליצור אינדקס שמשולב עם ישות אב של Songs, כמו בדוגמה הבאה:
CREATE INDEX SongsBySingerAlbumSongName
ON Songs(SingerId, AlbumId, SongName),
INTERLEAVE IN Albums;
בנוסף, אפשר גם ליצור אינדקס של Songs ב-(PublisherId, SingerId, AlbumId, SongName) שמשולב עם טבלה שלא נמצאת ברמה שמעל Songs, כמו Publishers. הערה: המפתח הראשי של הטבלה Publishers (id) לא מופיע כקידומת של העמודות עם האינדקס בדוגמה הבאה. הפעולה הזו עדיין מותרת כי Publishers.Id ו-Songs.PublisherId חולקים את אותו סוג, סדר מיון ואפשרות להגדרה כ-null.
CREATE INDEX SongsByPublisherSingerAlbumSongName
ON Songs(PublisherId, SingerId, AlbumId, SongName),
INTERLEAVE IN Publishers;
בחירה בין אינדקסים משולבים לבין אינדקסים גלובליים
כשיוצרים אינדקס משני ב-Spanner, מגדירים אותו כאינדקס גלובלי (ברירת המחדל, אינדקס לא משולב) או כאינדקס משולב. בחירה בסוג האינדקס האופטימלי היא קריטית לביצועי קריאה וכתיבה.
השוואת אינדקסים
| תכונה | אינדקס משולב | אינדקס גלובלי |
|---|---|---|
| מיקום האחסון | השורה ממוקמת באותו פיצול (עץ שורות) עם השורה הראשית. | הנתונים מאוחסנים בטבלת בסיס נפרדת, שאפשר לפצל אותה בנפרד מטבלת הבסיס. |
| הדרישות העיקריות | המפתח הראשי של טבלת ההורה חייב להיות תחילית של מפתחות האינדקס. | אין הגבלות על מפתחות האינדקס. |
| כתיבת תקורה | נמוכה כשמבצעים שזירה בישות אב של טבלת בסיס, העדכונים בדרך כלל מקומיים לפיצול, וכך נמנעים מביצוע פעולות שמירה דו-שלביות בין פיצולים. אם הנתונים משולבים בטבלה שהיא לא טבלת אב, עלויות הכתיבה גבוהות יותר כי יכול להיות שעדכונים ידרשו עסקאות מבוזרות. | גבוה יותר. העדכונים מתפרסים לעיתים קרובות על פני פיצולים שונים, ולכן נדרשות עסקאות מבוזרות. |
| מתאים במיוחד ל | שאילתות שהוגדרו לישות אם אחת – לדוגמה, 'find photos for user USER_NAME'. | שאילתות שמחפשות בכל מסד הנתונים – לדוגמה, "find user by email". |
למה בדרך כלל עדיף להשתמש במדדים משולבים
במסד נתונים מבוזר כמו Spanner, למיקום הנתונים יש חשיבות רבה לביצועים. בדרך כלל עדיף להשתמש במדדים משולבים כי הם שומרים על מיקום הנתונים:
- קריאות יעילות: אם השאילתות תמיד מסננות לפי מזהה של ישות אב, אינדקס משולב מאפשר ל-Spanner לספק את השאילתה על ידי סריקה רק של הפיצול שמכיל את ישות האב, במקום לבדוק כמה פיצולים.
- כתיבה יעילה: כשמעדכנים שורה בטבלה, Spanner צריך לעדכן גם את האינדקסים המשניים שלה. אם משתמשים באינדקס גלובלי, סביר להניח שרשומת האינדקס נמצאת בפיצול אחר מהשורה הבסיסית. כך Spanner נאלץ לבצע טרנזקציה מבוזרת (אישור דו-שלבי) בין הפיצולים, מה שמגדיל את זמן האחזור ואת השימוש במעבד. אם אינדקס משולב משולב בישות אב של טבלת הבסיס, רשומת האינדקס מאוחסנת באותו פיצול כמו שורת הבסיס, ולכן הכתיבה היא פעולה מקומית.
מדריך לקבלת החלטות
כדי לבחור את סוג האינדקס האופטימלי לתרחיש השימוש שלכם, מומלץ להיעזר בהנחיות הבאות:
מתי כדאי להשתמש באינדקס משולב
משתמשים במדד משולב אם מתקיים אחד או יותר מהתנאים הבאים:
- השאילתות תמיד כוללות מסנן במפתח הראשי של טבלת האב – לדוגמה,
WHERE UserId = @userId AND .... - אפשר להשתמש בעמודות של המפתח הראשי של טבלת האב כקידומת למפתחות האינדקס.
- רוצים לצמצם את זמן האחזור של הכתיבה ואת התקורה של המעבד לצורך תחזוקת האינדקס.
לדוגמה:
-- Indexing albums by title for a specific singer
CREATE INDEX AlbumsBySingerTitle ON Albums(SingerId, AlbumTitle),
INTERLEAVE IN Singers;
מתי כדאי להשתמש באינדקס גלובלי
משתמשים באינדקס גלובלי אם מתקיים אחד או יותר מהתנאים הבאים:
- צריך לשלוח שאילתה לנתונים בכל מסד הנתונים בלי לסנן לפי ישות אם – לדוגמה, חיפוש גלובלי.
- העמודות שרוצים ליצור להן אינדקס לא חולקות קידומת עם אף טבלת אב.
לדוגמה:
-- Indexing all albums globally by title. For example, for a global search catalog.
CREATE INDEX AlbumsByTitle ON Albums(AlbumTitle);
בדיקת ההתקדמות של מילוי חוסרים באינדקס
המסוף
בתפריט הניווט של Spanner, לוחצים על הכרטיסייה Operations. בדף Operations מוצגת רשימה של פעולות שפועלות.
מחפשים את פעולת המילוי החוזר ברשימה. אם הפעולה עדיין פועלת, אינדיקטור ההתקדמות בעמודה שעת סיום מציג את אחוז הפעולה שהושלם, כמו שמוצג בתמונה הבאה:

gcloud
כדי לבדוק את התקדמות הפעולה, משתמשים ב-gcloud spanner operations describe.
קבלת מזהה הפעולה:
gcloud spanner operations list --instance=INSTANCE-NAME \ --database=DATABASE-NAME --type=DATABASE_UPDATE_DDL
מחליפים את מה שכתוב בשדות הבאים:
- INSTANCE-NAME בשם של מופע Spanner.
- DATABASE-NAME בשם של מסד הנתונים.
הערות שימוש:
כדי להגביל את הרשימה, מציינים את הדגל
--filter. לדוגמה:-
--filter="metadata.name:example-db"מציג רק את הפעולות במסד נתונים ספציפי. --filter="error:*"מפרט רק את פעולות הגיבוי שנכשלו.
מידע על תחביר המסננים זמין במאמר gcloud topic filters. מידע על סינון פעולות גיבוי מופיע בשדה
filterבמאמר ListBackupOperationsRequest.-
הדגל
--typeלא תלוי באותיות רישיות.
הפלט אמור להיראות כך:
OPERATION_ID STATEMENTS DONE @TYPE _auto_op_123456 CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName) False UpdateDatabaseDdlMetadata CREATE INDEX SongsBySingerAlbumSongName ON Songs(SingerId, AlbumId, SongName), INTERLEAVE IN Albums _auto_op_234567 True CreateDatabaseMetadataמריצים את
gcloud spanner operations describe:gcloud spanner operations describe \ --instance=INSTANCE-NAME \ --database=DATABASE-NAME \ projects/PROJECT-NAME/instances/INSTANCE-NAME/databases/DATABASE-NAME/operations/OPERATION_ID
מחליפים את מה שכתוב בשדות הבאים:
- INSTANCE-NAME: שם מכונת Spanner.
- DATABASE-NAME: שם מסד הנתונים של Spanner.
- PROJECT-NAME: שם הפרויקט.
- OPERATION-ID: מזהה הפעולה שרוצים לבדוק.
בקטע
progressבפלט מוצג אחוז הפעולה שהושלמה. הפלט אמור להיראות כך:done: true ... progress: - endTime: '2021-01-22T21:58:42.912540Z' progressPercent: 100 startTime: '2021-01-22T21:58:11.053996Z' - progressPercent: 67 startTime: '2021-01-22T21:58:11.053996Z' ...
REST v1
קבלת מזהה הפעולה:
gcloud spanner operations list --instance=INSTANCE-NAME
--database=DATABASE-NAME --type=DATABASE_UPDATE_DDL
מחליפים את מה שכתוב בשדות הבאים:
- INSTANCE-NAME בשם של מופע Spanner.
- DATABASE-NAME בשם של מסד הנתונים.
לפני שמשתמשים בנתוני הבקשה, צריך להחליף את הנתונים הבאים:
- PROJECT-ID: מזהה הפרויקט.
- INSTANCE-ID: מזהה המכונה.
- DATABASE-ID: מזהה מסד הנתונים.
- OPERATION-ID: מזהה הפעולה.
ה-method של ה-HTTP וכתובת ה-URL:
GET https://spanner.googleapis.com/v1/projects/PROJECT-ID/instances/INSTANCE-ID/databases/DATABASE-ID/operations/OPERATION-ID
כדי לשלוח את הבקשה צריך להרחיב אחת מהאפשרויות הבאות:
אתם אמורים לקבל תגובת JSON שדומה לזו:
{
...
"progress": [
{
"progressPercent": 100,
"startTime": "2023-05-27T00:52:27.366688Z",
"endTime": "2023-05-27T00:52:30.184845Z"
},
{
"progressPercent": 100,
"startTime": "2023-05-27T00:52:30.184845Z",
"endTime": "2023-05-27T00:52:40.750959Z"
}
],
...
"done": true,
"response": {
"@type": "type.googleapis.com/google.protobuf.Empty"
}
}
ב-gcloud וב-REST, אפשר לראות את ההתקדמות של כל הצהרת מילוי חוזר של אינדקס בקטע progress. לכל הצהרה במערך ההצהרות,
יש שדה תואם במערך ההתקדמות. סדר המערך הזה של התקדמות התהליך
תואם לסדר של מערך ההצהרות. אחרי שהם יהיו זמינים, השדות startTime, progressPercent ו-endTime יאוכלסו בהתאם.
שימו לב: בפלט לא מוצג זמן משוער לסיום ההתקדמות של מילוי החסר.
אם הפעולה נמשכת יותר מדי זמן, אפשר לבטל אותה. מידע נוסף זמין במאמר בנושא ביטול יצירת אינדקס.
תרחישים שבהם אפשר לראות את ההתקדמות של מילוי החוסרים באינדקס
יש תרחישים שונים שיכולים לקרות כשמנסים לבדוק את התקדמות האכלת הנתונים של אינדקס. הצהרות על יצירת אינדקס שדורשות מילוי חוזר של אינדקס הן חלק מפעולות עדכון סכימה, ויכולות להיות כמה הצהרות שהן חלק מפעולת עדכון סכימה.
התרחיש הראשון הוא הפשוט ביותר, שבו הצהרת יצירת האינדקס היא ההצהרה הראשונה בפעולת העדכון של הסכימה. מכיוון שההצהרה על יצירת האינדקס היא ההצהרה הראשונה, היא הראשונה שעוברת עיבוד ומופעלת בגלל סדר הביצוע.
שדה startTime של הצהרת יצירת האינדקס יתמלא באופן מיידי בשעת ההתחלה של פעולת עדכון הסכימה. לאחר מכן, השדה progressPercent של הצהרת יצירת האינדקס מאוכלס כשההתקדמות של מילוי האינדקס היא מעל 0%. לבסוף, השדה endTime מאוכלס אחרי שההצהרה מאושרת.
התרחיש השני הוא כשמשפט יצירת האינדקס הוא לא המשפט הראשון בפעולת עדכון הסכימה. שום שדה שקשור להצהרת יצירת האינדקס לא יאוכלס עד שההצהרות הקודמות יאושרו, בגלל סדר הביצוע.
בדומה לתרחיש הקודם, אחרי שהדוחות הקודמים יאושרו, השדה startTime של הצהרת יצירת האינדקס יתמלא קודם, ואחריו השדה progressPercent. לבסוף, השדה endTime מתמלא אחרי שהדוח מסיים את השמירה.
ביטול יצירת האינדקס
אפשר להשתמש ב-Google Cloud CLI כדי לבטל את יצירת האינדקס. כדי לאחזר רשימה של פעולות עדכון סכימה במסד נתונים של Spanner, משתמשים בפקודה gcloud spanner operations list וכוללים את האפשרות --filter:
gcloud spanner operations list \
--instance=INSTANCE \
--database=DATABASE \
--filter="@TYPE:UpdateDatabaseDdlMetadata"
מאתרים את OPERATION_ID של הפעולה שרוצים לבטל, ואז משתמשים בפקודה gcloud spanner operations cancel כדי לבטל אותה:
gcloud spanner operations cancel OPERATION_ID \
--instance=INSTANCE \
--database=DATABASE
צפייה באינדקסים קיימים
כדי לראות מידע על אינדקסים קיימים במסד נתונים, אפשר להשתמש במסוףGoogle Cloud או ב-Google Cloud CLI:
המסוף
נכנסים לדף Instances של Spanner במסוף Google Cloud .
לוחצים על השם של המכונה שרוצים להציג.
בחלונית הימנית, לוחצים על מסד הנתונים שרוצים לראות ואז על הטבלה שרוצים לראות.
לוחצים על הכרטיסייה Indexes (אינדקסים). במסוף Google Cloud מופיעה רשימה של אינדקסים.
אופציונלי: כדי לקבל פרטים על אינדקס, כמו העמודות שהוא כולל, לוחצים על שם האינדקס.
gcloud
משתמשים בפקודה gcloud spanner databases ddl describe:
gcloud spanner databases ddl describe DATABASE \
--instance=INSTANCE
ה-CLI של gcloud מדפיס את ההצהרות של שפת הגדרת הנתונים (DDL)
כדי ליצור את הטבלאות והאינדקסים של מסד הנתונים. הצהרות CREATE
INDEX מתארות את האינדקסים הקיימים. לדוגמה:
--- |-
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
SingerInfo BYTES(MAX),
) PRIMARY KEY(SingerId)
---
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName)
שאילתה עם אינדקס ספציפי
בקטעים הבאים מוסבר איך לציין אינדקס בהצהרת SQL ועם ממשק הקריאה של Spanner. בדוגמאות שבקטעים האלה מניחים שהוספתם עמודה MarketingBudget לטבלה Albums ויצרתם אינדקס בשם AlbumsByAlbumTitle:
GoogleSQL
CREATE TABLE Albums (
SingerId INT64 NOT NULL,
AlbumId INT64 NOT NULL,
AlbumTitle STRING(MAX),
MarketingBudget INT64,
) PRIMARY KEY (SingerId, AlbumId),
INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);
PostgreSQL
CREATE TABLE Albums (
SingerId BIGINT NOT NULL,
AlbumId BIGINT NOT NULL,
AlbumTitle VARCHAR,
MarketingBudget BIGINT,
PRIMARY KEY (SingerId, AlbumId)
) INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);
ציון אינדקס בהצהרת SQL
כשמשתמשים ב-SQL כדי לשלוח שאילתה לטבלת Spanner, Spanner משתמש באופן אוטומטי בכל האינדקסים שיכולים לייעל את השאילתה. כתוצאה מכך, לא צריך לציין אינדקס לשאילתות SQL. עם זאת, לגבי שאילתות שחיוניות לעומס העבודה שלכם, Google ממליצה להשתמש בהנחיות FORCE_INDEX בהצהרות SQL כדי לקבל ביצועים עקביים יותר.
במקרים מסוימים, יכול להיות ש-Spanner יבחר אינדקס שיגרום להגדלת זמן האחזור של השאילתה. אם ביצעתם את השלבים לפתרון בעיות של ירידה בביצועים ואישרתם שכדאי לנסות אינדקס אחר לשאילתה, תוכלו לציין את האינדקס כחלק מהשאילתה.
כדי לציין אינדקס בהצהרת SQL, משתמשים ברמז FORCE_INDEX כדי לספק הוראת אינדקס. התחביר של הוראות לאינדקס הוא:
GoogleSQL
FROM MyTable@{FORCE_INDEX=MyTableIndex}
PostgreSQL
FROM MyTable /*@ FORCE_INDEX = MyTableIndex */
אפשר גם להשתמש בהוראת אינדקס כדי להנחות את Spanner לסרוק את טבלת הבסיס במקום להשתמש באינדקס:
GoogleSQL
FROM MyTable@{FORCE_INDEX=_BASE_TABLE}
PostgreSQL
FROM MyTable /*@ FORCE_INDEX = _BASE_TABLE */
אפשר להשתמש בהנחיית אינדקס כדי להנחות את Spanner לסרוק אינדקס בטבלה עם סכימות בעלות שם:
GoogleSQL
FROM NAMED_SCHEMA_NAME.TABLE_NAME@{FORCE_INDEX="NAMED_SCHEMA_NAME.TABLE_INDEX_NAME"}
PostgreSQL
FROM NAMED_SCHEMA_NAME.TABLE_NAME /*@ FORCE_INDEX = TABLE_INDEX_NAME */
הדוגמה הבאה מציגה שאילתת SQL שמציינת אינדקס:
GoogleSQL
SELECT AlbumId, AlbumTitle, MarketingBudget
FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
WHERE AlbumTitle >= "Aardvark" AND AlbumTitle < "Goo";
PostgreSQL
SELECT AlbumId, AlbumTitle, MarketingBudget
FROM Albums /*@ FORCE_INDEX = AlbumsByAlbumTitle */
WHERE AlbumTitle >= 'Aardvark' AND AlbumTitle < 'Goo';
הוראת אינדקס עשויה לחייב את מעבד השאילתות של Spanner לקרוא עמודות נוספות שנדרשות לשאילתה אבל לא מאוחסנות באינדקס.
מעבד השאילתות מאחזר את העמודות האלה על ידי צירוף האינדקס לטבלת הבסיס. כדי להימנע מצירוף נוסף, משתמשים בסעיף STORING (מסדי נתונים של ניב GoogleSQL) או בסעיף INCLUDE (מסדי נתונים של ניב PostgreSQL) כדי לאחסן את העמודות הנוספות באינדקס.
בדוגמה הקודמת, העמודה MarketingBudget לא מאוחסנת באינדקס, אבל שאילתת ה-SQL בוחרת את העמודה הזו. כתוצאה מכך, כדי להחזיר את תוצאות השאילתה, מערכת Spanner צריכה לחפש את העמודה MarketingBudget בטבלת הבסיס ואז לשלב אותה עם נתונים מהאינדקס.
מערכת Spanner מציגה שגיאה אם יש ב-directive של האינדקס אחת מהבעיות הבאות:
- האינדקס לא קיים.
- האינדקס נמצא בטבלת בסיס אחרת.
- בשאילתה חסר מסנן
NULLנדרש לאינדקסNULL_FILTERED.
בדוגמאות הבאות מוסבר איך לכתוב ולהריץ שאילתות שמביאות את הערכים של AlbumId, AlbumTitle ו-MarketingBudget באמצעות האינדקס AlbumsByAlbumTitle:
C++
C#
המשך
Java
Node.js
PHP
Python
Ruby
ציון אינדקס בממשק הקריאה
כשמשתמשים בממשק הקריאה של Spanner ורוצים ש-Spanner ישתמש באינדקס, צריך לציין את האינדקס. בממשק הקריאה לא נבחר האינדקס באופן אוטומטי.
בנוסף, האינדקס צריך להכיל את כל הנתונים שמופיעים בתוצאות השאילתה, לא כולל עמודות שמהוות חלק מהמפתח הראשי. המגבלה הזו קיימת כי ממשק הקריאה לא תומך בצירופים בין האינדקס לבין טבלת הבסיס. אם אתם צריכים לכלול עמודות אחרות בתוצאות השאילתה, יש לכם כמה אפשרויות:
- משתמשים בסעיף
STORINGאוINCLUDEכדי לאחסן את העמודות הנוספות באינדקס. - מריצים שאילתה בלי לכלול את העמודות הנוספות, ואז משתמשים במפתחות הראשיים כדי לשלוח שאילתה נוספת שקוראת את העמודות הנוספות.
Spanner מחזיר ערכים מהאינדקס בסדר מיון עולה לפי מפתח האינדקס. כדי לאחזר ערכים בסדר יורד, מבצעים את השלבים הבאים:
מוסיפים הערה למפתח האינדקס עם
DESC. לדוגמה:CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle DESC);ההערה
DESCחלה על מפתח אינדקס יחיד. אם האינדקס כולל יותר ממפתח אחד, ואתם רוצים שתוצאות השאילתה יופיעו בסדר יורד על סמך כל המפתחות, צריך לכלול הערתDESCלכל מפתח.אם הקריאה מציינת טווח מפתחות, צריך לוודא שטווח המפתחות גם הוא מסודר בסדר יורד. במילים אחרות, הערך של מקש ההתחלה צריך להיות גדול מהערך של מקש הסיום.
בדוגמה הבאה אפשר לראות איך מאחזרים את הערכים של AlbumId ושל AlbumTitle באמצעות האינדקס AlbumsByAlbumTitle:
C++
C#
המשך
Java
Node.js
PHP
Python
Ruby
יצירת אינדקס לסריקות של אינדקס בלבד
אופציונלי, אפשר להשתמש בסעיף STORING (למסדי נתונים של GoogleSQL) או בסעיף INCLUDE (למסדי נתונים של PostgreSQL) כדי לאחסן עותק של עמודה באינדקס. סוג האינדקס הזה מספק יתרונות לשאילתות ולקריאות קריאה שמשתמשות באינדקס, אבל הוא דורש שימוש באחסון נוסף:
- שאילתות SQL שמשתמשות באינדקס ובעמודות שנבחרו ומאוחסנות בסעיף
STORINGאוINCLUDEלא דורשות איחוד נוסף עם טבלת הבסיס. - קריאות
read()שמשתמשות באינדקס יכולות לקרוא עמודות שמאוחסנות על ידי פסוקיתSTORING/INCLUDE.
לדוגמה, נניח שיצרתם גרסה חלופית של AlbumsByAlbumTitle
שמאחסנת עותק של העמודה MarketingBudget באינדקס (שימו לב לסעיף STORING או INCLUDE שמודגש):
GoogleSQL
CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);
PostgreSQL
CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget);
עם האינדקס הישן AlbumsByAlbumTitle, מערכת Spanner צריכה לבצע הצטרפות לאינדקס
עם טבלת הבסיס, ואז לאחזר את העמודה מטבלת הבסיס. עם האינדקס החדשAlbumsByAlbumTitle2, Spanner קורא את העמודה ישירות מהאינדקס, וזה יעיל יותר.
אם משתמשים בממשק הקריאה במקום ב-SQL, האינדקס החדש AlbumsByAlbumTitle2
מאפשר גם לקרוא את העמודה MarketingBudget ישירות:
C++
C#
המשך
Java
Node.js
PHP
Python
Ruby
שינוי אינדקס
אפשר להשתמש בהצהרה ALTER INDEX כדי להוסיף עמודות נוספות לאינדקס קיים או להסיר עמודות. הפעולה הזו יכולה לעדכן את רשימת העמודות שמוגדרת על ידי פסקה STORING (מסדי נתונים של ניב GoogleSQL) או פסקה INCLUDE (מסדי נתונים של ניב PostgreSQL) כשיוצרים את האינדקס. אי אפשר להשתמש בהצהרה הזו כדי להוסיף עמודות למפתח האינדקס או להסיר ממנו עמודות. לדוגמה, במקום ליצור אינדקס חדש AlbumsByAlbumTitle2, אפשר להשתמש ב-ALTER INDEX כדי להוסיף עמודה ל-AlbumsByAlbumTitle, כמו בדוגמה הבאה:
GoogleSQL
ALTER INDEX AlbumsByAlbumTitle ADD STORED COLUMN MarketingBudget
PostgreSQL
ALTER INDEX AlbumsByAlbumTitle ADD INCLUDE COLUMN MarketingBudget
כשמוסיפים עמודה חדשה לאינדקס קיים, Spanner משתמש בתהליך מילוי חוזר ברקע. במהלך מילוי החוסרים, אי אפשר לקרוא את העמודה באינדקס, ולכן יכול להיות שלא תהיה עלייה בביצועים כמו שציפיתם. אפשר להשתמש בפקודה gcloud spanner operations כדי להציג את רשימת הפעולות הממושכות ולראות את הסטטוס שלהן.
מידע נוסף זמין במאמר בנושא תיאור פעולה.
אפשר גם להשתמש באפשרות ביטול הפעולה כדי לבטל פעולה שמתבצעת.
אחרי שמילוי החוסרים מסתיים, Spanner מוסיף את העמודה לאינדקס. ככל שהאינדקס גדל, יכול להיות שמהירות השאילתות שמשתמשות באינדקס תהיה נמוכה יותר.
בדוגמה הבאה אפשר לראות איך משמיטים עמודה מאינדקס:
GoogleSQL
ALTER INDEX AlbumsByAlbumTitle DROP STORED COLUMN MarketingBudget
PostgreSQL
ALTER INDEX AlbumsByAlbumTitle DROP INCLUDE COLUMN MarketingBudget
אינדקס של ערכי NULL
כברירת מחדל, Spanner יוצר אינדקס לערכים NULL. לדוגמה, נניח שהגדרתם את האינדקס SingersByFirstLastName בטבלה Singers:
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
כל השורות של Singers נוספות לאינדקס, גם אם FirstName או LastName, או שניהם, הם NULL.
כשמבצעים אינדוקס של ערכי NULL, אפשר לבצע שאילתות SQL וקריאות יעילות של נתונים שכוללים ערכי NULL. לדוגמה, אפשר להשתמש בהצהרת שאילתת ה-SQL הזו כדי למצוא את כל Singers עם NULL FirstName:
GoogleSQL
SELECT s.SingerId, s.FirstName, s.LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastName} AS s
WHERE s.FirstName IS NULL;
PostgreSQL
SELECT s.SingerId, s.FirstName, s.LastName
FROM Singers /* @ FORCE_INDEX = SingersByFirstLastName */ AS s
WHERE s.FirstName IS NULL;
סדר המיון של ערכי NULL
Spanner ממיין את NULL כערך הקטן ביותר לכל סוג נתונים. במיון עולה (ASC) של עמודה, הערכים NULL ממוינים ראשונים. אם רוצים למיין עמודה בסדר יורד (DESC), הערכים NULL ימוינו אחרונים.
השבתת יצירת אינדקס לערכי NULL
GoogleSQL
כדי להשבית את יצירת האינדקס של ערכי null, מוסיפים את מילת המפתח NULL_FILTERED להגדרת האינדקס. אינדקסים של NULL_FILTERED שימושיים במיוחד לאינדקס של עמודות דלילות, שבהן רוב השורות מכילות ערך של NULL. במקרים כאלה, האינדקס NULL_FILTERED יכול להיות קטן בהרבה ויעיל יותר לתחזוקה מאשר אינדקס רגיל שכולל ערכים של NULL.
הנה הגדרה חלופית של SingersByFirstLastName שלא כוללת אינדקס של ערכי NULL:
CREATE NULL_FILTERED INDEX SingersByFirstLastNameNoNulls
ON Singers(FirstName, LastName);
מילת המפתח NULL_FILTERED חלה על כל העמודות של מפתח האינדקס. אי אפשר לציין סינון של NULL על בסיס כל עמודה.
PostgreSQL
כדי לסנן שורות עם ערכי null בעמודה אחת או יותר עם אינדקס, משתמשים בפרדיקט WHERE COLUMN IS NOT NULL.
אינדקסים עם סינון של ערכי Null שימושיים במיוחד לאינדוקס של עמודות דלילות, שרוב השורות שלהן מכילות ערך NULL. במקרים כאלה, האינדקס עם הסינון של ערכי null יכול להיות קטן בהרבה ויעיל יותר לתחזוקה מאשר אינדקס רגיל שכולל ערכי NULL.
הנה הגדרה חלופית של SingersByFirstLastName שלא כוללת אינדקס של ערכי NULL:
CREATE INDEX SingersByFirstLastNameNoNulls
ON Singers(FirstName, LastName)
WHERE FirstName IS NOT NULL
AND LastName IS NOT NULL;
סינון ערכים של NULL מונע מ-Spanner להשתמש בהם בחלק מהשאילתות. לדוגמה, Spanner לא משתמש באינדקס בשאילתה הזו, כי האינדקס משמיט את כל השורות של Singers שבהן LastName הוא NULL. כתוצאה מכך, שימוש באינדקס ימנע מהשאילתה להחזיר את השורות הנכונות:
GoogleSQL
FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = "John";
PostgreSQL
FROM Singers /*@ FORCE_INDEX = SingersByFirstLastNameNoNulls */
WHERE FirstName = 'John';
כדי לאפשר ל-Spanner להשתמש באינדקס, צריך לשכתב את השאילתה כך שהיא לא תכלול את השורות שגם לא נכללות באינדקס:
GoogleSQL
SELECT FirstName, LastName
FROM Singers@{FORCE_INDEX=SingersByFirstLastNameNoNulls}
WHERE FirstName = 'John' AND LastName IS NOT NULL;
PostgreSQL
SELECT FirstName, LastName
FROM Singers /*@ FORCE_INDEX = SingersByFirstLastNameNoNulls */
WHERE FirstName = 'John' AND LastName IS NOT NULL;
שדות פרוטו של אינדקס
אפשר להשתמש בעמודות שנוצרו כדי ליצור אינדקס לשדות ב-protocol buffers שמאוחסנים בעמודות PROTO, כל עוד השדות שנוצר להם אינדקס הם מסוגי הנתונים הפרימיטיביים או ENUM.
אם מגדירים אינדקס בשדה של הודעת פרוטוקול, אי אפשר לשנות או להסיר את השדה הזה מסכימת ה-proto. מידע נוסף זמין במאמר בנושא עדכונים בסכימות שמכילות אינדקס בשדות פרוטו.
הדוגמה הבאה היא של טבלת Singers עם עמודה של הודעת SingerInfo proto. כדי להגדיר אינדקס בשדה nationality של PROTO, צריך ליצור עמודה וירטואלית מאוחסנת:
GoogleSQL
CREATE PROTO BUNDLE (googlesql.example.SingerInfo, googlesql.example.SingerInfo.Residence);
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
...
SingerInfo googlesql.example.SingerInfo,
SingerNationality STRING(MAX) AS (SingerInfo.nationality) STORED
) PRIMARY KEY (SingerId);
ההגדרה של סוג ה-proto googlesql.example.SingerInfo היא:
GoogleSQL
package googlesql.example;
message SingerInfo {
optional string nationality = 1;
repeated Residence residence = 2;
message Residence {
required int64 start_year = 1;
optional int64 end_year = 2;
optional string city = 3;
optional string country = 4;
}
}
לאחר מכן מגדירים אינדקס בשדה nationality של ה-proto:
GoogleSQL
CREATE INDEX SingersByNationality ON Singers(SingerNationality);
שאילתת ה-SQL הבאה קוראת נתונים באמצעות האינדקס הקודם:
GoogleSQL
SELECT s.SingerId, s.FirstName
FROM Singers AS s
WHERE s.SingerNationality = "English";
הערות:
- משתמשים בהוראת אינדקס כדי לגשת לאינדקסים בשדות של עמודות מאגר אחסון לפרוטוקולים.
- אי אפשר ליצור אינדקס בשדות חוזרים של מאגר אחסון לפרוטוקולים.
עדכונים בסכימות שמכילות אינדקס בשדות פרוטו
אם מגדירים אינדקס בשדה של הודעת פרוטוקול, אי אפשר לשנות או להסיר את השדה הזה מסכימת ה-proto. הסיבה לכך היא שאחרי שמגדירים את האינדקס, מתבצעת בדיקת סוג בכל פעם שהסכימה מתעדכנת. מערכת Spanner מתעדת את פרטי הסוג של כל השדות בנתיב שמשמשים בהגדרת האינדקס.
מדדים ייחודיים
אפשר להצהיר על אינדקסים UNIQUE. אינדקסים של UNIQUE מוסיפים אילוץ לנתונים שמתווספים לאינדקס, שאוסר על כניסות כפולות למפתח אינדקס נתון.
האילוץ הזה נאכף על ידי Spanner בזמן אישור העסקה.
באופן ספציפי, כל עסקה שתגרום לכך שיהיו כמה רשומות אינדקס לאותו מפתח תיכשל.
אם טבלה מכילה נתונים שאינם UNIQUE, ניסיון ליצור בה אינדקס UNIQUE ייכשל.
הערה לגבי אינדקסים מסוג UNIQUE NULL_FILTERED
UNIQUE NULL_FILTERED אינדקס לא אוכף ייחודיות של מפתח האינדקס אם לפחות אחד מחלקי המפתח של האינדקס הוא NULL.
לדוגמה, נניח שיצרתם את הטבלה והאינדקס הבאים:
GoogleSQL
CREATE TABLE ExampleTable (
Key1 INT64 NOT NULL,
Key2 INT64,
Key3 INT64,
Col1 INT64,
) PRIMARY KEY (Key1, Key2, Key3);
CREATE UNIQUE NULL_FILTERED INDEX ExampleIndex ON ExampleTable (Key1, Key2, Col1);
PostgreSQL
CREATE TABLE ExampleTable (
Key1 BIGINT NOT NULL,
Key2 BIGINT,
Key3 BIGINT,
Col1 BIGINT,
PRIMARY KEY (Key1, Key2, Key3)
);
CREATE UNIQUE INDEX ExampleIndex ON ExampleTable (Key1, Key2, Col1)
WHERE Key1 IS NOT NULL
AND Key2 IS NOT NULL
AND Col1 IS NOT NULL;
בשתי השורות הבאות ב-ExampleTable יש את אותם ערכים למפתחות האינדקס המשני Key1, Key2 ו-Col1:
1, NULL, 1, 1
1, NULL, 2, 1
מכיוון ש-Key2 הוא NULL והאינדקס מסונן לפי ערך null, השורות לא יופיעו באינדקס ExampleIndex. הן לא מוכנסות לאינדקס, ולכן האינדקס לא ידחה אותן בגלל הפרת הייחודיות ב-(Key1, Key2,
Col1).
אם רוצים שהאינדקס יאכוף את הייחודיות של ערכי הטופל (Key1, Key2, Col1), צריך להוסיף את ההערה NOT NULL ל-Key2 בהגדרת הטבלה או ליצור את האינדקס בלי לסנן ערכי null.
הסרת אינדקס
משתמשים בהצהרה DROP INDEX כדי להסיר אינדקס משני מהסכימה.
כדי להסיר את האינדקס בשם SingersByFirstLastName:
DROP INDEX SingersByFirstLastName;
יצירת אינדקס לסריקה מהירה יותר
כש-Spanner צריך לבצע סריקת טבלה (במקום חיפוש באינדקס) כדי לאחזר ערכים מעמודה אחת או יותר, אפשר לקבל תוצאות מהירות יותר אם קיים אינדקס לעמודות האלה, ובסדר שצוין בשאילתה. אם אתם מבצעים לעיתים קרובות שאילתות שדורשות סריקות, כדאי ליצור אינדקסים משניים כדי שהסריקות האלה יתבצעו בצורה יעילה יותר.
במיוחד אם אתם צריכים ש-Spanner יסרוק לעיתים קרובות את המפתח הראשי של טבלה או אינדקס אחר בסדר הפוך, אתם יכולים לשפר את היעילות שלו באמצעות אינדקס משני שמגדיר את הסדר שנבחר באופן מפורש.
לדוגמה, השאילתה הבאה תמיד מחזירה תוצאה מהירה, למרות ש-Spanner צריך לסרוק את Songs כדי למצוא את הערך הנמוך ביותר של SongId:
SELECT SongId FROM Songs LIMIT 1;
SongId הוא המפתח הראשי של הטבלה, שמאוחסן (כמו כל המפתחות הראשיים) בסדר עולה. מערכת Spanner יכולה לסרוק את האינדקס של המפתח הזה ולמצוא את התוצאה הראשונה במהירות.
אבל בלי עזרה של אינדקס משני, השאילתה הבאה לא תחזיר תוצאות כל כך מהר, במיוחד אם Songs מכיל הרבה נתונים:
SELECT SongId FROM Songs ORDER BY SongId DESC LIMIT 1;
למרות ש-SongId הוא המפתח הראשי של הטבלה, ל-Spanner אין דרך לאחזר את הערך הכי גבוה בעמודה בלי לבצע סריקה מלאה של הטבלה.
הוספת האינדקס הבא תאפשר לשאילתה הזו להחזיר תוצאות מהר יותר:
CREATE INDEX SongIdDesc On Songs(SongId DESC);
אם האינדקס הזה קיים, Spanner ישתמש בו כדי להחזיר תוצאה לשאילתה השנייה הרבה יותר מהר.
המאמרים הבאים
- מידע על שיטות מומלצות לשימוש ב-SQL ב-Spanner
- הסבר על תוכניות להפעלת שאילתות ב-Spanner
- איך פותרים בעיות של ירידה בביצועים בשאילתות SQL