索引是資料庫效能的重要因素。資料庫索引會將資料庫中的項目對應至資料庫中的實際位置,就像書籍索引會將書中主題對應至頁碼一樣。當您查詢資料庫時,資料庫可以使用索引快速找出您要求的項目位置。
本頁面說明 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,手動定義及管理複合式索引。如要進一步瞭解如何建立及管理複合式索引,請參閱「管理索引」一文。
索引模式與查詢範圍
單一欄位和複合式索引的設定方式不同,但兩者都必須需要為索引設定索引模式和查詢範圍。
索引模式
定義索引時,您會為每個已索引欄位選取索引模式。每個欄位的索引模式都可支援該欄位的特定查詢子句。您可以選取下列索引模式:
索引模式 | 說明 |
---|---|
遞增 | 支援 < 、<= 、== 、>= 、> 、!= 、in 和 not-in 等查詢子句,可根據欄位值以遞增順序排序結果。 |
遞減 | 支援欄位上的 < 、<= 、== 、>= 、> 、!= 、in 和 not-in 查詢子句,並支援根據這個欄位值以遞減順序排序結果。 |
陣列包含 | 支援欄位上的 array-contains 和 array-contains-any 查詢子句。 |
向量 | 支援欄位的 FindNearest 查詢子句。 |
查詢範圍
每個索引的範圍為集合或集合群組。這稱為索引的查詢範圍:
- 集合範圍
- Cloud Firestore 預設會建立設有集合範圍的索引。這些索引可支援從單一集合傳回結果的查詢。
- 集合群組範圍
- 集合群組包含集合 ID 相同的所有集合。如要執行集合群組查詢,並傳回從集合群組篩選或排序的結果,您必須建立設有集合群組範圍的對應索引。
預設順序和 __name__
欄位
除了依據為每個欄位指定的索引模式 (遞增或遞減) 排序文件外,索引也會依據每份文件的 __name__
欄位套用最終排序。__name__
欄位的值會設為完整的文件路徑。也就是說,結果集中具有相同欄位值的文件會依文件路徑排序。
根據預設,__name__
欄位的排序方向與索引定義中最後排序的欄位相同。例如:
集合 | 已建立索引的欄位 | 查詢範圍 |
---|---|---|
城市 | __name__ |
名稱、 集合 |
城市 | __name__ |
狀態, 集合 |
城市 | __name__ |
國家/地區, 人口, 集合 |
如要依非預設 __name__
方向排序結果,您必須建立該索引。
索引屬性
以下屬性定義了能讓查詢以最有效率的方式執行的索引:
- 等式篩選器使用的欄位
- 用於排序順序的欄位
- 範圍和不等式篩選器中使用的欄位 (未包含在排序順序中)
- 用於匯總的欄位 (未包含在排序順序、範圍和不等式篩選器中)
Cloud Firestore 會依下列方式計算查詢結果:
- 找出查詢的集合、篩選器屬性、篩選器運算子和排序順序所對應的索引。
- 識別掃描的起始索引位置。起始位置的前置字串為查詢的相等篩選器,結尾則會在第一個
orderBy
欄位中的範圍和不等式篩選器結束。 - 開始掃描索引,傳回符合所有篩選條件的每份文件,直到掃描程序執行下列其中一項操作為止:
- 遇到不符合篩選條件的文件,並確認後續的文件一律不會完全符合篩選條件。
- 掃描到索引結尾處。
- 收集查詢要求的結果數上限。
索引示例
Cloud Firestore 會自動為您建立單一欄位索引,讓應用程式快速支援最基本的資料庫查詢。單欄位索引可讓您根據欄位值和比較器 <
、<=
、==
、>=
、>
和 in
執行簡單的查詢。針對陣列欄位,您可以執行 array-contains
和 array-contains-any
查詢。
為說明這項功能,請從索引建立的角度查看下列範例。下列程式碼片段會在 cities
集合中建立幾個 city
文件,並為每個文件設定 name
、state
、country
、capital
、population
和 tags
欄位:
網路
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-contains
或 array-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
這個應用程式會使用以下查詢。應用程式會為 category
、city
和 editors_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")
索引限制
以下限制適用於索引。如要進一步瞭解配額和限制,請參閱「配額與限制」。
限制 | 說明 |
---|---|
資料庫的複合式索引數量上限 |
|
資料庫的單一欄位設定數量上限 |
一個欄位層級設定可以包含同一個欄位的多個設定。舉例來說,如果單一欄位的索引豁免項目和同一個欄位的存留時間政策,就會視為一項欄位設定計入上限。 |
每個文件的索引項目數量上限 |
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
錯誤),請參閱疑難排解頁面。