索引總覽
索引是影響資料庫效能的重要因素。就像書籍的索引會將書中的主題對應至頁碼,資料庫索引也會將資料庫中的項目對應至資料庫中的位置。查詢資料庫時,資料庫可使用索引快速找出您要求的項目位置。
本頁說明 Firestore 使用的兩種索引:單一欄位索引和複合式索引。
索引定義和結構
索引的定義是以指定文件的欄位清單為準,每個欄位各有其對應的索引模式。
索引定義中指定的每個欄位,索引都會包含一個項目。索引包含所有文件,這些文件可能是以索引為基礎的查詢結果。只有在文件中,索引中使用的每個欄位都設有索引值時,該文件才會納入索引。如果索引定義參照的文件欄位未設定任何值,該文件就不會出現在索引中。在這種情況下,根據索引進行的任何查詢都不會傳回該文件。
複合式索引會依據欄位值排序,順序則取決於索引定義。
每個查詢背後都有索引
如果查詢沒有索引,大多數資料庫會逐一檢索內容項目,這個過程很緩慢,而且資料庫越大,速度就越慢。Firestore 會對所有查詢使用索引,確保查詢效能優異。因此查詢效能取決於結果集的大小,而非資料庫中的項目數量。
減少索引管理工作,專注於應用程式開發
Firestore 內建多項功能,可減少您管理索引所需的時間。系統會自動為您建立最基本查詢所需的索引。您使用及測試應用程式時,Firestore 會協助您找出並建立應用程式所需的其他索引。
索引類型
Firestore 使用兩種索引:單一欄位和複合式。除了索引欄位數量外,單一欄位索引和複合式索引的管理方式也不同。
單一欄位索引
單一欄位索引會儲存集合中所有包含特定欄位的文件對應關係,並按照該欄位排序。單一欄位索引中的每個項目都會記錄特定欄位的文件值,以及文件在資料庫中的位置。Firestore 會使用這些索引執行許多基本查詢。您可以設定資料庫的自動索引設定和索引豁免項目,藉此管理單一欄位索引。
自動建立索引
根據預設,Firestore 會自動為文件中的每個欄位和對應中的每個子欄位維護單一欄位索引。Firestore 會為單一欄位索引套用下列預設設定:
針對每個非陣列和非對應欄位,Firestore 會定義兩個集合範圍單一欄位索引,一個為遞增模式,另一個為遞減模式。
針對每個對應欄位,Firestore 會建立下列項目:
- 每個非陣列、非對應子欄位各有一個集合範圍的遞增索引。
- 每個非陣列和非對應子欄位各有一個集合範圍的遞減索引。
- 每個陣列子欄位各有一個集合範圍的 array-contains 索引。
- Firestore 會以遞迴方式為每個對應子欄位建立索引。
針對文件中的每個陣列欄位,Firestore 會建立並維護集合範圍的 array-contains 索引。
系統預設不會維護集合群組範圍的單一欄位索引。
單一欄位索引豁免設定
您可以建立單一欄位索引豁免項目,從自動索引設定中豁免欄位。索引豁免設定會覆寫整個資料庫的自動索引設定。如果自動索引設定會停用某個單一欄位索引,您可以透過豁免設定啟用該索引;如果自動索引設定會啟用某個單一欄位索引,則可透過豁免設定停用該索引。如要瞭解豁免的適用情況,請參閱索引最佳做法。
使用 *
欄位路徑值,在集合群組的所有欄位中新增集合層級的索引豁免。舉例來說,如要為集合群組 comments
設定欄位路徑,請將欄位路徑設為 *
,比對 comments
集合群組中的所有欄位,並停用集合群組中所有欄位的索引。然後新增豁免項目,只為查詢所需的欄位建立索引。減少索引欄位數量可降低儲存費用,並提升寫入效能。
如果您為地圖欄位建立單一欄位索引豁免設定,地圖的子欄位會沿用這些設定。不過,您可以為特定子欄位定義單一欄位索引豁免項目。如果刪除子欄位的豁免項目,子欄位會繼承上層欄位的豁免項目設定 (如有),或資料庫的整體設定 (如無上層欄位的豁免項目)。
如要建立及管理單一欄位索引豁免設定,請參閱「管理索引」。
複合式索引
複合式索引中會儲存集合內所有文件的對應關係,並按照已排序的待建立索引欄位清單整理對應關係。
Firestore 會使用複合式索引,支援單一欄位索引尚未支援的查詢。
由於欄位組合的可能性太多,Firestore 不會像單一欄位索引一樣自動建立複合索引。Firestore 會在您建構應用程式時,協助找出並建立必要的複合式索引。
如果您嘗試執行索引不支援的查詢,Firestore 會傳回錯誤訊息,並附上連結,方便您建立缺少的索引。
您也可以使用控制台或 Firebase CLI 手動定義及管理複合索引。如要進一步瞭解如何建立及管理複合索引,請參閱「管理索引」。
索引模式和查詢範圍
單一欄位索引和複合式索引的設定方式不同,但兩者都必須為索引設定索引模式和查詢範圍。
索引模式
定義索引時,請為每個建立索引的欄位選取索引模式。每個欄位的索引模式都支援該欄位的特定查詢子句。您可以選取下列索引模式:
索引模式 | 說明 |
---|---|
遞增 | 支援 < 、<= 、== 、>= 、> 、!= 、in 和 not-in ,可查詢欄位子句,並根據這個欄位值,以遞增順序排序結果。 |
遞減 | 支援欄位上的 < 、<= 、== 、>= 、> 、!= 、in 和 not-in 查詢子句,並支援根據這個欄位值,以遞減順序排序結果。 |
array-contains | 支援欄位中的 array-contains 和 array-contains-any 查詢子句。 |
向量 | 支援欄位上的 FindNearest 查詢子句。 |
查詢範圍
每個索引的範圍都是集合或集合群組。這就是索引的查詢範圍:
- 集合範圍
- Firestore 預設會建立具有集合範圍的索引。這些索引支援從單一集合傳回結果的查詢。
- 集合群組範圍
- 集合群組包含集合 ID 相同的所有集合。如要執行集合群組查詢,從集合群組傳回經過篩選或排序的結果,您必須建立相應的索引,並設定集合群組範圍。
預設排序方式和 __name__
欄位
除了依據為每個欄位指定的索引模式 (遞增或遞減) 排序文件外,索引還會依據每份文件的 __name__
欄位進行最終排序。__name__
欄位的值會設為完整的文件路徑。也就是說,結果集中具有相同欄位值的文件會依文件路徑排序。
根據預設,__name__
欄位的排序方向與索引定義中最後排序的欄位相同。例如:
集合 | 已編入索引的欄位 | 查詢範圍 |
---|---|---|
cities | __name__ |
name, 集合 |
cities | __name__ |
state, 集合 |
cities | __name__ |
國家/地區、 人口、 集合 |
如要依非預設的 __name__
方向排序結果,您需要建立該索引。
索引屬性
可讓查詢以最高效率執行的索引是由下列屬性定義:
- 等式篩選器使用的欄位
- 排序順序使用的欄位
- 範圍和不等式篩選器中使用的欄位 (尚未納入排序順序)
- 用於彙整的欄位 (不包含已納入排序順序、範圍和不等式篩選器的欄位)
Firestore 會依下列方式計算查詢結果:
- 找出查詢的集合、篩選屬性、篩選運算子以及排序順序所對應的索引。
- 識別掃描開始的索引位置。起始位置會加上查詢的等式篩選條件,並以第一個
orderBy
欄位的範圍和不等式篩選條件結尾。 - 開始掃描索引,傳回符合所有篩選條件的每份文件,直到掃描程序執行下列其中一項操作:
- 遇到不符合篩選條件的文件,並確認後續文件永遠不會完全符合篩選條件。
- 掃描到索引結尾處。
- 收集查詢所要求的結果數上限。
索引範例
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"] });
假設採用預設的自動索引設定,Firestore 會為每個非陣列欄位更新一個遞增單一欄位索引、為每個非陣列欄位更新一個遞減單一欄位索引,並為陣列欄位更新一個 array-contains 單一欄位索引。下表中的每一列都代表單一欄位索引中的項目:
集合 | 已建立索引的欄位 | 查詢範圍 |
---|---|---|
cities | 名稱 | 集合 |
cities | 個州/省 | 集合 |
cities | 個國家/地區 | 集合 |
cities | capital | 集合 |
cities | 人口 | 集合 |
cities | 名稱 | 集合 |
cities | 個州/省 | 集合 |
cities | 個國家/地區 | 集合 |
cities | capital | 集合 |
cities | 人口 | 集合 |
cities | 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"])
複合式索引支援的查詢
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
),因此您可以對這個欄位使用遞增或遞減索引模式。根據預設,不等式子句會根據不等式子句中的欄位套用遞增排序順序。
集合 | 已編入索引的欄位 | 查詢範圍 |
---|---|---|
cities | (或 ) 國家/地區、 人口 | 集合 |
如要執行相同的查詢,但採用遞減排序順序,您需要為 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")
集合 | 已編入索引的欄位 | 查詢範圍 |
---|---|---|
cities | 個國家/地區、 個人口 | 集合 |
城市 | country, population | 集合 |
為避免索引合併導致效能降低,建議您建立複合式索引,將 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)
集合 | 已編入索引的欄位 | 查詢範圍 |
---|---|---|
cities | array-contains 標記、 | (或 ) 大寫集合 |
集合群組索引支援的查詢
如要示範具有集合群組範圍的索引,請將 landmarks
子集合新增至部分 city
文件:
網頁
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"]
單一欄位索引
- city_name ASC
- city_name DESC
- temperatures.summer ASC
- temperatures.summer DESC
- temperatures.winter ASC
- temperatures.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 |
temperatures.summer DESC | temperatures.summer: 67 |
temperatures.winter ASC | temperatures.winter: 55 |
temperatures.winter DESC | temperatures.winter: 55 |
neighborhoods 陣列包含 ASC | 鄰近地區:「Mission」 |
neighborhoods 陣列包含 DESC | 鄰近地區:「Mission」 |
neighborhoods 陣列包含 ASC | 鄰里:「市中心」 |
neighborhoods 陣列包含 DESC | 鄰里:「市中心」 |
neighborhoods 陣列包含 ASC | neighborhoods: "Marina" |
neighborhoods 陣列包含 DESC | neighborhoods: "Marina" |
複合式索引項目 | |
city_name ASC, neighborhoods ARRAY | city_name: "San Francisco", neighborhoods: "Mission" |
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, neighborhoods ARRAY | city_name: "San Francisco", neighborhoods: "Mission" |
city_name DESC, neighborhoods ARRAY | city_name: "San Francisco", neighborhoods: "Downtown" |
city_name DESC, neighborhoods ARRAY | city_name: "San Francisco", neighborhoods: "Marina" |
索引和定價
索引會增加應用程式的儲存空間費用。如要進一步瞭解如何計算索引的儲存空間大小,請參閱「索引項目大小」一節。
使用索引合併
雖然 Firestore 會對每項查詢使用索引,但並非每項查詢都需要一個索引。對於具有多個等式 (==
) 子句的查詢,以及 (選擇性) orderBy
子句,Firestore 可以重複使用現有索引。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")
您可以為每個查詢建立索引:
集合 | 已編入索引的欄位 | 查詢範圍 |
---|---|---|
餐廳 | 類別、 star_rating | 集合 |
餐廳 | 個城市、 星級評等 | 集合 |
餐廳 | 類別、 城市、 星等 | 集合 |
餐廳 | 類別、 城市、 編輯精選、 星等 | 集合 |
更好的解決方法是利用 Firestore 合併等值子句索引的功能,減少索引數量:
集合 | 已編入索引的欄位 | 查詢範圍 |
---|---|---|
餐廳 | 類別、 star_rating | 集合 |
餐廳 | 個城市、 星級評等 | 集合 |
餐廳 | editors_pick、 star_rating | 集合 |
這組索引不僅較小,也支援額外查詢:
網頁
db.collection("restaurants").where("editors_pick", "==", true) .orderBy("star_rating")
索引限制
以下限制適用於索引。如要進一步瞭解配額和限制,請參閱配額和限制。
本頁面說明 Firestore 的要求配額與限制。
限制 | 說明 |
---|---|
資料庫的複合式索引數量上限 |
|
資料庫的單一欄位設定數量上限 |
一個欄位層級設定可包含相同欄位的多項設定。舉例來說,如果同一欄位同時有單一欄位索引豁免設定和存留時間政策,則會計入上限,視為一項欄位設定。 |
每個文件的索引項目數量上限 |
40,000 個 索引項目數量是文件中下列兩種項目的總和:
如要瞭解 Firestore 如何將文件和一組索引轉換為索引項目,請參閱這個索引項目計數範例。 |
複合式索引的欄位數量上限 | 100 |
索引項目大小上限 |
7.5 KiB 如要瞭解 Firestore 計算索引項目大小的方式,請參閱索引項目大小一節。 |
文件的索引項目大小總和上限 |
8 MiB 大小總計是文件中下列兩種項目的總和: |
已建立索引的欄位值大小上限 |
1500 個位元組 超過 1500 個位元組的欄位值會遭到截斷。如果查詢中有欄位值遭截斷,系統可能會傳回不一致的結果。 |
建立索引的最佳做法
對於大多數應用程式,您可以依賴自動建立索引功能,並透過錯誤訊息連結管理索引。不過,在下列情況下,您可能需要新增單一欄位豁免項目:
案件 | 說明 |
---|---|
大型字串欄位 | 如果字串欄位經常保存您不查詢的長字串值,您可以免除該欄位的索引,藉此節省儲存空間費用。 |
對含有序列值的文件集合進行高速寫入 | 如果您為集合中文件之間依序遞增或遞減的欄位建立索引 (例如時間戳記),則該集合的寫入頻率上限為每秒 500 次寫入。如果不是根據具有連續值的欄位查詢,可以將該欄位從索引中排除,藉此略過這項限制。 舉例來說,在寫入頻率較高的 IoT 用途,如果集合中的文件含有時間戳記欄位,可能會接近每秒 500 次寫入的上限。 |
存留時間欄位 |
如果使用 TTL (存留時間) 政策,請注意 TTL 欄位必須是時間戳記。系統預設會對存留時間欄位建立索引,但這可能會在高流量時影響效能。最佳做法是為存留時間欄位新增單一欄位豁免項目。 |
大型陣列或對應欄位 | 大型陣列或對應欄位可能會接近每個文件 40,000 個索引項目的上限。如果不是根據大型陣列或對應欄位查詢,則應將其排除在索引之外。 |
如果您在多個欄位上使用範圍和不等於運算子查詢,請參閱索引編製注意事項,瞭解如何最佳化 Firestore 查詢的效能和費用
如要進一步瞭解如何解決索引問題 (索引扇出、INVALID_ARGUMENT
錯誤),請參閱疑難排解頁面。