索引是資料庫效能的重要因素。與將書中的主題對應到頁碼的書籍索引非常相似,資料庫索引將資料庫中的項目對應到它們在資料庫中的位置。當您向資料庫發送查詢時,資料庫可以使用索引來快速找到您要求的項目的位置。
本頁面介紹 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 可協助您在建立應用程式時識別並建立所需的複合索引。
如果您在沒有先建立所需索引的情況下嘗試執行上述查詢,Cloud Firestore 會傳回錯誤訊息,其中包含一個鏈接,您可以點擊該連結來建立缺少的索引。每當您嘗試索引不支援的查詢時,都會發生這種情況。您也可以使用控制台或Firebase CLI手動定義和管理複合索引。有關建立和管理複合索引的更多信息,請參閱管理索引。
索引模式和查詢範圍
單一欄位索引和複合索引的配置方式不同,但兩者都要求您為索引配置索引模式和查詢範圍。
索引模式
定義索引時,您可以為每個索引欄位選擇索引模式。每個欄位的索引模式都支援該欄位上的特定查詢子句。您可以選擇以下索引模式:
索引模式 | 描述 |
---|---|
上升 | 支援對此欄位使用< 、 <= 、 == 、 >= 、 > 、 != 、 in 、 not-in 查詢子句,並支援根據該欄位值對結果進行升序排序。 |
下降 | 支援欄位上的< 、 <= 、 == 、 >= 、 > 、 != 、 in 和not-in 查詢子句,並支援根據該欄位值對結果進行降序排序。 |
數組包含 | 支援欄位上的array-contains 和array-contains-any 查詢子句。 |
查詢範圍
每個索引的作用域是一個集合或一個集合組。這稱為索引的查詢範圍:
- 徵集範圍
- Cloud Firestore 預設會建立具有集合範圍的索引。這些索引支援從單一集合傳回結果的查詢。
- 集合組範圍
- 集合組包含具有相同集合ID的所有集合。若要執行從集合組傳回篩選或排序結果的集合組查詢,您必須建立具有集合組範圍的對應索引。
預設排序和__name__
字段
除了按為每個欄位指定的索引模式(升序或降序)對文件進行排序之外,索引還按每個文件的__name__
欄位套用最終排序。 __name__
欄位的值設定為完整文件路徑。這表示結果集中具有相同欄位值的文件會按文件路徑排序。
預設情況下, __name__
欄位的排序方向與索引定義中最後一個排序欄位的方向相同。例如:
收藏 | 已索引的字段 | 查詢範圍 |
---|---|---|
城市 | __name__ | 名稱, 收藏 |
城市 | __name__ | 狀態, 收藏 |
城市 | __name__ | 國家/地區、 人口、 收藏 |
若要按非預設__name__
方向對結果進行排序,您需要建立該索引。
索引範例
透過自動為您建立單一欄位索引,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)
收藏 | 已索引的字段 | 查詢範圍 |
---|---|---|
城市 | 陣列包含標籤, | (或 )大寫收藏 |
集合組索引支援的查詢
為了示範具有集合組範圍的索引,假設您為某些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
- 夏季 ASC 溫度
- 溫度.夏季 DESC
- 冬季ASC溫度
- 溫度.冬季 DESC
- Neighborhoods 陣列包含(ASC 和 DESC)
綜合指數
- city_name ASC,社群數組
- city_name DESC,街區 ARRAY
索引條目
此索引配置會產生以下 18 個文件索引條目:
指數 | 索引數據 |
---|---|
單一欄位索引條目 | |
城市名稱 ASC | city_name:“舊金山” |
城市名稱 DESC | city_name:“舊金山” |
夏季 ASC 溫度 | 夏季溫度:67 |
溫度.夏季 DESC | 夏季溫度:67 |
冬季ASC溫度 | 冬季溫度:55 |
溫度.冬季 DESC | 冬季溫度:55 |
Neighborhoods 陣列包含 ASC | 社區:“使命” |
Neighborhoods 陣列包含 DESC | 社區:“使命” |
Neighborhoods 陣列包含 ASC | 街區:“市中心” |
Neighborhoods 陣列包含 DESC | 街區:“市中心” |
Neighborhoods 陣列包含 ASC | 社區:“碼頭” |
Neighborhoods 陣列包含 DESC | 社區:“碼頭” |
綜合索引條目 | |
city_name ASC,社群數組 | city_name:“舊金山”,街區:“Mission” |
city_name ASC,社群數組 | city_name:“舊金山”,街區:“市中心” |
city_name ASC,社群數組 | city_name:“舊金山”,街區:“碼頭” |
city_name DESC,街區 ARRAY | city_name:“舊金山”,街區:“Mission” |
city_name DESC,街區 ARRAY | city_name:“舊金山”,街區:“市中心” |
city_name DESC,街區 ARRAY | city_name:“舊金山”,街區:“碼頭” |
指數和定價
索引會增加應用程式的儲存成本。有關如何計算索引存儲大小的更多信息,請參閱索引條目大小。
利用索引合併
儘管 Cloud Firestore 為每個查詢使用一個索引,但它不一定需要每個查詢一個索引。對於具有多個等式 ( ==
) 子句和一個orderBy
子句(可選)的查詢,Cloud Firestore 可以重複使用現有索引。 Cloud Firestore 可以合併簡單相等過濾器的索引,以建立更大的相等查詢所需的複合索引。
您可以透過確定可以利用索引合併的情況來降低索引成本。例如,想像一個餐廳評級應用程式的restaurants
集合:
漢堡百里香
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")
您可以為每個查詢建立一個索引:
收藏 | 已索引的字段 | 查詢範圍 |
---|---|---|
餐廳 | 類別、 star_ rating | 收藏 |
餐廳 | 城市、 star_ rating | 收藏 |
餐廳 | 類別、 城市、 星級 | 收藏 |
餐廳 | 類別、 城市、 editors_pick、 star_ rating | 收藏 |
作為更好的解決方案,您可以利用 Cloud Firestore 合併相等子句索引的功能來減少索引數量:
收藏 | 已索引的字段 | 查詢範圍 |
---|---|---|
餐廳 | 類別、 star_ rating | 收藏 |
餐廳 | 城市、 star_ rating | 收藏 |
餐廳 | editors_pick、 star_ rating | 收藏 |
這組索引不僅更小,還支援額外的查詢:
網路
db.collection("restaurants").where("editors_pick", "==", true) .orderBy("star_rating")
索引限制
以下限制適用於索引。有關所有配額和限制,請參閱配額和限制。
限制 | 細節 |
---|---|
資料庫最大複合索引數 |
|
資料庫單一欄位配置的最大數量 |
一個字段級配置可以包含同一字段的多個配置。例如,單一欄位索引豁免和同一欄位上的 TTL 策略將計為一個欄位配置,以達到限制。 |
每個文件的最大索引條目數 | 40,000 文件的索引條目數是以下各項的總和:
若要了解 Cloud Firestore 如何將文件和一組索引轉換為索引條目,請參閱此索引條目計數範例。 |
複合索引中的最大字段數 | 100 |
索引條目的最大大小 | 7.5 KB 若要了解 Cloud Firestore 如何計算索引條目大小,請參閱索引條目大小。 |
文件索引條目大小的最大總和 | 8 兆位元組 總大小是文件以下各項的總和: |
索引欄位值的最大大小 | 1500字節 超過 1500 位元組的欄位值將被截斷。涉及截斷欄位值的查詢可能會傳回不一致的結果。 |
索引最佳實踐
對於大多數應用程序,您可以依靠自動索引和錯誤訊息連結來管理索引。但是,在以下情況下您可能需要新增單一欄位豁免:
案件 | 描述 |
---|---|
大字串字段 | 如果您有一個經常保存不用於查詢的長字串值的字串字段,則可以透過免除該字段的索引來降低儲存成本。 |
包含具有順序值的文件的集合的高寫入率 | 如果您將集合中的文件之間依序增加或減少的欄位(例如時間戳記)建立索引,則集合的最大寫入速率為每秒 500 次寫入。如果您不根據具有順序值的欄位進行查詢,則可以將該欄位免除索引以繞過此限制。 例如,在具有高寫入速率的 IoT 使用案例中,包含帶有時間戳欄位的文件的集合可能會接近每秒 500 次寫入的限制。 |
TTL字段 | 如果您使用TTL(生存時間)策略,請注意TTL欄位必須是時間戳記。預設啟用 TTL 欄位索引,並且可能會影響較高流量速率下的效能。作為最佳實踐,請為 TTL 欄位新增單一欄位豁免。 |
大型數組或地圖字段 | 大型陣列或映射欄位可能會接近每個文件 40,000 個索引條目的限制。如果您不是基於大型陣列或對應欄位進行查詢,則應將其排除在索引之外。 |
有關如何解決索引問題(索引扇出、 INVALID_ARGUMENT
錯誤)的更多信息,請查看故障排除頁面。