סקירה כללית של סכימות

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

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

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

נתונים עם הקלדה חזקה

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

בחירת מפתח ראשי

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

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

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

קשרים בין טבלאות מסוג הורה-צאצא

יש שתי דרכים להגדיר קשרים של הורה-צאצא ב-Spanner: שילוב טבלאות ומפתחות זרים.

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

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

מידע נוסף על שילוב בין טבלאות זמין במאמר יצירת טבלאות משולבות.

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

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

מפתחות ראשיים בטבלאות משולבות

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

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

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

פיצול מסד נתונים

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

פיצול על סמך עומס

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

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

סכימות בעלות שם

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

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

סכימות עם שמות ב-Spanner מאפשרות לכם להשתמש בשמות מוגדרים במלואם (FQN) כדי לשלוח שאילתות לגבי נתונים. שמות FQN מאפשרים לכם לשלב את שם הסכימה ואת שם האובייקט כדי לזהות אובייקטים במסד הנתונים. לדוגמה, אפשר ליצור סכימה בשם warehouse ליחידה העסקית של המחסן. הטבלאות שמשתמשות בסכימה הזו יכולות לכלול את product, order ו-customer information. אפשר גם ליצור סכימה בשם fulfillment ליחידה העסקית של מרכז הלוגיסטיקה. יכול להיות שיהיו בסכימה הזו גם טבלאות בשמות product, order ו-customer information. בדוגמה הראשונה, ה-FQN הוא warehouse.product ובדוגמה השנייה, ה-FQN הוא fulfillment.product. כך נמנע בלבול במצבים שבהם לכמה אובייקטים יש את אותו שם.

ב-CREATE SCHEMA DDL, לאובייקטים של טבלאות מוקצה גם FQN, לדוגמה, sales.customers, וגם שם קצר, לדוגמה, sales.

אובייקטים של מסד נתונים שתומכים בסכימות עם שמות:

  • TABLE
    • CREATE
    • INTERLEAVE IN [PARENT]
    • FOREIGN KEY
    • SYNONYM
  • VIEW
  • INDEX
  • SEARCH INDEX
  • FOREIGN KEY
  • SEQUENCE

מידע נוסף על שימוש בסכימות עם שמות זמין במאמר ניהול סכימות עם שמות.

שימוש בבקרת גישה פרטנית עם סכימות בעלות שם

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

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

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

דוגמאות לסכימה

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

יצירת טבלת הורים

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

טבלת זמרים עם חמש שורות וארבע עמודות

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

אפשר להגדיר את הטבלה באמצעות ה-DDL הבא:

GoogleSQL

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

PostgreSQL

CREATE TABLE singers (
singer_id   BIGINT PRIMARY KEY,
first_name  VARCHAR(1024),
last_name   VARCHAR(1024),
singer_info BYTEA
);

הערות לגבי סכימת הדוגמה:

  • Singers היא טבלה בבסיס ההיררכיה של מסד הנתונים (כי היא לא מוגדרת כטבלת צאצא משולבת של טבלה אחרת).
  • במסדי נתונים של GoogleSQL, עמודות של מפתח ראשי בדרך כלל מסומנות ב-NOT NULL (אבל אפשר להשמיט את הסימון הזה אם רוצים לאפשר ערכים של NULL בעמודות של מפתח. מידע נוסף זמין במאמר בנושא עמודות מפתח.
  • עמודות שלא נכללות במפתח הראשי נקראות עמודות שאינן מפתח, ויכול להיות שיש להן הערה אופציונלית NOT NULL.
  • עמודות שמשתמשות בסוג STRING או BYTES ב-GoogleSQL צריכות להיות מוגדרות עם אורך, שמייצג את המספר המקסימלי של תווים ביוניקוד שאפשר לאחסן בשדה. אפשר לציין את האורך של סוגי הנתונים varchar ו-character varying ב-PostgreSQL. למידע נוסף, אפשר לעיין בסוגי נתונים סקלריים במסדי נתונים של ניב GoogleSQL ובסוגי נתונים של PostgreSQL במסדי נתונים של ניב PostgreSQL.

איך נראה הפריסה הפיזית של השורות בטבלה Singers? בתרשים הבא מוצגות שורות של הטבלה Singers שמאוחסנות לפי מפתח ראשי ("Singers(1)", ואז "Singers(2)", כאשר המספר בסוגריים הוא ערך המפתח הראשי).

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

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

יצירת טבלאות ראשיות ומשניות

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

טבלת אלבומים עם חמש שורות ושלוש עמודות

שימו לב שהמפתח הראשי של Albums מורכב משתי עמודות: SingerId ו-AlbumId, כדי לשייך כל אלבום לזמר שלו. סכימת הדוגמה הבאה מגדירה את הטבלאות Albums ו-Singers ברמת הבסיס של היררכיית מסד הנתונים, ולכן הן טבלאות אחיות.

-- Schema hierarchy:
-- + Singers (sibling table of Albums)
-- + Albums (sibling table of Singers)

GoogleSQL

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

CREATE TABLE Albums (
SingerId     INT64 NOT NULL,
AlbumId      INT64 NOT NULL,
AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId);

PostgreSQL

CREATE TABLE singers (
singer_id   BIGINT PRIMARY KEY,
first_name  VARCHAR(1024),
last_name   VARCHAR(1024),
singer_info BYTEA
);

CREATE TABLE albums (
singer_id     BIGINT,
album_id      BIGINT,
album_title   VARCHAR,
PRIMARY KEY (singer_id, album_id)
);

הפריסה הפיזית של השורות בטבלאות Singers ו-Albums נראית כמו בתרשים הבא, כשקודם מאוחסנות השורות של טבלת Albums לפי מפתח ראשי רציף, ואז השורות של טבלת Singers לפי מפתח ראשי רציף:

הפריסה הפיזית של השורות

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

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

יצירת טבלאות משולבות

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

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

במהלך התכנון של אפליקציית המוזיקה, נניח שאתם מבינים שהאפליקציה צריכה לגשת לעיתים קרובות לשורות מהטבלה Albums כשהיא ניגשת לשורה Singers. לדוגמה, כשניגשים לשורה Singers(1), צריך גם לגשת לשורות Albums(1, 1) ו-Albums(1, 2). במקרה כזה, Singers ו-Albums צריכים להיות קשורים קשר הדוק למיקום הנתונים. כדי להצהיר על קשר המקומיות הזה של הנתונים, יוצרים את Albums כטבלת צאצא משולבת של Singers.

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)

השורה המודגשת בסכימה הבאה מראה איך ליצור את Albums כטבלה משולבת של Singers.

GoogleSQL

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

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;

PostgreSQL

CREATE TABLE singers (
 singer_id   BIGINT PRIMARY KEY,
 first_name  VARCHAR(1024),
 last_name   VARCHAR(1024),
 singer_info BYTEA
 );

CREATE TABLE albums (
 singer_id     BIGINT,
 album_id      BIGINT,
 album_title   VARCHAR,
 PRIMARY KEY (singer_id, album_id)
 )
 INTERLEAVE IN PARENT singers ON DELETE CASCADE;

הערות לגבי הסכימה הזו:

  • SingerId, שהוא החלק הראשון של המפתח הראשי של טבלת הצאצא Albums, הוא גם המפתח הראשי של טבלת האב Singers.
  • ההערה ON DELETE CASCADE מציינת שכאשר שורה מהטבלה הראשית נמחקת, גם שורות הצאצא שלה נמחקות באופן אוטומטי. אם בטבלת צאצאים אין את ההערה הזו, או שההערה היא ON DELETE NO ACTION, צריך למחוק את שורות הצאצאים לפני שמוחקים את שורת האב.
  • השורות המשלבות מסודרות קודם לפי השורות של טבלת ההורה, ואז לפי השורות הסמוכות של טבלת הצאצא שחולקות את המפתח הראשי של טבלת ההורה. לדוגמה, 'Singers(1)‎', ואז 'Albums(1, 1)‎', ואז 'Albums(1, 2)‎'.
  • אם מסד הנתונים הזה יפוצל, הקשר של מיקום הנתונים של כל זמר ונתוני האלבום שלו יישמר, בתנאי שהגודל של שורה Singers וכל השורות Albums שלה יישאר מתחת למגבלת הגודל של הפיצול, ושלא יהיה אזור פעיל באף אחת מהשורות Albums האלה.
  • צריך להוסיף את שורת ההורה לפני שמוסיפים שורות של ילדים. שורת ההורה יכולה כבר להיות קיימת במסד הנתונים או שאפשר להוסיף אותה לפני הוספת שורות הצאצא באותה טרנזקציה.

שורות של אלבומים משולבות בין שורות של זמרים

נניח שאתם רוצים ליצור מודל של Projects ושל Resources כטבלאות משולבות. במקרים מסוימים כדאי להשתמש ב-INTERLEAVE IN – האפשרות לא לדרוש את קיום השורה Projects כדי שהישויות שמתחתיה יתקיימו (לדוגמה, פרויקט נמחק, אבל צריך לנקות את המשאבים שלו לפני המחיקה).

GoogleSQL

CREATE TABLE Projects (
  ProjectId   INT64 NOT NULL,
  ProjectName STRING(1024),
) PRIMARY KEY (ProjectId);

CREATE TABLE Resources (
  ProjectId    INT64 NOT NULL,
  ResourceId   INT64 NOT NULL,
  ResourceName STRING(1024),
) PRIMARY KEY (ProjectId, ResourceId),
  INTERLEAVE IN Projects;

PostgreSQL

CREATE TABLE Projects (
  ProjectId   BIGINT PRIMARY KEY,
  ProjectName VARCHAR(1024),
);

CREATE TABLE Resources (
  ProjectId    BIGINT,
  ResourceId   BIGINT,
  ResourceName VARCHAR(1024),
  PRIMARY KEY (ProjectId, ResourceId)
) INTERLEAVE IN Projects;

שימו לב שבדוגמה הזו אנחנו משתמשים בסעיף INTERLEAVE IN Projects ולא בסעיף INTERLEAVE IN PARENT Projects. המשמעות היא שלא נאכוף את הקשר בין פרויקטים למשאבים.

בדוגמה הזו, השורות Resources(1, 10) ו-Resources(1, 20) יכולות להיות קיימות במסד הנתונים גם אם השורה Projects(1) לא קיימת. אפשר למחוק את Projects(1) גם אם Resources(1, 10) ו-Resources(1, 20) עדיין קיימים, והמחיקה לא משפיעה על השורות האלה של Resources.

יצירת היררכיה של טבלאות משולבות

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

טבלת שירים עם שש שורות וארבע עמודות

ל-Songs חייב להיות מפתח ראשי שכולל את כל המפתחות הראשיים של הטבלאות שנמצאות ברמה גבוהה יותר בהיררכיה, כלומר SingerId ו-AlbumId.

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
--     + Songs (interleaved table, child table of Albums)

GoogleSQL

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

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;

PostgreSQL

CREATE TABLE singers (
 singer_id   BIGINT PRIMARY KEY,
 first_name  VARCHAR(1024),
 last_name   VARCHAR(1024),
 singer_info BYTEA
 );

CREATE TABLE albums (
 singer_id     BIGINT,
 album_id      BIGINT,
 album_title   VARCHAR,
 PRIMARY KEY (singer_id, album_id)
 )
 INTERLEAVE IN PARENT singers ON DELETE CASCADE;

CREATE TABLE songs (
 singer_id     BIGINT,
 album_id      BIGINT,
 track_id      BIGINT,
 song_name     VARCHAR,
 PRIMARY KEY (singer_id, album_id, track_id)
 )
 INTERLEAVE IN PARENT albums ON DELETE CASCADE;

הדיאגרמה הבאה מייצגת מבט פיזי על שורות שזירה.

השירים משולבים באלבומים, שמשולבים בין הזמרים

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

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

צירופים עם טבלאות משולבות

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

GoogleSQL

SELECT s.FirstName, a.AlbumTitle
FROM Singers AS s JOIN Albums AS a ON s.SingerId = a.SingerId;

PostgreSQL

SELECT s.first_name, a.album_title
FROM singers AS s JOIN albums AS a ON s.singer_id = a.singer_id;

קבוצות לפי אזור

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

  • שימוש באחסון בשכבות. אחסון בשכבות הוא תכונה מנוהלת במלואה שמאפשרת לכם לבחור אם לאחסן את הנתונים בכונני SSD או בכונני HDD. כברירת מחדל, בלי להשתמש באחסון מדורג, Spanner מאחסן את כל הנתונים באחסון SSD.

  • אפשר להשתמש בקיבוץ עמודות כדי לאחסן עמודות שצוינו בנפרד מעמודות אחרות. הנתונים בעמודות שצוינו מאוחסנים בנפרד, ולכן קריאת הנתונים מהעמודות האלה מהירה יותר מאשר אם כל הנתונים מקובצים יחד. כדי להשתמש באפשרות של קיבוץ עמודות, צריך ליצור קבוצת מיקומים בלי לציין אפשרויות של אחסון מדורג. ‫Spanner משתמש בקבוצות של אזורים כדי לאחסן את העמודות שצוינו בנפרד. אם מציינים, העמודות מקבלות בירושה את מדיניות האחסון בשכבות מהטבלה או מקבוצת ברירת המחדל של הלוקאליות. אחר כך, משתמשים בהצהרת ה-DDL‏ CREATE TABLEכדי להגדיר קבוצת מיקום לעמודות שצוינו או משתמשים בהצהרת ה-DDL‏ ALTER TABLEכדי לשנות את קבוצת המיקום שבה נעשה שימוש בעמודה של טבלה. הצהרת ה-DDL קובעת את העמודות שמאוחסנות בקבוצת הלוקאליות. לבסוף, תוכלו לקרוא את הנתונים בעמודות האלה בצורה יעילה יותר.

עמודות מרכזיות

בקטע הזה יש הערות על עמודות חשובות.

שינוי מפתחות הטבלה

אי אפשר לשנות את המפתחות של טבלה, ואי אפשר להוסיף עמודת מפתח לטבלה קיימת או להסיר עמודת מפתח מטבלה קיימת.

אחסון ערכי NULL במפתח ראשי

ב-GoogleSQL, אם רוצים לאחסן NULL בעמודה של מפתח ראשי, צריך להשמיט את פסוקית NOT NULL של העמודה בסכימה. (מסדי נתונים בניב PostgreSQL לא תומכים בערכי NULL בעמודת מפתח ראשי).

הנה דוגמה להשמטת הסעיף NOT NULL בעמודה של המפתח הראשי SingerId. שימו לב: מכיוון ש-SingerId הוא המפתח הראשי, יכולה להיות רק שורה אחת שבה מאוחסן SingerId בעמודה הזו.NULL

CREATE TABLE Singers (
  SingerId   INT64 PRIMARY KEY,
  FirstName  STRING(1024),
  LastName   STRING(1024),
);

מאפיין ה-nullable של עמודת המפתח הראשי חייב להיות זהה בהצהרות של טבלת האב וטבלת הבן. בדוגמה הזו, הערך NOT NULL בעמודה Albums.SingerId אסור כי הוא לא מופיע ב-Singers.SingerId.

CREATE TABLE Singers (
  SingerId   INT64 PRIMARY KEY,
  FirstName  STRING(1024),
  LastName   STRING(1024),
);

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;

סוגים אסורים

העמודות הבאות לא יכולות להיות מסוג ARRAY:

  • עמודות המפתח של טבלה.
  • עמודות המפתח של אינדקס.

עיצוב לשימוש של כמה דיירים

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

ריבוי דיירים קלאסי

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

Database 1: Ackworth Records
SingerId FirstName LastName
1MarcRichards
2CatalinaSmith
Database 2: Cama Records
SingerId FirstName LastName
1AliceTrentor
2גבריאלWright
Database 3: Eagan Records
SingerId FirstName LastName
1בנג'מיןMartinez
2חנההאריס

ריבוי דיירים שמנוהל על ידי סכימה

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

מסד נתונים של Spanner לריבוי דיירים
CustomerId SingerId FirstName LastName
11MarcRichards
12CatalinaSmith
21AliceTrentor
22גבריאלWright
31בנג'מיןMartinez
32חנההאריס

אם אתם חייבים להשתמש במסדי נתונים נפרדים לכל דייר, חשוב שתכירו את המגבלות הבאות:

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

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

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

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