Cloud Datastore 索引

App Engine 會針對實體的每個屬性預先定義簡單的索引。App Engine 應用程式可在名為 index.yaml索引設定檔中進一步定義自訂索引。開發伺服器遇到無法使用現有索引執行的查詢時,會自動在這個檔案中新增建議。將索引上傳至應用程式之前,您可以透過編輯檔案的方式手動調整索引。

附註:基於索引的查詢機制支援各種查詢,適用於大多數應用程式。不過該機制並不支援其他資料庫技術常見的某些查詢種類:Cloud Datastore 查詢引擎尤其不支援彙整與匯總查詢。如需瞭解 Cloud Datastore 查詢的限制,請參閱 Datastore 查詢頁面。

索引定義和結構

索引的定義列於指定實體種類的屬性清單中,每個屬性會有對應的排序順序 (遞增或遞減)。如需與祖系查詢一同使用,索引也可以選擇是否包含實體的祖系。

索引表格中,該索引定義中提及的屬性都各自有一欄。表格中的每一列代表 Cloud Datastore 中的一個實體,依據索引,這些實體可能是查詢的結果。只有在索引所用的每個屬性都設有已編入索引的值時,該實體才會加入索引中;如果索引定義參照實體未設有值的屬性,該實體便不會出現在索引中,且因此不會在任何基於該索引的查詢結果中傳回。

索引表格的列將先依據祖系排列,再依據屬性值排列,排列順序依照索引定義指定。如要定義查詢的「完美索引」 (亦即允許最有效執行查詢的索引),應依序在以下屬性中定義:

  1. 等式篩選器使用的屬性
  2. 不等式篩選器使用的屬性 (可以不超過一個)
  3. 排序順序使用的屬性

這可確保每個潛在查詢執行作業的所有結果,都會出現在表格的連續列中。Cloud Datastore 會依照以下步驟使用完美索引來執行查詢:

  1. 找出查詢種類、篩選器屬性、篩選器運算子以及排序順序所對應的索引。
  2. 從索引的開頭開始掃描,直到符合該查詢所有篩選條件的第一個實體為止。
  3. 繼續掃描索引,依次傳回每個實體,直到篩選器:
    • 遇到不符合篩選條件的實體,或
    • 抵達索引尾端,或
    • 已收集到查詢所要求的結果數上限。

例如,請思考以下查詢:

SELECT * FROM Person WHERE LastName = "Smith"
                       AND Height < 72
                  ORDER BY Height DESC

此查詢的完美索引是 Person 種類實體的金鑰表格,其中有 LastNameHeight 屬性值的欄。索引先依 LastName 遞增排序,然後再依 Height 遞減排序。

屬於相同表單但具有不同篩選器值的兩個查詢,將使用相同的索引。例如,以下查詢使用與前述查詢相同的索引:

SELECT * FROM Person WHERE LastName = "Jones"
                       AND Height < 63
                     ORDER BY Height DESC

以下兩個查詢雖然格式不同,但也使用相同索引:

SELECT * FROM Person WHERE LastName = "Friedkin"
                       AND FirstName = "Damian"
                     ORDER BY Height ASC

SELECT * FROM Person WHERE LastName = "Blair"
                  ORDER BY FirstName, Height ASC

索引設定

根據預設,Cloud Datastore 會針對每種實體種類的每個屬性預先定義索引。這些預先定義的索引已足以執行許多簡單的查詢,例如只有等式篩選器的查詢,和簡單的不等式查詢。對於所有其他查詢,應用程式必須在名為 index.yaml索引設定檔中定義所需的索引。如果應用程式嘗試執行可用索引 (預先定義的索引,或索引設定檔指定的索引) 無法執行的查詢,查詢便會失敗。

Datastore 為以下格式的查詢建構自動索引:

  • 僅使用祖系和金鑰篩選器的無種類查詢
  • 僅使用祖系和等式篩選器的查詢
  • 僅使用不等式篩選器 (只能使用一個屬性) 的查詢
  • 僅使用祖系篩選器、針對屬性的等式篩選器,以及針對金鑰的不等式篩選器的查詢
  • 沒有篩選器、只依一個屬性以遞增或遞減順序排序的查詢

其他格式的查詢需在索引設定檔中指定索引,包含:

  • 含有祖系和不等式篩選器的查詢
  • 含有一或多個針對屬性的不等式篩選器,以及一個或多個針對其他屬性的等式篩選器的查詢
  • 針對金鑰依遞減順序排序的查詢
  • 具有多個排序順序的查詢

索引和屬性

有關索引以及索引與 Cloud Datastore 中實體屬性的關係,以下是一些特別注意事項:

具有混合值類型的屬性

如果兩個實體有名稱相同、但值類型不同的屬性,屬性的索引便會先以值類型,再以每種類型適合的次級順序排序實體。舉例來說,如果兩個實體都有名為 age 的屬性,其中之一為整數值,而另一個是字串值。以 age 屬性排序時,無論屬性值本身為何,使用整數值的實體皆會一律位於使用字串值的實體之前。

在整數值和浮點數的情況下尤其需要注意,因為 Cloud Datastore 將這兩種數值視為不同類型。由於所有整數都會排在浮點數之前,因此具有整數值 38 的屬性會排在具有浮點值 37.5 的屬性之前。

未編製索引的屬性

如果您確定將永遠不必篩選或排序特定屬性,便可透過聲明該屬性「未編製索引」,讓 Cloud Datastore 不再維護該屬性的索引項目。這能夠減少 Cloud Datastore 應執行的寫入數量,進而降低執行應用程式的成本。具有未編製索引屬性的實體,行為方式與未設定該屬性的實體一樣:該實體將永遠不符合篩選或排序未編製索引屬性的查詢。

附註:如果屬性出現在含有多個屬性的索引中,將該屬性設為未編製索引,便會防止該屬性編入多屬性的索引中。

例如,假設實體有 ab 屬性,而您想要建立滿足 WHERE a ="bike" and b="red" 等查詢的索引。另外假設您並不在乎 WHERE a="bike"WHERE b="red" 查詢。如果您將 a 設為未編製索引,並為 ab 建立索引,Cloud Datastore 將不會為 ab 索引建立索引項目,因而 WHERE a="bike" and b="red" 查詢將不會運作。如需 Cloud Datastore 為 ab 索引建立項目,ab 皆需加入索引。

您可以在 STRUCT 欄位標記設定 noindex,聲明屬性未編入索引:

type Person struct {
	Name string
	Age  int `datastore:",noindex"`
}

然而請注意,將屬性從未編入索引改為已編入索引,並不會影響改變之前建立的任何既有實體。查詢使用該屬性進行篩選時,將不會傳回這類既有實體,因為這些實體在建立時並未寫入查詢的索引。為使實體可供未來的查詢使用,您必須將實體重新寫入 Cloud Datastore,才能讓實體輸入適當的索引中。亦即,您必須為每個既有實體執行以下動作:

  1. 從 Cloud Datastore 擷取 (get) 實體。
  2. 將實體寫入 (put) 回 Cloud Datastore。

同樣地,將屬性從已編入索引變更為未編入索引,只會影響後續寫入 Cloud Datastore 的實體。在更新或刪除實體之前,具有該屬性之任何既有實體的索引項目會持續存在。為了避免產生不必要的結果,您必須清除依照 (目前未編入索引的) 屬性篩選或排序所有查詢的程式碼。

索引上限

針對可以與單一實體相關聯的索引項目,Datastore 會限制數量和總體大小。這些上限很高,大多數應用程式都不受影響。然而,在部分情況下,您可能會遭遇這些上限。

所述,Cloud Datastore 會針對每個實體的每個屬性 ([]byte 欄位以及您明確聲明未編入索引的屬性除外),在預先定義的索引中建立項目。該屬性也可能列在 index.yaml 設定檔的額外自訂索引之中。如果實體並未列出屬性,非祖系索引的這類自訂索引便會有最多一個項目,或者祖系索引的每個實體祖系會有一個項目。每次屬性值變更時,必須更新每個索引項目。

如果某屬性在每個實體只有單一值,便必須在該屬性的預先定義索引中,為每個實體僅儲存一次每個可能的值。即便如此,仍可能有實體因為該單一值屬性過大,導致其超過索引項目或大小限制。同樣地,相同屬性可以有多重值的實體必須為每個值儲存不同的索引項目;同理,如果可能值的數字太大,便可能超過項目限制。

如果實體有多個屬性,且每個屬性都能有多個值,情況便會變得更糟。為了適應這類實體,索引必須包含每個可能屬性值「組合」的項目。如果自訂索引參照多個屬性,且每個屬性有多個值,只要實體有大量項目及相對較少的可能屬性值,便可能在組合的情況下「爆炸」。由於大量必須更新的索引項目,這類「爆炸式索引」可能會大幅提升寫入 Cloud Datastore 實體的成本,也能夠輕易使實體超過索引實體或大小上限。

舉例來說,查詢

SELECT * FROM Widget WHERE X=1 AND Y=2 ORDER BY Date

使 SDK 建議下列索引:

indexes:
- kind: Widget
  properties:
  - name: X
  - name: Y
  - name: Date
該索引將需要每個實體各 |X| * |Y| * |Date| 個項目 (其中 |X| 代表與具備屬性 X 的實體相關聯的值數量)。舉例來說,以下程式碼
type Widget struct {
	X    []int
	Y    []string
	Date time.Time
}

func f(ctx context.Context) {
	e2 := &Widget{
		X:    []int{1, 2, 3, 4},
		Y:    []string{"red", "green", "blue"},
		Date: time.Now(),
	}

	k := datastore.NewIncompleteKey(ctx, "Widget", nil)
	if _, err := datastore.Put(ctx, k, e2); err != nil {
		// Handle error.
	}
}

將會建立一個實體,其中 x 屬性有四個值、y 屬性有三個值,date 則設為目前日期。這會需要 12 個索引項目,每個可能的屬性值組合都須有一個索引項目:

(1, "red", <now>) (1, "green", <now>) (1, "blue", <now>)

(2, "red", <now>) (2, "green", <now>) (2, "blue", <now>)

(3, "red", <now>) (3, "green", <now>) (3, "blue", <now>)

(4, "red", <now>) (4, "green", <now>) (4, "blue", <now>)

當相同的屬性重複數次後,Cloud Datastore 便能偵測爆炸式索引,並建議替代的索引。然而,在所有其他情況下 (例如本範例定義的查詢),Cloud Datastore 會產生爆炸式索引。在此情況下,您可以手動在索引設定檔中設定索引,以便防止爆炸式索引出現:

indexes:
- kind: Widget
  properties:
  - name: X
  - name: Date
- kind: Widget
  properties:
  - name: Y
  - name: Date
這會將所需的項目數減少至僅 7 個 (|X| * |Date| + |Y| * |Date|),而非 12 個:

(1, <now>) (2, <now>) (3, <now>) (4, <now>)

("red", <now>) ("green", <now>) ("blue", <now>)

任何導致索引超出索引項目或大小限制的 Put 作業將會失敗,並顯示錯誤。錯誤的文字說明超過的上限為何 ("Too many indexed properties""Index entries too large"),以及原因為哪項自訂索引。如果您建立的新索引,會在建構時超過任何實體的上限,關於該索引的查詢會失敗,且該索引會在 GCP 主控台中顯示 Error 狀態。如要解決 Error 狀態中的索引:

  1. index.yaml 檔案移除 Error 狀態的索引。

  2. index.yaml 所在的目錄執行下列指令,以便從 Cloud Datastore 移除該索引:

    gcloud datastore cleanup-indexes index.yaml
    
  3. 解決錯誤成因。例如:

    • 重新制定索引定義和對應查詢。
    • 移除導致索引爆炸的實體。
  4. 將索引加回 index.yaml 檔案。

  5. index.yaml 所在的目錄執行下列指令,以便在 Cloud Datastore 中建立該索引:

    gcloud datastore create-indexes index.yaml
    

如果您要避免爆炸式索引,可以避免使用清單屬性自訂索引的查詢。如上所述,這包括多種排列順序的查詢,或同時包括等式篩選器和不等式篩選器的查詢。

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

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

這個網頁
Go 適用的 App Engine 標準環境