更新結構定義

Spanner 可讓您在不停機的情況下更新結構定義。您可透過下列幾種方式更新現有資料庫的結構定義:

支援的結構定義更新

Spanner 支援下列現有資料庫的結構定義更新:

  • 新增或捨棄具名結構定義。
  • 建立新資料表。新資料表中的資料欄可以是 NOT NULL
  • 刪除資料表,前提是資料表中沒有其他交錯的資料表,而且沒有次要索引。
  • 建立或刪除含有外來鍵的資料表。
  • 在現有資料表中新增或移除外來鍵。
  • 新增非鍵資料欄到任何資料表。新的非鍵資料欄不得為 NOT NULL
  • 從任何資料表中捨棄非鍵資料欄,除非有次要索引、外鍵、儲存的產生資料欄或檢查限制使用該非鍵資料欄。
  • NOT NULL 新增至非鍵資料欄,但不包括 ARRAY 欄。
  • 從非鍵資料欄移除 NOT NULL
  • STRING 資料欄變更為 BYTES 資料欄,或將 BYTES 資料欄變更為 STRING 資料欄。
  • PROTO 資料欄變更為 BYTES 資料欄,或將 BYTES 資料欄變更為 PROTO 資料欄。
  • 變更 PROTO 資料欄的 Proto 訊息類型。
  • 使用 ALTER PROTO BUNDLE 將新值新增至 ENUM 定義,並重新命名現有值。
  • 以任意方式變更 PROTO BUNDLE 中定義的訊息,但前提是這些訊息的修改欄位不得做為任何資料表中的鍵,且現有資料須符合新限制。
  • 增加或減少 STRINGBYTES 類型的長度限制 (包括 MAX),除非資料欄是主鍵資料欄,且有一個或多個子資料表。
  • 增加或減少 ARRAY<STRING>ARRAY<BYTES>ARRAY<PROTO> 資料欄的長度限制,最多可達允許上限。
  • 啟用或停用值和主鍵資料欄中的修訂時間戳記
  • 新增或移除次要索引。
  • 在現有資料表中新增或移除檢查條件約束。
  • 在現有資料表中新增或移除儲存的產生資料欄。
  • 建構新的最佳化工具統計資料套件
  • 建立及管理檢視畫面
  • 建立及管理序列
  • 建立資料庫角色並授予權限。
  • 設定、變更或捨棄資料欄的預設值。
  • 變更資料庫選項 (例如 default_leaderversion_retention_period)。
  • 建立及管理變更串流
  • 建立及管理機器學習模型。

不支援的結構定義更新

Spanner 不支援下列現有資料庫的結構定義更新:

  • 如果資料表或索引鍵參照 ENUM 類型的 PROTO 欄位,您就無法從 proto 列舉中移除 ENUM 值。(系統支援從 ENUM<> 資料欄使用的列舉中移除 ENUM 值,包括這些資料欄做為鍵的情況。)

結構定義更新效能

在 Spanner 中更新結構定義不需要停機。當您向 Spanner 資料庫發出一批 DDL 陳述式時,您可以在 Spanner 透過長時間執行的作業套用更新時,繼續不間斷地從資料庫進行寫入和讀取作業。

執行 DDL 陳述式所需的時間取決於更新是否需要驗證現有的資料,或補充任何資料。舉例來說,如果您將 NOT NULL 註解新增至現有的資料欄,Spanner 必須讀取資料欄中的所有值,以確認資料欄不包含任何 NULL 值。如果有很多資料需要驗證,這個步驟可能需要花費較長的時間。另一個例子是,如果您將索引新增至資料庫,Spanner 會使用現有的資料來補充索引。根據索引的定義和對應的主資料表的大小,這個程序可能需要較長的時間。不過,如果您是新增資料欄到資料表,就沒有現有的資料可以驗證,因此 Spanner 能更迅速地進行更新。

簡單來說,不需要 Spanner 驗證現有資料的結構定義更新只需要幾分鐘。需要進行驗證的結構定義更新則需要較長的時間,時間長短取決於需要驗證的現有資料量。資料驗證能以較實際工作環境流量低的優先順序在背景中執行。下節將詳細探討需要進行資料驗證的結構定義更新。

根據檢視定義驗證結構定義更新

更新結構定義時,Spanner 會驗證更新不會導致用來定義現有檢視區塊的查詢失效。如果驗證成功,結構定義更新就會成功。如果驗證失敗,結構定義更新就會失敗。詳情請參閱「建立檢視區的最佳做法」。

需要驗證資料的結構定義更新

您可以將結構定義更新設定為需要驗證現有資料符合新限制後才能完成。當結構定義更新需要驗證資料時,Spanner 不允許在受影響的結構定義有相衝突的結構定義更新,並且會在背景中驗證資料。如果驗證成功,結構定義更新就會成功。如果驗證失敗,結構定義更新就會失敗。驗證作業是透過長時間執行的作業執行。您可以檢查這些作業的狀態,以判定作業成功或失敗。

舉例來說,假設您已定義下列 music.proto 檔案,其中包含 RecordLabel 列舉和 Songwriter 通訊協定訊息:

  enum RecordLabel {
    COOL_MUSIC_INC = 0;
    PACIFIC_ENTERTAINMENT = 1;
    XYZ_RECORDS = 2;
  }

  message Songwriter {
    required string nationality   = 1;
    optional int64  year_of_birth = 2;
  }

如要在結構定義中新增 Songwriters 資料表,請按照下列步驟操作:

GoogleSQL

CREATE PROTO BUNDLE (
  googlesql.example.music.Songwriter,
  googlesql.example.music.RecordLabel,
);

CREATE TABLE Songwriters (
  Id         INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  Nickname   STRING(MAX),
  OpaqueData BYTES(MAX),
  SongWriter googlesql.example.music.Songwriter
) PRIMARY KEY (Id);

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

系統會允許下列結構定義更新,但這些更新需要進行驗證。根據現有的資料量,可能需要較長的時間才能完成驗證:

  • NOT NULL 註解新增至非鍵資料欄。例如:

    ALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL;
    
  • 縮減資料欄的長度。例如:

    ALTER TABLE Songwriters ALTER COLUMN FirstName STRING(10);
    
  • BYTES 變更為 STRING。例如:

    ALTER TABLE Songwriters ALTER COLUMN OpaqueData STRING(MAX);
    
  • INT64/INT32 變更為 ENUM。例如:

    ALTER TABLE Albums ALTER COLUMN Label googlesql.example.music.RecordLabel;
    
  • RecordLabel 列舉定義中移除現有值。

  • 在現有的 TIMESTAMP 資料欄上啟用修訂時間戳記。例如:

    ALTER TABLE Albums ALTER COLUMN LastUpdateTime SET OPTIONS (allow_commit_timestamp = true);
    
  • 在現有資料表中新增檢查條件約束。

  • 在現有資料表中新增儲存的產生資料欄。

  • 建立含有外來鍵的新資料表。

  • 在現有資料表中加入外鍵。

如果基礎資料不符合新限制,這些結構定義更新就會失敗。舉例來說,如果 Nickname 資料欄中的任何值為 NULLALTER TABLE Songwriters ALTER COLUMN Nickname STRING(MAX) NOT NULL 陳述式會失敗,因為現有資料不符合新定義的 NOT NULL 限制。

資料驗證可能需要幾分鐘到幾小時的時間。完成資料驗證的時間取決於下列因素:

  • 資料集大小
  • 執行個體的運算能力
  • 執行個體的負載

結構定義更新完成之前,有些結構定義更新可能會變更傳送要求到資料庫的行為。舉例來說,如果您將 NOT NULL 新增到資料欄,Spanner 會立即開始拒絕在資料欄寫入 NULL 的新要求。如果新結構定義更新最終因資料驗證而失敗,則會有一段封鎖寫入作業的時間,即使舊結構定義可接受這種寫入也是如此。

您可以使用 projects.instances.databases.operations.cancel 方法或 gcloud spanner operations,取消長時間執行的資料驗證作業。

批次陳述式的執行順序

如果您使用 Google Cloud CLI、REST API 或 RPC API,可以發出一批 CREATEALTERDROP 陳述式,其中包含一或多個陳述式。

Spanner 會依序套用來自同一批次的陳述式,並在發生第一個錯誤時停止。如果套用某個陳述式導致發生錯誤,則會復原該陳述式。系統不會復原批次中之前套用的陳述式的結果。這表示如果您希望某些陳述式並行執行 (例如可能需要很長時間的索引回填),就應以個別批次提交這些陳述式。

Spanner 可能會結合並重新排序來自不同批次的陳述式,且可能會將來自不同批次的陳述式混合成一個不可分割的變更,再套用至資料庫。在每個不可分割的變更中,來自不同批次的陳述式會以任意順序執行。舉例來說,如果有一批陳述式包含 ALTER TABLE MyTable ALTER COLUMN MyColumn STRING(50),另一批陳述式包含 ALTER TABLE MyTable ALTER COLUMN MyColumn STRING(20),Spanner 會將該資料欄保留在其中一個狀態,但不會指定是哪個狀態。

在結構定義更新期間建立的結構定義版本

Spanner 使用結構定義版本管理,因此對大型資料庫進行結構定義更新期間無須停機。Spanner 會維護較舊的結構定義版本,以在處理結構定義更新時支援讀取作業。接著,Spanner 會建立一或多個新的結構定義版本,以處理結構定義更新。每個版本都會在單一不可分割的變更中包含陳述式集合的結果。

結構定義版本未必會以一對一的方式對應到其中一批 DDL 陳述式或個別 DDL 陳述式。某些個別 DDL 陳述式 (例如現有基本資料表的索引建立,或需要資料驗證的陳述式) 會導致多個結構定義版本。在其他情況下,可在單一版本中將多個 DDL 陳述式組成批次。舊版結構定義會耗用大量伺服器和儲存空間資源,並保留至過期為止 (不再需要提供舊版資料的讀取作業)。

下表顯示 Spanner 更新結構定義所需的時間。

結構定義作業 預估持續時間
CREATE TABLE 分鐘
CREATE INDEX

數分鐘到數小時,前提是主資料表是在索引之前建立。

數分鐘,前提是陳述式與主資料表的 CREATE TABLE 陳述式同時執行。

DROP TABLE 分鐘
DROP INDEX 數分鐘
ALTER TABLE ... ADD COLUMN 分鐘
ALTER TABLE ... ALTER COLUMN

數分鐘到數小時,前提是需要背景驗證

數分鐘,前提是不需要背景驗證。

ALTER TABLE ... DROP COLUMN 分鐘
ANALYZE

視資料庫大小而定,可能需要幾分鐘到幾小時。

資料類型變更和變更串流

如果您變更變更串流監看的資料欄資料類型,後續相關變更串流記錄column_types 欄位會反映新類型,記錄 mods 欄位中的 old_values JSON 資料也會反映新類型。

變更串流記錄 new_values 欄位的 mods 一律會與資料欄的目前類型相符。變更受監控資料欄的資料類型,不會影響該變更之前的任何變更串流記錄。

如果是 BYTESSTRING 的變更,Spanner 會在結構定義更新期間驗證資料欄的舊值。因此,在寫入任何後續變更串流記錄時,Spanner 已安全地將舊的 BYTES 型別值解碼為字串。

結構定義更新的最佳做法

下列各節將說明更新結構定義的最佳做法。

發出結構定義更新前的程序

在您發出結構定義更新之前:

  • 針對您要變更的資料庫,驗證其中所有現有資料都符合結構定義更新施加的限制。由於某些類型的結構定義更新能否成功不僅取決於目前的結構定義,更取決於資料庫中的資料,因此即使在測試資料庫結構定義更新獲得成功,並不能保證實際工作環境資料庫結構定義更新會成功。以下是一些常見的範例:

    • 如果要在現有的資料欄新增 NOT NULL 註解,請檢查資料欄未包含任何現有的 NULL 值。
    • 如果您縮短允許的 STRINGBYTES 資料欄長度,請檢查該資料欄中所有現有的值都符合長度限制。
  • 如果您寫入的資料欄、資料表或索引要經過結構定義更新,請確保寫入的值符合新限制。

  • 如果您要捨棄資料欄、資料表或索引,請確認您目前沒有寫入或讀取該資料欄。

限制結構定義更新頻率

如果在短時間內執行過多結構定義更新,Spanner 可能會throttle處理佇列中的結構定義更新。這是因為 Spanner 限制了結構定義版本的儲存空間大小。如果保留期限內有太多舊版結構定義,系統可能會限制結構定義更新。架構變更的最高速率取決於許多因素,其中一個是資料庫中的資料欄總數。舉例來說,如果資料庫有 2000 個資料欄 (大約是 INFORMATION_SCHEMA.COLUMNS 中的 2000 個資料列),在保留期限內最多可進行 1500 次結構定義變更 (如果結構定義變更需要多個版本,則次數會更少)。如要查看進行中結構定義更新的狀態,請使用 gcloud spanner operations list 指令,並依 DATABASE_UPDATE_DDL 類型的作業進行篩選。如要取消進行中的結構定義更新,請使用 gcloud spanner operations cancel 指令並指定作業 ID。

DDL 陳述式的批次處理方式,以及每個批次中的順序,可能會影響產生的結構定義版本數量。如要在特定時間範圍內盡可能執行更多結構定義更新,請使用批次處理,盡量減少結構定義版本數量。大型更新中說明瞭一些經驗法則。

結構定義版本所述,部分 DDL 陳述式會建立多個結構定義版本,考慮批次處理和每個批次中的順序時,這些版本非常重要。有兩種主要類型的陳述式可能會建立多個結構定義版本:

  • 可能需要補充索引資料的陳述式,例如 CREATE INDEX
  • 可能需要驗證現有資料的陳述式,例如新增 NOT NULL

不過,這類陳述式不一定會建立多個結構定義版本。Spanner 會嘗試偵測何時可最佳化這類陳述式,避免使用多個結構定義版本,這取決於批次處理。舉例來說,如果 CREATE INDEX 陳述式與索引基本資料表的 CREATE TABLE 陳述式位於同一批次,且沒有其他資料表的介入陳述式,Spanner 就能保證建立索引時基本資料表為空白,因此可避免回填索引資料。「大型更新」一節說明如何使用這項屬性有效率地建立多個索引。

如果無法批次處理 DDL 陳述式,避免建立多個結構定義版本,請在保留期限內,限制單一資料庫結構定義的結構定義更新次數。延長兩次結構定義更新之間的時間,可讓 Spanner 在建立新版前,移除舊版結構定義。

  • 某些關聯資料庫管理系統有一些軟體套件,會在每個實際工作環境部署上,對資料庫結構定義更新進行一長串的升級和降級。我們不建議在 Spanner 使用這類程序。
  • Spanner 經過最佳化,可使用主鍵分割資料以用於多租戶架構解決方案。多租戶架構解決方案針對每個客戶使用個別資料表,因此造成大量待處理的結構定義更新作業,需要很長時間才能完成這些作業。
  • 需要驗證或索引補充作業的結構定義更新會使用更多伺服器資源,因為每個陳述式都會在內部建立多個結構定義版本。

大型結構定義更新的選項

如要在資料表中建立大量索引,最好的方法是同時建立所有索引,這樣系統只會建立單一結構定義版本。最佳做法是在 DDL 陳述式清單中,緊接在資料表後方建立索引。您可以在建立資料庫時建立資料表及其索引,也可以在單一大型批次陳述式中建立。如果您需要建立多個資料表,且每個資料表都有許多索引,可以將所有陳述式納入單一批次。如果所有陳述式都能使用單一結構定義版本一起執行,您可以在單一批次中納入數千個陳述式。

如果陳述式需要回填索引資料或執行資料驗證,就無法在單一結構定義版本中執行。如果索引的基底資料表已存在 (可能是因為在先前的 DDL 陳述式批次中建立,或是因為 CREATE TABLECREATE INDEX 陳述式之間的批次中,有需要多個結構定義版本的陳述式),就會發生這種情況。CREATE INDEXSpanner 規定單一批次中,這類陳述式不得超過 10 個。需要回填的索引建立作業,每個索引會使用多個結構定義版本,因此建議每天建立的回填索引不要超過 3 個 (無論如何批次處理,除非批次處理可避免回填)。

舉例來說,這批陳述式會使用單一結構定義版本:

GoogleSQL

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

CREATE INDEX SingersByFirstName ON Singers(FirstName);

CREATE INDEX SingersByLastName ON Singers(LastName);

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

CREATE INDEX AlbumsByTitle ON Albums(AlbumTitle);

相反地,這個批次會使用許多結構定義版本,因為 UnrelatedIndex 需要回填 (因為其基本資料表必須已存在),這會強制後續所有索引也需要回填 (即使這些索引與基本資料表位於同一批次):

GoogleSQL

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

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

CREATE INDEX UnrelatedIndex ON UnrelatedTable(UnrelatedIndexKey);

CREATE INDEX SingersByFirstName ON Singers(FirstName);

CREATE INDEX SingersByLastName ON Singers(LastName);

CREATE INDEX AlbumsByTitle ON Albums(AlbumTitle);

建議將 UnrelatedIndex 的建立作業移至批次結尾或不同批次,盡量減少結構定義版本。

等候 API 要求完成

發出 projects.instances.databases.updateDdl (REST API) 或 UpdateDatabaseDdl (RPC API) 要求時,請分別使用 projects.instances.databases.operations.get (REST API) 或 GetOperation (RPC API),等待每個要求完成後再開始新的要求。等待每個要求完成,應用程式才能追蹤結構定義更新進度。並將擱置中的結構定義更新的待處理工作維持在可管理的大小。

大量載入

如果您在建立資料後會將資料大量載入到資料表,通常在載入資料後建立索引會比較有效率。如要新增多個索引,使用初始結構定義中的所有資料表和索引來建立資料庫可能會比較有效率,詳情請參閱大型結構定義更新的選項一節。