בדף הזה מוסבר על הדרישות של סכימת Spanner, איך להשתמש בסכימה כדי ליצור קשרים היררכיים ועל תכונות הסכימה. בנוסף, נוספו טבלאות משולבות, שיכולות לשפר את ביצועי השאילתות כשמבצעים שאילתות בטבלאות עם קשר הורה-צאצא.
סכימה היא מרחב שמות שמכיל אובייקטים של מסד נתונים, כמו טבלאות, תצוגות, אינדקסים ופונקציות. אתם משתמשים בסכימות כדי לארגן אובייקטים, להחיל הרשאות פרטניות של בקרת גישה ולמנוע התנגשויות בשמות. צריך להגדיר סכימה לכל מסד נתונים ב-Spanner.
אפשר גם לפלח עוד יותר את השורות בטבלת מסד הנתונים ולשמור אותן באזורים גיאוגרפיים שונים. מידע נוסף זמין במאמר בנושא סקירה כללית על חלוקה גיאוגרפית למחיצות.
נתונים עם הקלדה חזקה
הנתונים ב-Spanner הם בעלי הקלדה חזקה. סוגי הנתונים כוללים סוגים סקלריים ומורכבים, שמתוארים במאמרים סוגי נתונים ב-GoogleSQL וסוגי נתונים ב-PostgreSQL.
בחירת מפתח ראשי
מסדי נתונים ב-Spanner יכולים להכיל טבלה אחת או יותר. הטבלאות בנויות משורות ועמודות. סכימת הטבלה מגדירה עמודה אחת או יותר בטבלה כמפתח ראשי של הטבלה, שמזהה באופן ייחודי כל שורה. מפתחות ראשיים תמיד עוברים אינדוקס כדי לאפשר חיפוש מהיר של שורות. אם רוצים לעדכן או למחוק שורות קיימות בטבלה, הטבלה צריכה להכיל מפתח ראשי. בטבלה ללא עמודות של מפתח ראשי יכולה להיות רק שורה אחת. רק במסדי נתונים עם ניב GoogleSQL יכולות להיות טבלאות ללא מפתח ראשי.
לרוב, לאפליקציה כבר יש שדה שמתאים באופן טבעי לשימוש כמפתח ראשי. לדוגמה, בטבלה Customers יכול להיות שדה CustomerId שסופק על ידי האפליקציה ומתאים לשמש כמפתח ראשי. במקרים אחרים, יכול להיות שתצטרכו ליצור מפתח ראשי כשמוסיפים את השורה. בדרך כלל זה יהיה ערך של מספר שלם ייחודי ללא משמעות עסקית (מפתח ראשי חלופי).
בכל המקרים, חשוב להיזהר ולא ליצור נקודות חמות בבחירה של המפתח הראשי. לדוגמה, אם מוסיפים רשומות עם מספר שלם שעולה באופן מונוטוני כמפתח, הרשומות תמיד יתווספו לסוף מרחב המפתחות. זה לא רצוי כי Spanner מחלק את הנתונים בין השרתים לפי טווחי מפתחות, מה שאומר שההוספות יופנו לשרת יחיד, וייווצר hotspot. יש טכניקות שמאפשרות לפזר את העומס על פני כמה שרתים ולהימנע מנקודות חמות:
- מבצעים גיבוב של המפתח ומאחסנים אותו בעמודה. שימוש בעמודת הגיבוב (או בעמודת הגיבוב ובעמודות המפתח הייחודי ביחד) כמפתח הראשי.
- החלפת הסדר של העמודות במפתח הראשי.
- שימוש במזהה ייחודי אוניברסלי (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 ממליצה לבחור לייצג קשרים של הורה-צאצא או כטבלאות משולבות או כמפתחות זרים, אבל לא את שתי האפשרויות. מידע נוסף על מפתחות זרים והשוואה שלהם לטבלאות משולבות זמין במאמר סקירה כללית על מפתחות זרים.
מפתחות ראשיים בטבלאות משולבות
כדי להשתמש בטכניקת ה-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.
אובייקטים של מסד נתונים שתומכים בסכימות עם שמות:
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היא טבלה בבסיס ההיררכיה של מסד הנתונים (כי היא לא מוגדרת כטבלת צאצא משולבת של טבלה אחרת). - במסדי נתונים של 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כדי להגדיר קבוצת מיקום לעמודות שצוינו או משתמשים בהצהרת ה-DDLALTER 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 משלו:
| SingerId | FirstName | LastName |
|---|---|---|
| 1 | Marc | Richards |
| 2 | Catalina | Smith |
| SingerId | FirstName | LastName |
|---|---|---|
| 1 | Alice | Trentor |
| 2 | גבריאל | Wright |
| SingerId | FirstName | LastName |
|---|---|---|
| 1 | בנג'מין | Martinez |
| 2 | חנה | האריס |
ריבוי דיירים שמנוהל על ידי סכימה
דרך נוספת לתכנן את Spanner לשימוש של כמה דיירים היא להשתמש בטבלה אחת במסד נתונים אחד לכל הלקוחות, ולהשתמש בערך שונה של מפתח ראשי לכל לקוח. לדוגמה, אפשר לכלול CustomerIdעמודת מפתח בטבלאות. אם תגדירו את CustomerId כעמודת המפתח הראשונה, הנתונים של כל לקוח יהיו מקומיים. לאחר מכן, Spanner יכול להשתמש ביעילות בפיצולים של מסד הנתונים כדי לשפר את הביצועים בהתבסס על גודל הנתונים ודפוסי הטעינה. בדוגמה הבאה,
יש טבלת Singers אחת לכל הלקוחות:
| CustomerId | SingerId | FirstName | LastName |
|---|---|---|---|
| 1 | 1 | Marc | Richards |
| 1 | 2 | Catalina | Smith |
| 2 | 1 | Alice | Trentor |
| 2 | 2 | גבריאל | Wright |
| 3 | 1 | בנג'מין | Martinez |
| 3 | 2 | חנה | האריס |
אם אתם חייבים להשתמש במסדי נתונים נפרדים לכל דייר, חשוב שתכירו את המגבלות הבאות:
- יש מגבלות על מספר מסדי הנתונים לכל מופע ועל מספר הטבלאות והאינדקסים לכל מסד נתונים. בהתאם למספר הלקוחות, יכול להיות שלא תהיה אפשרות להשתמש במסדי נתונים או בטבלאות נפרדים.
- יכול לקחת הרבה זמן להוסיף טבלאות חדשות ואינדקסים לא משולבים. יכול להיות שלא תוכלו להשיג את הביצועים הרצויים אם עיצוב הסכימה שלכם תלוי בהוספה של טבלאות ואינדקסים חדשים.
אם רוצים ליצור מסדי נתונים נפרדים, כדאי לפזר את הטבלאות בין מסדי הנתונים כך שבכל מסד נתונים יהיה מספר נמוך של שינויים בסכימה בכל שבוע.
אם יוצרים טבלאות ואינדקסים נפרדים לכל לקוח של האפליקציה, לא כדאי לשים את כל הטבלאות והאינדקסים באותו מסד נתונים. במקום זאת, כדאי לפצל אותם למסדי נתונים רבים כדי לצמצם את בעיות הביצועים שקשורות ליצירה של מספר גדול של אינדקסים.
מידע נוסף על דפוסי ניהול נתונים אחרים ועל עיצוב אפליקציות לריבוי דיירים זמין במאמר הטמעה של ריבוי דיירים ב-Spanner.