בדף הזה מוסבר על דרישות הסכמה של Spanner, על אופן השימוש בסכמה ליצירת קשרים היררכיים ועל תכונות הסכמה. בנוסף, נוספו טבלאות משולבות, שיכולות לשפר את ביצועי השאילתות כשמבצעים שאילתות בטבלאות עם קשר הורה-צאצא.
סכימה היא מרחב שמות שמכיל אובייקטים של מסד נתונים, כמו טבלאות, תצוגות, אינדקסים ופונקציות. אתם משתמשים בסכימות כדי לארגן אובייקטים, להחיל הרשאות פרטניות של בקרת גישה ולמנוע התנגשויות בשמות. צריך להגדיר סכימה לכל מסד נתונים ב-Spanner.
אפשר גם לפלח עוד יותר את השורות בטבלת מסד הנתונים ולשמור אותן באזורים גיאוגרפיים שונים. מידע נוסף זמין במאמר בנושא סקירה כללית על חלוקה גיאוגרפית למחיצות.
נתונים עם הקלדה חזקה
הנתונים ב-Spanner הם בעלי הקלדה חזקה. סוגי הנתונים כוללים סוגים סקלריים ומורכבים, שמתוארים במאמרים סוגי נתונים ב-GoogleSQL וסוגי נתונים ב-PostgreSQL.
בחירת מפתח ראשי
מסדי נתונים ב-Spanner יכולים להכיל טבלה אחת או יותר. הטבלאות בנויות משורות ועמודות. סכימת הטבלה מגדירה עמודה אחת או יותר בטבלה כמפתח ראשי של הטבלה, שמזהה באופן ייחודי כל שורה. מפתחות ראשיים תמיד עוברים אינדוקס כדי לאפשר חיפוש מהיר של שורות. אם רוצים לעדכן או למחוק שורות קיימות בטבלה, הטבלה צריכה לכלול מפתח ראשי. בטבלה ללא עמודות של מפתח ראשי יכולה להיות רק שורה אחת. רק במסדי נתונים של ניב GoogleSQL יכולות להיות טבלאות ללא מפתח ראשי.
לרוב, לאפליקציה כבר יש שדה שמתאים באופן טבעי לשימוש כמפתח ראשי. לדוגמה, בטבלה Customers יכול להיות שדה CustomerId שסופק על ידי האפליקציה ומתאים לשמש כמפתח ראשי. במקרים אחרים, יכול להיות שתצטרכו ליצור מפתח ראשי כשמוסיפים את השורה. בדרך כלל זה יהיה ערך של מספר שלם ייחודי ללא משמעות עסקית (מפתח ראשי חלופי).
בכל המקרים, חשוב להיזהר ולא ליצור נקודות חמות בבחירה של המפתח הראשי. לדוגמה, אם מוסיפים רשומות עם מספר שלם שעולה באופן מונוטוני כמפתח, הרשומות תמיד יתווספו לסוף מרחב המפתחות. זה לא רצוי כי Spanner מחלק את הנתונים בין השרתים לפי טווחי מפתחות, מה שאומר שההוספות יופנו לשרת יחיד וייווצר אזור פעיל. יש טכניקות שמאפשרות לפזר את העומס על פני כמה שרתים ולהימנע מנקודות חמות:
- מבצעים גיבוב (hash) של המפתח ומאחסנים אותו בעמודה. שימוש בעמודת הגיבוב (או בעמודת הגיבוב ובעמודות המפתח הייחודי ביחד) כמפתח הראשי.
- החלפת הסדר של העמודות במפתח הראשי.
- שימוש במזהה ייחודי אוניברסלי (UUID). מומלץ להשתמש בגרסה 4 של UUID, כי היא משתמשת בערכים אקראיים בביטים הגבוהים. אל תשתמשו באלגוריתם UUID (כמו UUID מגרסה 1) שמאחסן את חותמת הזמן בביטים הגבוהים.
- Bit-reverse sequential values.
קשרים בין טבלאות מסוג הורה-צאצא
יש שתי דרכים להגדיר קשרים של הורה/צאצא ב-Spanner: שילוב טבלאות ומפתחות זרים.
שילוב טבלאות ב-Spanner הוא בחירה טובה להרבה קשרים של הורה-צאצא. בשיטת השילוב, מערכת Spanner ממקמת פיזית את שורות הצאצא לצד שורות האב באחסון. מיקום משותף יכול לשפר משמעותית את הביצועים. לדוגמה, אם יש לכם טבלה Customers וטבלה Invoices, והאפליקציה שלכם מאחזרת לעיתים קרובות את כל החשבוניות של לקוח, אתם יכולים להגדיר את Invoices כטבלת צאצא משולבת של Customers. במקרה כזה, אתם מגדירים קשר של מיקום נתונים בין שתי טבלאות נפרדות. אתם אומרים ל-Spanner לאחסן שורה אחת או יותר של Invoices עם שורה אחת של Customers. הקשר הזה של הורה-צאצא נאכף כשמשלבים אותו עם פסקה INTERLEAVE IN PARENT. בטבלאות צאצא INTERLEAVE IN יש מאפיינים משותפים של שילוב שורות פיזי, אבל Spanner לא אוכף את השלמות ההפניה בין הורה לצאצא.
כדי לשייך טבלת צאצא לטבלת אב, משתמשים ב-DDL שמצהיר על טבלת הצאצא כטבלה משולבת בטבלת האב, וכוללים את המפתח הראשי של טבלת האב כחלק הראשון של המפתח הראשי המורכב של טבלת הצאצא.
מידע נוסף על שילוב בין טבלאות זמין במאמר יצירת טבלאות משולבות.
מפתחות זרים הם פתרון כללי יותר של יחסי הורה-צאצא, והם מתאימים לתרחישי שימוש נוספים. הם לא מוגבלים לעמודות של מפתח ראשי, ובטבלאות יכולים להיות כמה קשרים של מפתח זר, גם כהורה בחלק מהקשרים וגם כצאצא באחרים. עם זאת, קשר של מפתח זר לא מרמז על מיקום משותף של הטבלאות בשכבת האחסון.
Google ממליצה לבחור לייצג את קשרי ההורה-צאצא או כטבלאות משולבות או כמפתחות זרים, אבל לא את שניהם. מידע נוסף על מפתחות זרים והשוואה שלהם לטבלאות משולבות מופיע במאמר סקירה כללית על מפתחות זרים.
מפתחות ראשיים בטבלאות משולבות
כדי להשתמש בטכניקת השילוב, לכל טבלה צריך להיות מפתח ראשי. אם מגדירים טבלה כטבלת צאצא משולבת של טבלה אחרת, לטבלה צריכה להיות מפתח ראשי מורכב שכולל את כל הרכיבים של המפתח הראשי של טבלת האב, באותו סדר, ובדרך כלל גם עמודה אחת או יותר של טבלת הצאצא.
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.
אובייקטים של מסדי נתונים שתומכים בסכימות עם שמות:
TABLECREATEINTERLEAVE IN [PARENT]FOREIGN KEYSYNONYM
VIEWINDEXSEARCH INDEXFOREIGN KEYSEQUENCE
מידע נוסף על שימוש בסכימות עם שמות זמין במאמר ניהול סכימות עם שמות.
שימוש בבקרת גישה פרטנית עם סכימות בעלות שם
סכימות עם שם מאפשרות להעניק גישה ברמת הסכימה לכל אובייקט בסכימה. ההגדרה הזו חלה על אובייקטים של סכימה שקיימים בזמן שמעניקים גישה. צריך לתת גישה לאובייקטים שנוספים בשלב מאוחר יותר.
בקרת גישה פרטנית מגבילה את הגישה לקבוצות שלמות של אובייקטים במסד הנתונים, כמו טבלאות, עמודות ושורות בטבלה.
מידע נוסף זמין במאמר בנושא הענקת הרשאות בקרת גישה מפורטות לסכימות עם שמות.
דוגמאות לסכימה
בדוגמאות לסכימות שבקטע הזה מוצגות טבלאות הורה וצאצא עם ובלי שילוב, ומוצגים הפריסות הפיזיות המתאימות של הנתונים.
יצירת טבלת הורים
נניח שאתם יוצרים אפליקציה למוזיקה ואתם צריכים טבלה שמאחסנת שורות של נתוני זמרים:
שימו לב שהטבלה מכילה עמודה אחת של מפתח ראשי, 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היא טבלה ברמה הבסיסית (root) של היררכיית מסד הנתונים (כי היא לא מוגדרת כטבלת צאצא משולבת של טבלה אחרת). - במסדי נתונים של 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 משתמש בקבוצות של אזורים כדי לאחסן את העמודות שצוינו בנפרד. אם מציינים, העמודות מקבלות בירושה את מדיניות האחסון בשכבות מהטבלה או מקבוצת המיקום שמוגדרת כברירת מחדל. לאחר מכן, משתמשים
CREATE TABLEבפקודת DDL כדי להגדיר קבוצת מיקום לעמודות שצוינו או משתמשיםALTER TABLEבפקודת DDL כדי לשנות את קבוצת המיקום שבה נעשה שימוש בעמודה של טבלה. הצהרת ה-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 משלו:
| SingerId | שם פרטי | שם משפחה |
|---|---|---|
| 1 | Marc | Richards |
| 2 | Catalina | סמית' |
| SingerId | שם פרטי | שם משפחה |
|---|---|---|
| 1 | אליס | Trentor |
| 2 | גבריאל | Wright |
| SingerId | שם פרטי | שם משפחה |
|---|---|---|
| 1 | עומר | Martinez |
| 2 | חנה | האריס |
ריבוי דיירים שמנוהל על ידי סכימה
דרך נוספת לתכנן ריבוי דיירים ב-Spanner היא להשתמש בטבלה אחת במסד נתונים אחד לכל הלקוחות, ולהשתמש בערך שונה של מפתח ראשי לכל לקוח. לדוגמה, אפשר לכלול CustomerIdעמודת מפתח בטבלאות. אם הופכים את CustomerId לעמודת המפתח הראשונה, אז הנתונים של כל לקוח יהיו מקומיים. לאחר מכן, מערכת Spanner יכולה להשתמש ביעילות בפיצולים של מסד הנתונים כדי לשפר את הביצועים בהתבסס על גודל הנתונים ודפוסי הטעינה. בדוגמה הבאה, יש טבלת Singers אחת לכל הלקוחות:
| CustomerId | SingerId | שם פרטי | שם משפחה |
|---|---|---|---|
| 1 | 1 | Marc | Richards |
| 1 | 2 | Catalina | סמית' |
| 2 | 1 | אליס | Trentor |
| 2 | 2 | גבריאל | Wright |
| 3 | 1 | עומר | Martinez |
| 3 | 2 | חנה | האריס |
אם אתם חייבים להשתמש במסדי נתונים נפרדים לכל דייר, יש מגבלות שחשוב להכיר:
- יש מגבלות על מספר מסדי הנתונים לכל מופע ועל מספר הטבלאות והאינדקסים לכל מסד נתונים. יכול להיות שלא תהיה אפשרות להשתמש במסדי נתונים או בטבלאות נפרדים, בהתאם למספר הלקוחות.
- יכול לקחת הרבה זמן להוסיף טבלאות חדשות ואינדקסים לא משולבים. יכול להיות שלא תוכלו להשיג את הביצועים הרצויים אם עיצוב הסכימה שלכם תלוי בהוספה של טבלאות ואינדקסים חדשים.
אם רוצים ליצור מסדי נתונים נפרדים, כדאי לפזר את הטבלאות בין מסדי הנתונים כך שבכל מסד נתונים יהיה מספר נמוך של שינויים בסכימה בכל שבוע.
אם יוצרים טבלאות ואינדקסים נפרדים לכל לקוח של האפליקציה, לא כדאי לשים את כל הטבלאות והאינדקסים באותו מסד נתונים. במקום זאת, כדאי לפצל אותם למסדי נתונים רבים כדי לצמצם את בעיות הביצועים שקשורות ליצירה של מספר גדול של אינדקסים.
מידע נוסף על דפוסי ניהול נתונים אחרים ועל עיצוב אפליקציות לריבוי דיירים זמין במאמר הטמעה של ריבוי דיירים ב-Spanner.