結構定義與資料模型

資料模型摘要

Cloud Spanner 資料庫可包含一或多個資料表。資料表看起來像關聯式資料庫資料表,因為這兩者都有資料列、資列欄和值,而且都包含主鍵。Cloud Spanner 中的資料是強類型資料:您必須為每個資料庫定義一個結構定義,該結構定義必須指定各資料表中各資料欄的資料類型。允許的資料類型包括純量和陣列,詳細說明請見資料類型一節。您也可以按照次要索引所述,在資料表上定義一或多個次要索引。

父項/子項資料表關係

您可以在資料庫中定義多個資料表;如要讓 Cloud Spanner 將這些資料表的資料列實體放在同個位置以進行有效擷取,您可以選擇定義資料表間的父項/子項關係。比方說,如果您有 Customers 資料表和 Invoices 資料表,而且應用程式經常擷取特定客戶的所有月結單,則可將 Invoices 定義為 Customers 的子項資料表。這樣做其實是在宣告兩個邏輯上獨立的資料表之間的資料本地性,亦即告訴 Cloud Spanner 將 Invoices 的一或多個資料列和 Customers 資料列儲存在一起。

主鍵

如何告訴 Cloud Spanner 哪些 Invoices 資料列要與哪些 Customers 資料列一起儲存?您可以使用這些資料表的主鍵來完成這個任務。每個資料表都要有一個主鍵,這個主鍵是由該資料表的零個或多個資料欄所組成。若您宣告某個資料表為另一個資料表的子項,則父項資料表的主鍵資料欄必須為子項資料表主鍵的前置字串。這表示如果父項資料表的主鍵是由 N 個資料欄組成,則其每個子項資料表也必須由這 N 個資料欄以相同的順序組成,並具有相同的開頭資料欄。

Cloud Spanner 會依據主鍵值的排序來儲存資料列,並且在共用相同主鍵前置字串的父項資料列之間插入子項資料列。這種在主鍵維度,在父項資料列之間插入子項資料列的行為稱為交錯,因此子項資料表也稱為交錯資料表 (請查看下方建立交錯資料表一節中,有關交錯資料列的插圖)。

簡而言之,Cloud Spanner 可將相關資料表的資料列實體儲存於相同的位置。以下結構定義範例將說明這項實體配置看起來的樣子。

選擇主鍵

主鍵是用來識別資料表中的每個資料列,每個資料列都有唯一的主鍵。如要更新或刪除資料表中的現有資料列,則該資料表的主鍵必須由一或多個資料列組成 (沒有主鍵資料欄的資料表只能擁有一個資料列)。您的應用程式通常已有非常適合當做主鍵使用的欄位。例如,在上述 Customers 資料表範例中,應用程式可能已提供非常適合當主鍵的 CustomerId。而在其他情況下,當您插入資料列 (例如您產生的唯一 INT64 值) 時可能需要產生主鍵。

在所有情況下,您都應該要小心,避免因為選錯主鍵而發生資源使用率不均的情況。舉例來說,如果您插入的記錄是以單調遞增整數做為索引鍵,則一律會在索引鍵空間的尾端插入資料。這不是一個好的做法,因為 Cloud Spanner 會依據索引鍵範圍將資料分配到各伺服器,也就是說,您插入的資料會被引導至單一伺服器,而導致資源使用率不均。您可善用下列技術,將負載分散到多部伺服器上,以避免發生資源使用率不均的情形。

資料庫分割

您可以在最多七層深的資料表之間定義父項/子項階層關係,也就是說,您可以將七個邏輯獨立的資料表中的資料列放在同一個位置。如果資料表中的資料不大,則可能由單一 Cloud Spanner 伺服器來處理資料庫。但是當相關資料表變大,開始達到個別伺服器的資源限制時會發生什麼事?Cloud Spanner 屬於分散式資料庫,因此隨著資料庫增長,Cloud Spanner 會將資料拆成名為「分割」的區塊,而個別分割可獨立移動,並指派給位於不同實體位置的不同伺服器。分割會定義為頂層 (亦即非交錯) 資料表中的某個資料列範圍,其中,資料列會按主鍵排序。此範圍內的起始及結束索引鍵稱為「分割界線」。Cloud Spanner 會自動新增和移除分割界線,進而變更資料庫中的分割數量。

Cloud Spanner 會依據負載來分割資料,做法是在單一分割中偵測到許多索引鍵之間散佈高讀取和寫入負載時,即自動新增分割界線。Cloud Spanner 只能在階層根部資料表 (即未在父項資料表中交錯的資料表) 的資料列之間劃下分割界線,因此您仍可控制資料的分割。此外,交錯資料表的資料列無法從父項資料表的對應資料列分割出來,因為交錯資料表的資料列是按照排列好的主鍵順序,與父項資料表中具有相同主鍵前置字串的資料列一起儲存 (請參閱建立交錯資料表的階層中交錯資料列的插圖)。因此,您可以透過定義的父項/子項資料表關係,以及您為相關資料表的資料列所設定的主鍵值,來控制資料的分割方式

依負載進行分割

做為 Cloud Spanner 依負載進行分割以解決讀取資源使用率不均的範例之一,假設資料庫包含一個資料表,其中有 10 個資料列的讀取頻率遠高於其他資料列,只要該資料表位於資料庫階層的根部 (也就是說,這個資料表不是交錯資料表),Cloud Spanner 就能在這 10 個資料列之間新增分割界線,並由不同的伺服器處理各資料列,而不是讓這些資料列的所有讀取作業耗用單一伺服器的資源。

一般來說,如果您遵循結構定義設計的最佳做法,Cloud Spanner 就能解決非交錯資料表中資料列讀取資源使用率不均的問題,因此讀取總處理量應該每隔幾分鐘就會提升,直到執行個體中的資源飽和,或是無法再新增分割界線為止 (因為您的分割只包含單一資料列及其交錯子項)。

結構定義範例

下列結構定義範例顯示如何建立包含及不包含父項/子項關係的 Cloud Spanner 資料表,並且說明資料的對應實體配置。

建立資料表

假設您正在建立音樂應用程式,您需要一個簡單的資料表來儲存歌手資料的資料列:

具有 5 個資料列和 4 個資料欄的「Singers」(歌手) 資料表。

簡單的「Singers」(歌手) 資料表的資料列邏輯檢視。主鍵資料欄位於粗線的左側。

請注意,該資料表包含一個主鍵資料列「SingerId」,位於粗線的左側。資料表是依據資料列、資料欄和值來整理。

您可透過 Cloud Spanner 結構定義來定義資料表,如下所示:

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

請注意下列結構定義範例的相關事項:

  • Singers 是位於資料庫階層根部的資料表 (因為我們未將此資料表定義為另一個資料表的子項)。
  • 主鍵資料欄通常會有 NOT NULL 註解 (但如果您允許在主鍵資料欄中使用 NULL 值,則可省略此註解;詳情請參閱索引鍵資料欄)。
  • 未包含主鍵中的資料欄稱為非鍵欄,這種資料欄可包含選用的 NOT NULL 註解。
  • 採用 STRINGBYTES 類型的資料欄必須以長度來定義,此長度代表欄位中可儲存的 Unicode 字元上限 (詳情請參閱純量資料類型)。

Singers 資料表中的資料列實體配置看起來會是什麼樣子?下圖顯示依據連續主鍵 (亦即按照主鍵的排序順序) 儲存的 Singers 資料表資料列。主鍵為「Singers(1)」、「Singers(2)」等,以此類推。其中「Singers(1)」代表 Singers 資料表中資料列的鍵值為 1。

按連續索引鍵順序儲存的資料表資料列範例。

「Singers」(歌手) 資料表中資料列的實體配置,其中的分割界線範例會讓不同的伺服器處理分割。

上圖也說明了可能的分割界線。分割界線可以位於 Singers 的任何資料列之間,因為 Singers 位於資料庫階層的根部。此外,圖表也提供鍵值為 Singers(3)Singers(4) 的資料列之間的分割界線範例,將分割後的資料指派給不同的伺服器。這表示當此資料表增長時,即可將 Singers 資料的資料列儲存於不同的位置。

建立多個資料表

假設您現在要將每位歌手的專輯基本資料新增至音樂應用程式:

具有 5 個資料列和 3 個資料欄的「Albums」(專輯) 資料表

「Albums」(專輯) 資料表的資料列邏輯檢視。主鍵資料欄位於粗線的左側。

請注意,Albums 的主鍵是由 SingerIdAlbumId 兩個資料欄組成,以便建立專輯和歌手之間的關聯。下列結構定義範例會在資料庫階層根部定義 AlbumsSingers 資料表,讓這兩個資料表變成同層級資料表:

-- Schema hierarchy:
-- + Singers (sibling table of Albums)
-- + Albums (sibling table of Singers)
CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

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

SingersAlbums 的資料列實體配置看起來下列圖表,其中有由連續主鍵儲存的 Albums 資料表資料列,然後是由連續主鍵儲存的 Singers 資料列:

資料列實體配置:各「Albums」(專輯) 和「Singers」(歌手) 資料列均按鍵值儲存

「Singers」(歌手) 和「Albums」(專輯) 資料表的資料列實體配置。這兩個資料表都位於資料庫階層的根部。

上述結構定義的重要注意事項之一,即是 SingersAlbums 均為頂層資料表,因此 Cloud Spanner 假設這兩者之間沒有資料本地性。當資料庫變大時,Cloud Spanner 即可在上面顯示的任何資料列之間新增分割界線。這表示 Albums 資料表的資料列的分割結果可能和 Singers 資料表的資料列不同,而且這兩個分割可獨立移動。

您可視應用程式的需求,將 Albums 資料放在與 Singers 資料不同的分割上。但如果應用程式常常需要擷取特定歌手所有專輯的相關資訊,那麼您應該建立 Albums 當做 Singers 的子項資料表,如此即可在主鍵維度將這兩個資料表的資料列放置在相同位置。下一個範例會更詳細地說明這種做法。

建立交錯資料表

假設您在設計音樂應用程式時,發現應用程式必須常常從 SingersAlbums 資料表存取特定主鍵的資料列 (比方說,每次存取 Singers(1) 資料列時,也必須存取 Albums(1, 1)Albums(1, 2) 資料列)。換句話說,SingersAlbums 要有密切的資料本地性。

您可以建立 Albums 當做 Singers 的子項或「交錯」資料表,藉以宣告這項資料本地性。如同主鍵中所述,交錯資料表是一個宣告為其他資料表子項的資料表,因為您想將子項資料表的資料列與相關父項資料列儲存在一起。如上所述,子項資料表主鍵的前置字串必須是父項資料表的主鍵。

下列結構定義中的粗線說明如何建立 Albums 當做 Singers 的交錯資料表。

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

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

此結構定義的注意事項:

  • Albums 子項資料表的主鍵前置字串 SingerId 亦為其父項資料表 Singers 的主鍵。如果 SingersAlbums 位於階層的相同層級,則不需要這樣做;但是 Albums 在此結構定義中被宣告為 Singers 的子項資料表,因此您必須這樣做。
  • ON DELETE CASCADE 註解表示如果在父項資料表刪除資料列,也會自動刪除其在此資料表的子項資料列 (亦即開頭為相同主鍵的所有資料列)。如果子項資料表沒有這項註解,或是註解為 ON DELETE NO ACTION,您必須先刪除子項資料列,才能刪除父項資料列。
  • 交錯資料列會先依照父項資料表的資料列排序,然後依照具有父項主鍵之子項資料表的連續資料列排序,亦即「Singers(1)」、「Albums(1, 1)、「Albums(1, 2)」等,以此類推。
  • 當此資料庫進行分割時,系統會保留每位歌手及其專輯資料的資料本地性,因為分割只能插入 Singers 資料表的資料列之間。
  • 父項資料列必須存在,您才能插入子項資料列。

資料列實體配置:「Albums」(專輯) 資料列在「Singers」(歌手) 資料列之間交錯

「Singers」(歌手) 及其子項資料表「Albums」(專輯) 的資料列實體配置。

建立交錯資料表的階層

SingersAlbums 之間的父項/子項關係可延伸至更多下階資料表。比方說,您可以建立名為 Songs 的交錯資料表當做 Albums 的子項,以儲存每張專輯的歌曲清單:

具有 6 個資料列和 4 個資料欄的「Songs」(歌曲) 資料表

「Songs」(歌手) 資料表的資料列邏輯檢視。主鍵資料欄位於粗線的左側。

Songs 的主鍵必須由階層上方資料表所有的主鍵組成,即 SingerIdAlbumId

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
--     + Songs (interleaved table, child table of Albums)
CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

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

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

交錯資料列的實體檢視顯示系統保留了歌手及其專輯和歌曲資料之間的資料本地性:

資料列實體檢視:「Songs」(歌曲) 會在「Albums」(專輯) 中交錯,而「Albums」(專輯) 會在「Singers」(歌手) 之間交錯

「Singers」(歌手)、「Albums」(專輯) 和「Songs」(歌曲) 資料表的資料列實體配置。這三個資料表形成交錯資料表的階層。

簡而言之,父項資料表及其所有子項和下階資料表在結構定義中構成資料表階層。雖然階層中每個資料表在邏輯上都是獨立的,但透過這種方式進行實體交錯,可提升效能、有效地預先彙整資料表、讓您一併存取相關資料列,同時減少磁碟存取次數。

可能的話,利用主鍵在交錯資料表中彙整資料。每個交錯資料列一定會和其父項資料列儲存於相同的分割中。因此,Cloud Spanner 可利用主鍵在本機進行彙整,減少存取磁碟的次數並降低網路流量。在下列範例中,SingersAlbums 是在 SingerId 主鍵上彙整:

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

Cloud Spanner 中的交錯資料表不是必要項目,但建議具備緊密資料本地性的資料表採用。如果單一資料列及其下階有可能超過幾 GB,則請避免使用交錯資料表。

索引鍵資料欄

資料表的索引鍵無法變更;您無法將索引鍵資料欄新增到現有資料表,或從現有資料表移除索引鍵資料欄。

儲存空值

您可將主鍵資料欄定義為儲存空值。如果您要在主鍵資料欄中儲存空值,請在結構定義中省略該資料欄的 NOT NULL 子句。

下列範例示範如何在主鍵資料欄 SingerId 上省略 NOT NULL 子句。請注意,由於 SingerId 是主鍵,因此 Singers 資料表中最多只能有一個資料列儲存 NULL

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

在父項和子項資料表的宣告中,主鍵資料欄的 NULLABLE (可為空值) 屬性必須相符。此範例中不允許 Albums.SingerId INT64 NOT NULL。索引鍵宣告必須省略 NOT NULL 子句,因為 Singers.SingerId 省略了該子句。

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

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

不允許的類型

不得採用 ARRAY 類型的項目如下:

  • 資料表的索引鍵資料欄。
  • 索引的索引鍵資料欄。

多租戶架構設計

如果您要儲存屬於不同客戶的資料,則可能必須提供多租戶架構。舉例來說,音樂服務可能想要個別儲存每家獨立唱片公司的資料。

傳統版多租戶架構

設計多租戶架構的傳統方式是為每個客戶建立個別的資料庫。在此範例中,每個資料庫都會具備自己的 Singers 資料表:

資料庫 1:Ackworth 唱片
SingerId FirstName LastName
1MarcRichards
2CatalinaSmith
資料庫 2:Cama 唱片
SingerId FirstName LastName
3MarcRichards
4GabrielWright
資料庫 3:Eagan 唱片
SingerId FirstName LastName
1BenjaminMartinez
2HannahHarris

如要在 Cloud Spanner 中設計多租戶架構,我們建議您為每個客戶使用不同的主鍵值。您可以在資料表中納入 CustomerId 索引鍵或類似索引鍵的資料欄。如果您將 CustomerId 設為第一個索引鍵資料欄,則每個客戶的資料都能獲得良好的本地性。Cloud Spanner 會根據大小和負載模式,自動將資料分割並分配到各個節點。在此範例中,所有客戶都有一個 Singers 資料表:

Cloud Spanner 多租戶架構資料庫
CustomerId SingerId FirstName LastName
11MarcRichards
12CatalinaSmith
23MarcRichards
24GabrielWright
31BenjaminMartinez
32HannahHarris

如果每個租戶都要有個別的資料庫,則請務必注意下列限制:

  • 每個執行個體的資料庫數量以及每個資料庫的資料表數量均有限制。視客戶數量而定,可能無法建立個別的資料庫或資料表。
  • 新增資料表和非交錯索引可能會花很多時間才能完成。如果結構定義設計需要新增資料表和索引,您可能無法獲得需要的效能。

如要建立個別的資料庫,則透過此方式將資料表分散到各資料庫,讓每個資料庫 每週進行較少次結構定義變更,成功的機率較大。

當您為應用程式的每位客戶建立個別資料表和索引時,請勿將全部的資料表和索引放置到相同的資料庫。請將資料表和索引拆分到許多資料庫上,以降低建立大量索引所造成的效能問題。但請注意,每個資料庫的資料表數量和索引數量也有限制。

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Cloud Spanner 說明文件