Cloud Firestore 中的索引類型

索引是資料庫效能的重要因素。資料庫索引會將資料庫中的項目對應至資料庫中的實際位置,就像書籍索引會將書中主題對應至頁碼一樣。當您查詢資料庫時,資料庫可以使用索引快速找出您要求的項目位置。

本頁面說明 Cloud Firestore 使用的兩種索引類型:單一欄位索引複合式索引

索引定義和結構

索引的定義是在指定文件的欄位清單上定義,每個欄位都會有對應的索引模式

索引包含項目,適用於索引定義中的每個欄位。這個索引包括所有文件,這些文件是根據索引,可能是查詢結果的。只有在索引中使用的每個欄位都設有索引值時,文件才會納入索引。如果索引定義參照的欄位在文件中沒有設定值,該文件就不會顯示在索引中。在這種情況下,系統不會將文件傳回為任何以索引為依據的查詢結果。

複合式索引會按照索引定義中指定的順序,依欄位值排序。

每個查詢的索引

如果查詢沒有索引,大多數資料庫會逐一檢索內容,這項作業速度緩慢,且隨著資料庫的成長而變得更加緩慢。Cloud Firestore 會為所有查詢使用索引,確保高查詢效能。因此,查詢效能取決於結果集的大小,而非資料庫中的項目數量。

減少索引管理,專注於應用程式開發

Cloud Firestore 包含可縮短索引管理時間的功能。系統會自動為您建立最基本查詢所需的索引。使用及測試應用程式時,Cloud Firestore 可協助您找出應用程式所需的額外索引,並建立這些索引。

索引類型

Cloud Firestore 使用兩種類型的索引:單一欄位複合型。除了索引的欄位數量之外,單一欄位和複合式索引的管理方式也有所不同。

單一欄位索引

單欄位索引會儲存集合中所有包含特定欄位的文件,並按照排序對應關係排列。單一欄位索引中的每個項目都會記錄文件在特定欄位的值,以及該文件在資料庫中的位置。Cloud Firestore 會使用這些索引執行許多基本查詢。您可以設定資料庫的自動索引設定和索引豁免,藉此管理單一欄位索引。

自動建立索引

根據預設,Cloud Firestore 會自動為文件中的每個欄位和地圖中的每個子欄位,維護單一欄位索引。Cloud Firestore 會為單一欄位索引使用下列預設設定:

  • 對於每個非陣列和非地圖欄位,Cloud Firestore 會定義兩個集合範圍單一欄位索引,一個在遞增模式,另一個在遞減模式。

  • 針對每個地圖欄位,Cloud Firestore 會建立以下項目:

    • 每個非陣列、非地圖子欄位,都有一個集合範圍遞增索引。
    • 每個非陣列、非地圖子欄位,都有一個集合範圍遞減索引。
    • 每個陣列子欄位都有一個集合範圍陣列包含索引。
    • Cloud Firestore 會遞迴索引每個地圖子欄位。
  • 對於文件中的每個陣列欄位,Cloud Firestore 會建立並維護集合範圍陣列包含的索引。

  • 根據預設,系統不會維護具有集合群組範圍的單一欄位索引。

單一欄位索引豁免設定

您可以建立單一欄位索引豁免項目,從自動索引設定中排除特定欄位。索引豁免設定會覆寫整個資料庫的自動索引設定。豁免項目可以啟用單一欄位索引,否則自動索引設定會停用或停用自動索引功能啟用的單一欄位索引。如要瞭解豁免情況可能相當實用,請參閱建立索引的最佳做法

使用 * 欄位路徑值,即可在集合群組中的所有欄位中新增集合層級索引例外狀況。舉例來說,如果是集合群組 comments,請將欄位路徑設為 *,以便比對 comments 集合群組中的所有欄位,並停用集合群組下所有欄位的索引。接著,您可以新增豁免項目,只為查詢所需的欄位建立索引。減少已建立索引的欄位數量可降低儲存空間費用,而且可提高寫入效能。

如果您為地圖欄位建立單一欄位索引豁免設定,地圖的子欄位會繼承這些設定。不過,您可以為特定子欄位定義單一欄位索引豁免項目。如果您刪除子欄位的豁免項目,子欄位將會繼承其父項的豁免設定 (如有),如果沒有父項豁免項目,則會繼承資料庫層級的設定。

如要建立及管理單一欄位索引豁免設定,請參閱管理索引一文。

複合式索引

複合式索引中會儲存集合內所有文件的對應關係,並按照已排序的待建立索引欄位清單整理對應關係。

Cloud Firestore 會使用複合式索引,支援單一欄位索引尚未支援的查詢。

Cloud Firestore 不會像單一欄位索引一樣自動建立複合式索引,因為有大量的可能欄位組合。Cloud Firestore 可協助您在建構應用程式時找出並建立必要的複合索引

每當您嘗試執行索引不支援的查詢時,Cloud Firestore 就會傳回錯誤訊息,並附上連結,讓您建立缺少的索引。

您也可以使用主控台或 Firebase CLI,手動定義及管理複合式索引。如要進一步瞭解如何建立及管理複合式索引,請參閱「管理索引」一文。

索引模式與查詢範圍

單一欄位和複合式索引的設定方式不同,但兩者都必須需要為索引設定索引模式和查詢範圍。

索引模式

定義索引時,您會為每個已索引欄位選取索引模式。每個欄位的索引模式都可支援該欄位的特定查詢子句。您可以選取下列索引模式:

索引模式 說明
遞增 支援 <<===>=>!=innot-in 等查詢子句,可根據欄位值以遞增順序排序結果。
遞減 支援欄位上的 <<===>=>!=innot-in 查詢子句,並支援根據這個欄位值以遞減順序排序結果。
陣列包含 支援欄位上的 array-containsarray-contains-any 查詢子句。
向量 支援欄位的 FindNearest 查詢子句。

查詢範圍

每個索引的範圍為集合或集合群組。這稱為索引的查詢範圍:

集合範圍
Cloud Firestore 預設會建立設有集合範圍的索引。這些索引可支援從單一集合傳回結果的查詢。

集合群組範圍
集合群組包含集合 ID 相同的所有集合。如要執行集合群組查詢,並傳回從集合群組篩選或排序的結果,您必須建立設有集合群組範圍的對應索引。

預設順序和 __name__ 欄位

除了依據為每個欄位指定的索引模式 (遞增或遞減) 排序文件外,索引也會依據每份文件的 __name__ 欄位套用最終排序。__name__ 欄位的值會設為完整的文件路徑。也就是說,結果集中具有相同欄位值的文件會依文件路徑排序。

根據預設,__name__ 欄位的排序方向與索引定義中最後排序的欄位相同。例如:

集合 已建立索引的欄位 查詢範圍
城市 名稱、 __name__ 集合
城市 狀態, __name__ 集合
城市 國家/地區,人口, __name__ 集合

如要依非預設 __name__ 方向排序結果,您必須建立該索引。

索引屬性

以下屬性定義了能讓查詢以最有效率的方式執行的索引:

  • 等式篩選器使用的欄位
  • 用於排序順序的欄位
  • 範圍和不等式篩選器中使用的欄位 (未包含在排序順序中)
  • 用於匯總的欄位 (未包含在排序順序、範圍和不等式篩選器中)

Cloud Firestore 會依下列方式計算查詢結果:

  1. 找出查詢的集合、篩選器屬性、篩選器運算子和排序順序所對應的索引。
  2. 識別掃描的起始索引位置。起始位置的前置字串為查詢的相等篩選器,結尾則會在第一個 orderBy 欄位中的範圍和不等式篩選器結束。
  3. 開始掃描索引,傳回符合所有篩選條件的每份文件,直到掃描程序執行下列其中一項操作為止:
    • 遇到不符合篩選條件的文件,並確認後續的文件一律不會完全符合篩選條件。
    • 掃描到索引結尾處。
    • 收集查詢要求的結果數上限。

索引示例

Cloud Firestore 會自動為您建立單一欄位索引,讓應用程式快速支援最基本的資料庫查詢。單欄位索引可讓您根據欄位值和比較器 <<===>=>in 執行簡單的查詢。針對陣列欄位,您可以執行 array-containsarray-contains-any 查詢。

為說明這項功能,請從索引建立的角度查看下列範例。下列程式碼片段會在 cities 集合中建立幾個 city 文件,並為每個文件設定 namestatecountrycapitalpopulationtags 欄位:

網路
var citiesRef = db.collection("cities");

citiesRef.doc("SF").set({
    name: "San Francisco", state: "CA", country: "USA",
    capital: false, population: 860000,
    regions: ["west_coast", "norcal"] });
citiesRef.doc("LA").set({
    name: "Los Angeles", state: "CA", country: "USA",
    capital: false, population: 3900000,
    regions: ["west_coast", "socal"] });
citiesRef.doc("DC").set({
    name: "Washington, D.C.", state: null, country: "USA",
    capital: true, population: 680000,
    regions: ["east_coast"] });
citiesRef.doc("TOK").set({
    name: "Tokyo", state: null, country: "Japan",
    capital: true, population: 9000000,
    regions: ["kanto", "honshu"] });
citiesRef.doc("BJ").set({
    name: "Beijing", state: null, country: "China",
    capital: true, population: 21500000,
    regions: ["jingjinji", "hebei"] });

假設採用預設的自動索引設定,Cloud Firestore 會為每個非陣列欄位更新一個遞增單一欄位索引、為每個非陣列欄位更新一個遞減單一欄位索引,以及為陣列欄位更新一個陣列單一欄位索引。下表中的每一列都代表單一欄位索引中的項目:

集合 已建立索引的欄位 查詢範圍
城市 名稱 集合
城市 狀態 集合
城市 個國家/地區 集合
城市 大寫 集合
城市 人口 集合
城市 名稱 集合
城市 個州 集合
城市 個國家/地區 集合
城市 大寫 集合
城市 人口 集合
城市 array-contains 個區域 集合

單一欄位索引支援的查詢

您可以使用這些自動建立的單欄索引,執行下列簡單查詢:

網路
const stateQuery = citiesRef.where("state", "==", "CA");
const populationQuery = citiesRef.where("population", "<", 100000);
const nameQuery = citiesRef.where("name", ">=", "San Francisco");

您也可以建立 in 和複合等式 (==) 查詢:

網路
citiesRef.where('country', 'in', ["USA", "Japan", "China"])

// Compound equality queries
citiesRef.where("state", "==", "CO").where("name", "==", "Denver")
citiesRef.where("country", "==", "USA")
         .where("capital", "==", false)
         .where("state", "==", "CA")
         .where("population", "==", 860000)

如果您需要執行使用範圍比較 (<<=>>=) 的複合查詢,或是需要依據不同的欄位排序,則必須為該查詢建立複合索引

array-contains 索引可讓您查詢 regions 陣列欄位:

網路
citiesRef.where("regions", "array-contains", "west_coast")
// array-contains-any and array-contains use the same indexes
citiesRef.where("regions", "array-contains-any", ["west_coast", "east_coast"])

複合式索引支援的查詢

Cloud Firestore 會使用複合式索引,支援單一欄位索引尚未支援的複合式查詢。例如,您需要複合式索引進行下列查詢:

網路
citiesRef.where("country", "==", "USA").orderBy("population", "asc")
citiesRef.where("country", "==", "USA").where("population", "<", 3800000)
citiesRef.where("country", "==", "USA").where("population", ">", 690000)
// in and == clauses use the same index
citiesRef.where("country", "in", ["USA", "Japan", "China"])
         .where("population", ">", 690000)

這些查詢需要下列複合式索引。由於查詢會為 country 欄位使用相等運算子 (==in),因此您可以為此欄位使用遞增或遞減索引模式。根據預設,不等式子句會根據不等式子句中的欄位,以遞增排序方式排序。

集合 已建立索引的欄位 查詢範圍
城市 (或 ) 國家/地區、 人口 集合

如要執行相同的查詢,但以遞減順序排列,則 population 需要額外的複合式索引 (遞減方向):

網路
citiesRef.where("country", "==", "USA").orderBy("population", "desc")

citiesRef.where("country", "==", "USA")
         .where("population", "<", 3800000)
         .orderBy("population", "desc")

citiesRef.where("country", "==", "USA")
         .where("population", ">", 690000)
         .orderBy("population", "desc")

citiesRef.where("country", "in", ["USA", "Japan", "China"])
         .where("population", ">", 690000)
         .orderBy("population", "desc")
集合 已建立索引的欄位 查詢範圍
城市 國家/地區、 人口 集合
城市 國家/地區 人口 集合

為避免索引合併造成效能損失,建議您建立複合式索引,將 array-containsarray-contains-any 查詢與其他子句合併:

網路
citiesRef.where("regions", "array-contains", "east_coast")
         .where("capital", "==", true)

// array-contains-any and array-contains use the same index
citiesRef.where("regions", "array-contains-any", ["west_coast", "east_coast"])
         .where("capital", "==", true)
集合 已建立索引的欄位 查詢範圍
城市 array-contains 標記, (或 ) 大寫 集合

集合群組索引支援的查詢

如要示範具有集合群組範圍的索引,請在部分 city 文件中新增 landmarks 子集合:

網路
var citiesRef = db.collection("cities");

citiesRef.doc("SF").collection("landmarks").doc().set({
    name: "Golden Gate Bridge",
    category : "bridge" });
citiesRef.doc("SF").collection("landmarks").doc().set({
    name: "Golden Gate Park",
    category : "park" });

citiesRef.doc("DC").collection("landmarks").doc().set({
    name: "National Gallery of Art",
    category : "museum" });
citiesRef.doc("DC").collection("landmarks").doc().set({
    name: "National Mall",
    category : "park" });

使用下列設有集合範圍的單一欄位索引,您可以根據 category 欄位查詢單一城市的 landmarks 集合:

集合 已建立索引的欄位 查詢範圍
地標 (或 ) 類別 集合
網路
citiesRef.doc("SF").collection("landmarks").where("category", "==", "park")
citiesRef.doc("SF").collection("landmarks").where("category", "in", ["park", "museum"])

舉例來說,如果您想查詢所有城市的地標,請在包含所有 landmarks 集合的集合群組上執行這項查詢。您也必須啟用 landmarks 單一欄位索引,並設為集合群組範圍:

集合 已建立索引的欄位 查詢範圍
地標 (或 ) 類別 集合群組

啟用這個索引後,您就能查詢 landmarks 集合群組:

網路
var landmarksGroupRef = db.collectionGroup("landmarks");

landmarksGroupRef.where("category", "==", "park")
landmarksGroupRef.where("category", "in", ["park", "museum"])

如要執行集合群組查詢,以便傳回經過篩選或排序的結果,您必須啟用相應的單一欄位或複合索引,並設為集合群組範圍。然而,不會篩選或排序結果的集合群組查詢,不需要任何其他索引定義。

舉例來說,您可以執行下列集合群組查詢,而不需要啟用額外的索引:

網路
db.collectionGroup("landmarks").get()

索引項目

專案設定的索引和文件結構會決定文件的索引項目數量。索引項目會計入索引項目數量上限

以下範例示範文件的索引項目。

文件

/cities/SF

city_name : "San Francisco"
temperatures : {summer: 67, winter: 55}
neighborhoods : ["Mission", "Downtown", "Marina"]

單一欄位索引

  • 城市名稱 ASC
  • 城市名稱 DESC
  • temperatures.summer ASC
  • temperatures.summer DESC
  • temperatures.winter ASC
  • results.winter DESC
  • neighborhoods 陣列包含 (ASC 和 DESC)

複合式索引

  • city_name ASC、neighborhoods ARRAY
  • city_name DESC、neighborhoods ARRAY

索引項目

這項索引設定會為文件產生下列索引項目:

索引 已建立索引的資料
單一欄位索引項目
city_name ASC city_name: "San Francisco"
city_name DESC City_name:「San Francisco」
temperatures.summer ASC temperatures.summer: 67
溫度 s.summer DESC temperatures.summer: 67
temperatures.winter ASC results.winter:55
temperatures.winter DESC temperatures.winter: 55
neighborhoods 陣列包含 ASC 社區:「使命」
neighborhoods 陣列包含 DESC 社區:「Mission」
Communitys Array 包含 ASC 社區:「市中心」
neighborhoods 陣列包含 DESC 社區:「市中心」
neighborhoods 陣列包含 ASC 鄰里:"Marina"
neighborhoods 陣列包含 DESC 鄰里:"Marina"
複合式索引項目
City_name ASC,底座 ARRAY City_name:「舊金山」、社區:「使命」
city_name ASC、neighborhoods ARRAY city_name: "San Francisco", neighborhoods: "Downtown"
city_name ASC、neighborhoods ARRAY city_name: "San Francisco", neighborhoods: "Marina"
City_name DESC,鄰近地區 ARRAY City_name:「舊金山」、社區:「使命」
City_name DESC,鄰近地區 ARRAY city_name: "San Francisco", neighborhoods: "Downtown"
city_name DESC、neighborhoods ARRAY City_name:「舊金山」、社區:「小艇碼頭」

索引與定價

索引會計入應用程式的儲存空間費用。如要進一步瞭解如何計算索引的儲存空間大小,請參閱「索引項目大小」一節。

使用索引合併

雖然 Cloud Firestore 會為每個查詢使用索引,但不一定每個查詢都需要一個索引。如果查詢包含多個相等 (==) 子句和選用的 orderBy 子句,Cloud Firestore 可以重複使用現有的索引。Cloud Firestore 可以合併用於簡單相等篩選器的索引,以建構大型等式查詢所需的複合式索引。

您可以找出可用於合併索引的情況,藉此降低索引費用。舉例來說,在餐廳評分應用程式的 restaurants 集合中:

  • 間餐廳

    • burgerthyme

      name : "Burger Thyme"
      category : "burgers"
      city : "San Francisco"
      editors_pick : true
      star_rating : 4

這個應用程式會使用以下查詢。應用程式會為 categorycityeditors_pick 使用相等子句組合,並一律依照遞增 star_rating 排序:

網路
db.collection("restaurants").where("category", "==", "burgers")
                            .orderBy("star_rating")

db.collection("restaurants").where("city", "==", "San Francisco")
                            .orderBy("star_rating")

db.collection("restaurants").where("category", "==", "burgers")
                            .where("city", "==", "San Francisco")
                            .orderBy("star_rating")

db.collection("restaurants").where("category", "==", "burgers")
                            .where("city", "==" "San Francisco")
                            .where("editors_pick", "==", true )
                            .orderBy("star_rating")

您可以為每個查詢建立索引:

集合 已建立索引的欄位 查詢範圍
餐廳 category、 star_rating 集合
餐廳 座城市, 星級評等 集合
餐廳 類別、 城市、 星等級 集合
餐廳 category, city, editors_pick, star_rating 集合

如要減少索引數量,您可以利用 Cloud Firestore 的等式子句合併索引功能:

集合 已建立索引的欄位 查詢範圍
餐廳 category、 star_rating 集合
餐廳 座城市, 星級評等 集合
餐廳 editors_pick、 star_rating 集合

這組索引不僅較小,還支援額外查詢:

網路
db.collection("restaurants").where("editors_pick", "==", true)
                            .orderBy("star_rating")

索引限制

以下限制適用於索引。如要進一步瞭解配額和限制,請參閱「配額與限制」。

限制 說明
資料庫的複合式索引數量上限
資料庫的單一欄位設定數量上限
  • 200,如果您尚未為 Google Cloud 專案啟用計費功能。

    如果您需要更多配額,則必須Google Cloud 專案啟用計費功能

  • 500 美元 (如果您為 Google Cloud 專案啟用計費功能)。

一個欄位層級設定可以包含同一個欄位的多個設定。舉例來說,如果單一欄位的索引豁免項目和同一個欄位的存留時間政策,就會視為一項欄位設定計入上限。

每個文件的索引項目數量上限

40,000 個

索引項目數量是文件中下列兩種項目的總和:

  • 單一欄位索引項目的數量
  • 複合式索引項目的數量

如要瞭解 Cloud Firestore 如何將文件和一組索引轉換為索引項目,請參閱這個索引項目計數範例

複合式索引中的欄位數量上限 100
索引項目大小上限

7.5 KiB

如要瞭解 Cloud Firestore 計算索引項目大小的方式,請參閱「索引項目大小」一節。

文件的索引項目大小總和上限

8 MiB

大小總計是文件中下列兩種項目的總和:

  • 文件的單一欄位索引項目大小總和
  • 文件的複合式索引項目大小總和
  • 已建立索引的欄位值大小上限

    1500 個位元組

    超過 1500 個位元組的欄位值會遭到截斷。如果查詢中有欄位值遭截斷,系統可能會傳回不一致的結果。

    索引最佳做法

    對於大多數應用程式,您可以使用自動建立索引功能和錯誤訊息連結來管理索引。不過,您可能會在下列情況下新增單一欄位豁免項目:

    案件 說明
    大型字串欄位

    如果有字串欄位經常包含不用於查詢的長字串值,則可將該欄位排除在索引範圍之外,藉此節省儲存空間費用。

    寫入含有序列值的集合速度過快

    如果您為集合中的文件建立索引,以便在這些文件之間以序列方式增加或減少欄位 (例如時間戳記),則集合的寫入頻率上限為每秒 500 次。如果您不依據含有序列值的欄位進行查詢,可以將該欄位排除在索引之外,以便略過這項限制。

    舉例來說,在寫入頻率高的 IoT 用途中,包含有時間戳記欄位的文件集合可能會接近每秒 500 次寫入的限制。

    存留時間欄位

    如果您使用TTL (存留時間) 政策,請注意 TTL 欄位必須是時間戳記。系統預設會為 TTL 欄位啟用索引功能,這可能會影響高流量率的效能。最佳做法是為 TTL 欄位新增單一欄位豁免項目。

    大型陣列或對應關係欄位

    大型陣列或對應欄位可能會達到每份文件 40,000 個索引項目的上限。如果您不是根據大型陣列或對應欄位進行查詢,請將該欄位排除在索引之外。

    如果您在多個欄位上使用包含範圍和不等於運算子的查詢,請參閱索引考量,以便針對 Cloud Firestore 查詢的效能和成本進行最佳化調整

    如要進一步瞭解如何解決索引問題 (索引分支、INVALID_ARGUMENT 錯誤),請參閱疑難排解頁面