本文將說明更新結構定義的最佳做法。
開始更新結構定義前的程序
在您發出結構定義更新之前:
確保資料庫中的所有現有資料,都符合結構定義更新所導入的限制。由於某些結構定義更新取決於實際資料,而不只是目前的結構定義,因此即使在測試資料庫更新成功,也不能保證實際工作環境資料庫更新會成功。以下是一些常見的範例:
- 如果您要在現有的資料欄新增
NOT NULL註解,請檢查資料欄未包含任何現有的NULL值。 - 如果您縮短允許的
STRING或BYTES資料欄長度,請檢查該資料欄中所有現有的值都符合長度限制。
- 如果您要在現有的資料欄新增
如果您寫入的資料欄、資料表或索引要經過結構定義更新,請確保寫入的值符合新限制。
如果您要捨棄資料欄、資料表或索引,請確認您目前沒有寫入或讀取該資料欄。
限制結構定義更新頻率
如果在短時間內執行太多結構定義更新,Spanner 可能會throttle處理佇列中的結構定義更新。這是因為 Spanner 限制了儲存結構定義版本的空間量。如果保留期間內有太多舊版結構定義,系統可能會限制結構定義更新。架構變更的最高速率取決於許多因素,其中一個是資料庫中的資料欄總數。舉例來說,如果資料庫有 2000 個資料欄 (大約是 2000 個資料列,以 INFORMATION_SCHEMA.COLUMNS 為準),在保留期限內最多可執行 1500 次結構定義變更 (如果結構定義變更需要多個版本,則次數會更少)。如要查看進行中結構定義更新的狀態,請使用 gcloud spanner operations
list 指令,並依 DATABASE_UPDATE_DDL 類型的作業進行篩選。如要取消進行中的結構定義更新,請使用 gcloud spanner operations
cancel 指令並指定作業 ID。
DDL 陳述式的批次處理方式,以及每個批次中的順序,可能會影響產生的結構定義版本數量。如要在特定時間範圍內盡可能執行更多結構定義更新,請使用批次處理,盡量減少結構定義版本數量。部分指南請參閱大型更新。
如結構定義版本所述,部分 DDL 陳述式會建立多個結構定義版本,考量每個批次中的批次處理和順序時,這些版本非常重要。可能建立多個結構定義版本的陳述式主要有兩種:
- 可能需要回填索引資料的陳述式,例如
CREATE INDEX。 - 強制 Spanner 驗證現有資料的陳述式,例如新增
NOT NULL或長度限制。
不過,這類陳述式不一定會建立多個結構定義版本。Spanner 會嘗試偵測何時可最佳化這類陳述式,避免使用多個結構定義版本,這取決於批次處理。舉例來說,如果 CREATE INDEX 陳述式與索引主資料表的 CREATE TABLE 陳述式位於同一批次,且沒有其他資料表的介入陳述式,Spanner 就能保證建立索引時主資料表為空白,因此可避免回填索引資料。「大型更新」一節說明如何使用這項屬性,有效率地建立多個索引。
如果無法批次處理 DDL 陳述式,避免建立多個結構定義版本,請在保留期限內,限制單一資料庫結構定義的結構定義更新次數。延長兩次結構定義更新之間的時間,可讓 Spanner 在建立新版前,移除舊版結構定義。
- 某些關聯資料庫管理系統有一些軟體套件,會在每個實際工作環境部署上,對資料庫結構定義更新進行一長串的升級和降級。我們不建議在 Spanner 使用這類程序。
- Spanner 經過最佳化,可使用主鍵分割資料以用於多租戶解決方案。如果您使用多租戶架構解決方案,針對每個客戶使用個別資料表,請注意,一次更新多位客戶的結構定義,可能會造成大量待處理的結構定義更新作業,需要很長時間才能完成這些作業。
- 需要驗證或索引補充作業的結構定義更新會使用更多伺服器資源,因為每個陳述式都會在內部建立多個結構定義版本。
批次陳述式的執行順序
如果您使用 Google Cloud CLI、REST API 或 RPC API,可以發出一批 CREATE、ALTER 或 DROP 陳述式,其中包含一或多個陳述式。
Spanner 會依序套用來自同一批次的陳述式,並在發生第一個錯誤時停止。如果套用某個陳述式導致發生錯誤,則會復原該陳述式。系統不會復原批次中之前套用的陳述式的結果。這表示如果想讓需要不可避免的回填作業的陳述式並行執行 (例如在現有大型資料表上建立多個索引),您應以個別批次提交這些陳述式,因為每次回填作業可能需要很長時間。另一方面,如果您要建立含索引的新資料表,最佳做法是將索引與資料表一起建立 (先執行 CREATE TABLE,再執行 CREATE INDEX),這樣就能完全避免回填。
Spanner 可能會結合並重新排序來自不同批次的陳述式,且可能會將來自不同批次的陳述式混合成一個不可分割的變更,再套用至資料庫。在每個不可分割的變更中,來自不同批次的陳述式會以任意順序執行。舉例來說,如果有一批陳述式包含 ALTER TABLE table_name ALTER COLUMN column_name STRING(50),另一批陳述式包含 ALTER TABLE table_name ALTER COLUMN
column_name STRING(20),Spanner 會將該資料欄保留在其中一個狀態,但保留的狀態不具決定性。
大型結構定義更新的選項
如要在資料表中建立大量索引,最好的方法是同時建立所有索引,這樣只會建立單一結構定義版本。最佳做法是在 DDL 陳述式清單中,緊接在資料表後方建立索引。您可以在建立資料庫時建立資料表及其索引,也可以在單一大型批次陳述式中建立。如需建立多個資料表,且每個資料表都有許多索引,您可以將所有陳述式納入單一批次。如果所有陳述式都能使用單一結構定義版本一起執行,您可以在單一批次中納入數千個陳述式。
如果陳述式需要回填索引資料或執行資料驗證,就無法在單一結構定義版本中執行。如果索引的基底資料表已存在 (可能是因為在先前的 DDL 陳述式批次中建立,或是因為 CREATE TABLE 和 CREATE 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),等待每個要求完成後再開始新的要求。等待每個要求完成,應用程式才能追蹤結構定義更新的進度。並將擱置中的結構定義更新的待處理工作維持在可管理的大小。
大量載入
大量載入資料到新資料表時,您可以在載入資料前後建立次要索引。如果在載入資料後建立索引,載入資料的速度會比較快,但這表示索引必須回填。
如果您先載入資料,然後再建立索引,資料擷取速度會比較快,因為系統只會寫入資料表,之後的索引回填作業可以將索引資料寫入經過最佳化的批次,這比同時寫入索引資料和資料表資料更有效率。不過,回填索引需要多個結構定義版本,且有相關限制。如大規模更新選項所述,您應在單一批次中建立最多 10 個需要回填的索引,且最好每天建立最多 3 個這類索引。
或者,您也可以在同一批次中建立資料表和索引,詳情請參閱大型更新的選項。這樣可避免索引回填,但大量載入資料的速度會較慢,因為系統必須在載入資料時更新每個索引。
在特定情況下,哪種選擇較好取決於要載入多少資料、特定資料表和索引鍵、需要多少索引,以及在同一個資料庫中需要大量載入作業的頻率。根據經驗法則,如果需要將大量資料載入每個資料表,且只需要少數索引,最好分別建立索引。