索引

每個 Cloud Firestore (Datastore 模式) 查詢會使用一或多個「索引」計算其結果,這些索引當中包含由索引的屬性及實體的祖系 (選用) 所指定序列中的實體索引鍵。索引會不斷更新,以反映應用程式對其實體所做的任何變更,因此所有查詢均可獲得正確的結果,無需再進一步計算。

索引分為兩種類型:

內建索引
根據預設,Datastore 模式資料庫會自動為每一個實體種類的每一種屬性預先定義一個索引。這些單一屬性索引適用於簡單的查詢類型。
複合式索引
複合式索引會針對每個索引實體的多個屬性值建立索引。複合式索引支援複合式查詢,需要使用索引設定檔 (index.yaml) 進行定義。

本主題後文將深入探討索引類型。

索引定義和結構

索引的定義是以指定實體種類的屬性清單為準,每個屬性各有其對應順序 (遞增或遞減)。如用於祖系查詢,索引也可以選擇是否包含實體的祖系。

在索引表中,索引定義中指定名稱的每一項屬性各成一欄。表格中的每一列代表一個實體,這些實體可能是以索引為基礎的查詢結果。只有在索引所用的每個屬性都設有已編入索引的值時,才會將該實體加入索引中;如果索引定義參照實體未設任何值的屬性,該實體便不會出現在索引中,且因此不會在任何以該索引為基礎的查詢結果中傳回。

索引表的資料列是以索引定義中指定的順序為準,先按祖系排序,再按屬性值排序。如果要定義查詢的「完美索引」 (亦即能讓查詢功能發揮最高執行效率的索引),應依序在以下屬性中定義:

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

這樣能夠保證每個可能執行查詢的所有結果均出現在表中的連續資料列裡。Datastore 模式資料庫會依照以下步驟使用完美索引執行查詢:

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

例如,請思考以下查詢:

SELECT * FROM Task
WHERE category = 'Personal'
  AND priority < 3
ORDER BY priority DESC

此查詢的完美索引是 Task 種類實體的索引鍵表格,其中包含 categorypriority 屬性值的欄。索引的排序順序是先按 category 遞增排序,再依 priority 遞減排序:

indexes:
- kind: Task
  properties:
  - name: category
    direction: asc
  - name: priority
    direction: desc

形式相同但包含不同篩選條件值的兩個查詢使用同一個索引。例如,以下查詢使用與前述查詢相同的索引:

SELECT * FROM Task
WHERE category = 'Work'
  AND priority < 5
ORDER BY priority DESC

此索引

indexes:
- kind: Task
  properties:
  - name: category
    direction: asc
  - name: priority
    direction: asc
  - name: created
    direction: asc

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

SELECT * FROM Task
WHERE category = 'Personal'
  AND priority = 5
ORDER BY created ASC

SELECT * FROM Task
WHERE category = 'Work'
ORDER BY priority ASC, created ASC

以上建立的索引可以同時符合這兩個查詢。

索引設定

Cloud Firestore (Datastore 模式) 提供「內建」(或稱自動) 索引,適用於以下形式的查詢:

  • 僅使用祖系和索引鍵篩選條件的無種類查詢
  • 僅使用祖系和等式篩選器的查詢
  • 只使用不等式篩選條件的查詢 (只限單一屬性)
  • 只使用祖系篩選條件、等式篩選條件 (查詢屬性),以及不等式篩選條件 (查詢金鑰) 的查詢
  • 沒有篩選條件,且只有一個按遞增或遞減順序查詢屬性的排序順序

舉例說明,Datastore 模式資料庫預設會自動為每一個實體種類的每個屬性預先定義兩個單一屬性索引,其中一個按遞增順序排序,另一個則按遞減順序排序。若您不希望資料庫留存屬性的索引,請將該屬性排除於您的索引之外。請注意,排除屬性會將該屬性自任何複合式索引移除。

內建索引足以執行許多簡單查詢,例如只使用等式的查詢以及簡單的不等式查詢。

內建索引不會出現在 Google Cloud Platform Console 的索引頁面中。

若要進行較複雜的查詢,應用程式必須定義「複合式」(亦稱「手動」) 索引。下列形式的查詢必須使用複合式索引:

  • 使用祖系和不等式篩選條件的查詢
  • 使用一或多個不等式篩選條件查詢某個屬性,並使用一或多個等式查詢其他屬性的查詢
  • 按遞減順序排序金鑰的查詢
  • 採用多種排序順序的查詢
  • 使用一或多個篩選條件且有一或多個排序順序的查詢

複合式索引必須在應用程式的索引設定檔 (index.yaml) 中定義。(索引設定檔並未含有內建索引)。

複合式索引由多種屬性組成,不得將其中任何一個屬性排除於您的索引之外

如需查看複合式索引,請前往 GCP Console 的索引頁面。您無法使用 GCP Console 建立或更新複合式索引。

若應用程式嘗試執行不能使用可用索引執行的查詢 (內建索引或是索引設定檔中指定的索引),查詢便會失敗。

Datastore mode API 會自動建議大多數應用程式適用的索引。依據應用程式的 Datastore 模式資料庫使用量,以及資料的大小與形狀而定,您或許可以手動調整索引。例如,寫入具有多個屬性值的實體,可能會形成爆炸式索引,導致儲存成本偏高。

Cloud Datastore 模擬器可以讓您更容易管理索引設定檔。Cloud Datastore 可以產生能夠讓查詢成功的索引設定,不會因為要在沒有索引的情況下執行必須要有索引的查詢而失敗。如果您在本機測試應用程式時,會使用每一種篩選條件和排序順序組合執行應用程式可能會發出的每一個查詢,所產生的項目將代表一組完整的索引。如果您的測試不會執行每種可能的查詢形式,您可以在更新索引之前審查及調整索引設定檔。

如要進一步瞭解 index.yaml,請參閱索引設定一文。

部署或刪除索引

索引設定檔修改完成後,請執行 gcloud datastore indexes create 指令讓索引設定開始生效。如要瞭解詳情,請參閱更新索引相關說明。

若不再需要之前部署過的索引,可以刪除未使用的索引

索引和屬性

關於索引以及索引與實體屬性之間的關聯性,請謹記以下幾項注意事項:

具有混合值類型的屬性

若兩個實體所含的屬性名稱相同但值類型不同,屬性索引會先按值類型排序實體,再按每種類型適合的次要順序排序。舉例來說,如果兩個實體各有一個名為 age 的屬性,其中一個含有整數值,另一個含有字串值,則以 age 屬性排序時,無論屬性值本身為何,具有整數值的實體一律優先於具有字串值的實體。

如果是整數和浮點數,尤其要注意這一點,因為 Datastore 模式會將這兩種類型視為不同的類型。由於會先排序所有整數再排序所有浮點數,因此若屬性含整數值 38,其排序順序就會在含浮點數值 37.5 的屬性之前。

排除的屬性

若您知道自己絕對不會需要篩選或排序某個特定屬性,可以將該屬性排除於索引之外,不讓 Datastore 模式資料庫保留該屬性的索引項目。這樣能夠減少索引項目需要的儲存空間大小,有利於降低執行應用程式的成本。實體若含已排除的屬性,就會表現出與未設定屬性相同的行為:如使用已排除的屬性為查詢的篩選條件或排序順序,絕對不會與該屬性相符。

以下範例是從索引中排除 description 屬性:

C#

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore C# API 參考說明文件

Entity task = new Entity()
{
    Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
    ["category"] = "Personal",
    ["created"] = new DateTime(1999, 01, 01, 0, 0, 0, DateTimeKind.Utc),
    ["done"] = false,
    ["priority"] = 4,
    ["percent_complete"] = 10.0,
    ["description"] = new Value()
    {
        StringValue = "Learn Cloud Datastore",
        ExcludeFromIndexes = true
    },
};

Go

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Go API 參考說明文件

type Task struct {
	Category        string
	Done            bool
	Priority        int
	Description     string `datastore:",noindex"`
	PercentComplete float64
	Created         time.Time
}
task := &Task{
	Category:        "Personal",
	Done:            false,
	Priority:        4,
	Description:     "Learn Cloud Datastore",
	PercentComplete: 10.0,
	Created:         time.Now(),
}

Java

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Java API 參考說明文件

Entity task = Entity.newBuilder(taskKey)
    .set("category", "Personal")
    .set("created", Timestamp.now())
    .set("done", false)
    .set("priority", 4)
    .set("percent_complete", 10.0)
    .set("description",
      StringValue.newBuilder("Learn Cloud Datastore").setExcludeFromIndexes(true).build())
    .build();

Node.js

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Node.js API 參考說明文件

const task = [
  {
    name: 'category',
    value: 'Personal',
  },
  {
    name: 'created',
    value: new Date(),
  },
  {
    name: 'done',
    value: false,
  },
  {
    name: 'priority',
    value: 4,
  },
  {
    name: 'percent_complete',
    value: 10.0,
  },
  {
    name: 'description',
    value: 'Learn Cloud Datastore',
    excludeFromIndexes: true,
  },
];

PHP

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore PHP API 參考說明文件

$task = $datastore->entity(
    $key,
    [
        'category' => 'Personal',
        'created' => new DateTime(),
        'done' => false,
        'priority' => 4,
        'percent_complete' => 10.0,
        'description' => 'Learn Cloud Datastore'
    ],
    ['excludeFromIndexes' => ['description']]
);

Python

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Python API 參考說明文件

task = datastore.Entity(
    key,
    exclude_from_indexes=['description'])
task.update({
    'category': 'Personal',
    'description': 'Learn Cloud Datastore',
    'created': datetime.datetime.utcnow(),
    'done': False,
    'priority': 4,
    'percent_complete': 10.5,
})

Ruby

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Ruby API 參考說明文件

task = datastore.entity "Task" do |t|
  t["category"] = "Personal"
  t["created"] = Time.now
  t["done"] = false
  t["priority"] = 4
  t["percent_complete"] = 10.0
  t["description"] = "Learn Cloud Datastore"
  t.exclude_from_indexes! "description", true
end

GQL

不適用

如果排除 description 屬性,以下範例中的查詢將不會傳回任何結果:

C#

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore C# API 參考說明文件

Query query = new Query("Task")
{
    Filter = Filter.Equal("description", "Learn Cloud Datastore")
};

Go

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Go API 參考說明文件

query := datastore.NewQuery("Tasks").Filter("Description =", "A task description")

Java

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Java API 參考說明文件

Query<Entity> query = Query.newEntityQueryBuilder()
    .setKind("Task")
    .setFilter(PropertyFilter.eq("description", "A task description"))
    .build();

Node.js

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Node.js API 參考說明文件

const query = datastore
  .createQuery('Task')
  .filter('description', '=', 'A task description.');

PHP

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore PHP API 參考說明文件

$query = $datastore->query()
    ->kind('Task')
    ->filter('description', '=', 'A task description.');

Python

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Python API 參考說明文件

query = client.query(kind='Task')
query.add_filter('description', '=', 'Learn Cloud Datastore')

Ruby

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Ruby API 參考說明文件

query = datastore.query("Task")
                 .where("description", "=", "A task description.")

GQL


# Will not return any results!
SELECT * FROM Task WHERE description = 'A task description.'

之後您可以將屬性改回已編入索引。

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

  1. 查詢 (get) 實體。
  2. 將實體寫入 (put) 回資料庫。

同樣地,若將已編入索引的屬性變更為已排除的屬性,只會影響之後寫入資料庫的實體。任何具有該屬性的既有實體,其索引項目仍然會繼續存在,直到更新或刪除該實體為止。為避免出現不必要的結果,您必須清除所有依 (已排除的) 屬性篩選或排序之查詢的程式碼。

索引限制

針對可以與單一實體相關聯的索引項目,Cloud Firestore (Datastore 模式) 會限制數量和總體大小。這些限制相當寬鬆,大多數應用程式都不受影響。不過,有些情況下您可能會受到限制。

前文所述,Datastore 模式資料庫會在每個實體各屬性的預先定義索引中建立一個項目,但不含您已明確宣告為排除於索引之外的屬性。該屬性也可能列於索引設定檔 (index.yaml) 中宣告的外加自訂索引中。如果實體並無任何清單屬性,則這類自訂索引 (非祖系索引) 最多各會有一個項目,如為祖系索引,則該實體的每個祖系會各有一個項目。每次屬性時出現變化時,皆須更新其中每一個索引項目。

若每個實體的某個屬性只有單一值,則每個實體只能將每個可能的值儲存在其預先定義索引中一次。即便如此,如實體包含大量此類單一值屬性,仍有可能會超過索引項目或大小限制。同樣地,若實體可以包含同一個屬性的多個值,每個值必須各有一個索引項目;而若可能值的數字太大,實體就有可能超過項目限制。

若實體含多個屬性,其中每一個屬性都能包含多個值,情況就會更糟糕。索引須針對每一個可能的屬性值「組合」編入一個項目,才能包含此類實體。如果自訂索引參照多個屬性,且每個屬性各含多個值,其組合可能會形成「爆炸式索引」,一個實體必須要含大量項目,而可能的屬性值數目則相對較少。此類「爆炸式索引」可能會大幅增加實體的儲存空間大小,因為必須儲存大量索引項目。爆炸式索引也很容易導致實體超出索引項目數目或大小限制。

請考慮使用以下程式碼:

C#

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore C# API 參考說明文件

Entity task = new Entity()
{
    Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
    ["tags"] = new ArrayValue() { Values = { "fun", "programming", "learn" } },
    ["collaborators"] = new ArrayValue() { Values = { "alice", "bob", "charlie" } },
    ["created"] = DateTime.UtcNow
};

Go

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Go API 參考說明文件

task := &Task{
	Tags:          []string{"fun", "programming", "learn"},
	Collaborators: []string{"alice", "bob", "charlie"},
	Created:       time.Now(),
}

Java

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Java API 參考說明文件

Entity task = Entity.newBuilder(taskKey)
    .set("tags", "fun", "programming", "learn")
    .set("collaborators", "alice", "bob", "charlie")
    .set("created", Timestamp.now())
    .build();

Node.js

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Node.js API 參考說明文件

const task = {
  method: 'insert',
  key: datastore.key('Task'),
  data: {
    tags: ['fun', 'programming', 'learn'],
    collaborators: ['alice', 'bob', 'charlie'],
    created: new Date(),
  },
};

PHP

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore PHP API 參考說明文件

$task = $datastore->entity(
    $datastore->key('Task'),
    [
        'tags' => ['fun', 'programming', 'learn'],
        'collaborators' => ['alice', 'bob', 'charlie'],
        'created' => new DateTime(),
    ]
);

Python

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Python API 參考說明文件

task = datastore.Entity(client.key('Task'))
task.update({
    'tags': [
        'fun',
        'programming',
        'learn'
    ],
    'collaborators': [
        'alice',
        'bob',
        'charlie'
    ],
    'created': datetime.datetime.utcnow()
})

Ruby

如要瞭解如何安裝及使用 Cloud Datastore 的用戶端程式庫,請參閱 Cloud Datastore 用戶端程式庫相關說明。詳情請參閱 Cloud Datastore Ruby API 參考說明文件

task = datastore.entity "Task" do |t|
  t["tags"] = ["fun", "programming", "learn"]
  t["collaborators"] = ["alice", "bob", "charlie"]
  t["created"] = Time.now
end

GQL

不適用

會建立一個 Task 實體,其中包含三個 tags 屬性值、三個 collaborators 屬性值,created 則設為目前日期。這樣就需要 9 個索引項目 (每個可能的屬性值組合各一個):

('fun', 'alice', NOW())
(, 'bob', NOW())
(, 'charlie', NOW())

('programming', 'alice', NOW())
(, 'bob', NOW())
(, 'charlie', NOW())

('learn', 'alice', NOW())
(, 'bob', NOW())
(, 'charlie', NOW())

多次重複同一個屬性後,Cloud Firestore (Datastore 模式) 可能會偵測到爆炸式索引並建議替代索引。然而,在所有其他情況下 (例如本範例定義的查詢),Datastore 模式資料庫均會產生爆炸式索引。在此情況下,您可以在索引設定檔中手動設定索引,避免出現爆炸式索引:

indexes:
- kind: Task
  properties:
  - name: tags
  - name: created
- kind: Task
  properties:
  - name: collaborators
  - name: created

這樣會將需要的項目減少到只有 (|tags| * |created| + |collaborators| * |created|) 或 6 個項目,而非 9 個:

('fun', NOW())
('programming', NOW())
('learn', NOW())

('alice', NOW())
('bob', NOW())
('charlie', NOW())

任何會導致索引超過索引項目或大小限制的 commit 作業都會失敗。錯誤文字會說明超過的限制 ("Too many indexed properties""Index entries too large"),以及原因是哪一個自訂索引。如果新索引在建立時會超過任何實體的限制,查詢該索引就會失敗,而該索引也會在 GCP Console 中顯示為 Error 狀態。如何處理這類 Error 索引:

  1. 從索引設定檔 (index.yaml) 移除該索引。
  2. 依照刪除未使用的索引一節的說明,在 gcloud 指令列工具中使用 vacuum_indexes 選項清空該索引。
  3. 執行以下其中一項:
    • 重新制定索引定義及對應的查詢,或者
    • 移除會導致索引爆炸的實體。
  4. 將索引加回 index.yaml
  5. 依照更新索引一節的說明,在 gcloud 指令列工具中使用 update_indexes 選項更新該索引。

若要避免爆炸式索引,可以使用清單屬性避開需要自訂索引的查詢。如上所述,其中包括含多種排序順序的查詢,或是混用等式及不等式篩選條件的查詢。

投影索引

投影查詢要求必須將投影指定的屬性全數納入索引。Cloud Datastore 模擬器會在索引設定檔 index.yaml (這是使用您的應用程式上傳的檔案) 中自動產生需要的索引。

如要盡可能減少必要的索引數目,其中一種方法是一致投影相同的屬性 (即使並非每一次都需要其中所有屬性)。例如,以下查詢需要兩個獨立的索引:

SELECT priority, percent_complete FROM Task

SELECT priority, percent_complete, created FROM Task

然而,如果一律投影 prioritypercent_completecreated 等屬性 (即使是在不需要 created 的情況下),則只需要一個索引。

如欲將現有查詢轉換為投影查詢,若查詢的其他部分尚不包含投影中的屬性,則可能必須建構一個新的索引。例如,假設有一個如下所示的現有查詢:

SELECT * FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

這項查詢需要以下索引:

indexes:
- kind: Task
  properties:
  - name: priority
  - name: percent_complete

如果將這項索引轉換成以下任一個投影查詢:

SELECT created FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

SELECT priority, percent_complete, created FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

就會出現一個新的屬性 (created),因而必須建立新索引:

indexes:
- kind: Task
  properties:
  - name: priority
  - name: percent_complete
  - name: created

SELECT priority, percent_complete FROM Task
WHERE priority > 1
ORDER BY priority, percent_complete

「不會」變更需要的索引,因為現有查詢中已包含投影屬性 prioritypercent_complete

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

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

這個網頁
Cloud Datastore 說明文件