索引是影響資料庫效能的重要因素。就像書籍索引會將書中主題對應至頁碼一樣,資料庫索引也會將資料庫中的項目對應至其在資料庫中的位置。查詢資料庫時,資料庫可以使用索引快速找出所要求項目的位置。
本頁面說明 Cloud Firestore 使用的兩種索引類型:自動索引和手動索引。
索引定義和結構
索引是根據特定文件的欄位清單定義,每個欄位都有對應的索引模式。
索引包含索引定義中每個欄位的項目。索引包含所有文件,這些文件是根據索引查詢的潛在結果。只有在文件的每個索引欄位都設有索引值時,文件才會納入索引。如果索引定義參照的欄位在文件中沒有設定值,該文件就不會顯示在索引中。在這種情況下,系統不會根據索引傳回任何查詢結果。
索引會依欄位值排序,順序則依索引定義中指定的順序。
每筆查詢背後的索引
如果查詢沒有索引,大多數資料庫會逐項檢索內容,這個過程很慢,而且隨著資料庫成長,速度會更慢。Cloud Firestore 會為「所有」查詢使用索引,協助提供高查詢效能。因此,查詢效能取決於結果集的大小,而非資料庫中的項目數量。
減少索引管理,專注開發應用程式
Cloud Firestore 包含多項功能,可減少您管理索引所需的時間。系統會自動為您建立最基本查詢所需的索引。在您使用及測試應用程式時,Cloud Firestore會協助您找出並建立應用程式所需的其他索引。
索引類型
Cloud Firestore 使用兩種索引:自動和手動。手動和自動索引的管理方式不同。
自動索引
根據預設,Cloud Firestore 會自動為集合中文件內的每個欄位建立索引。這些單一欄位索引可讓您執行許多基本查詢。如要管理自動索引,請調整資料庫的自動索引設定和索引豁免項目。
自動索引預設值
Cloud Firestore 會對自動索引使用下列預設設定:
對於每個非陣列和非對應欄位,Cloud Firestore 會定義兩個集合範圍索引,一個為遞增模式,另一個為遞減模式。
針對每個地圖欄位,Cloud Firestore 會建立下列項目:
- 每個非陣列、非對應子欄位,都有一個集合範圍遞增索引。
- 每個非陣列、非對應子欄位,都有一個集合範圍遞減索引。
- 整個地圖值有一個集合範圍遞增索引
- 一個集合範圍遞減索引,用於整個地圖值
- 每個陣列子欄位都有一個集合範圍的 array-contains 索引。
- Cloud Firestore 會以遞迴方式為每個地圖子欄位建立索引。
針對文件中的每個陣列欄位,Cloud Firestore 會建立下列項目:
- 一個集合範圍遞增索引,用於整個陣列值
- 一個集合範圍遞減索引,用於整個陣列值
- 一個集合範圍 array-contains 索引。
根據預設,系統不會維護集合群組範圍的自動索引。
自動索引豁免項目
您可以建立索引豁免項目,將欄位從自動索引設定中排除。索引豁免設定會覆寫整個資料庫的自動索引設定。如果自動索引設定會停用某個索引,您可以透過豁免設定啟用該索引;如果自動索引設定會啟用某個索引,則可透過豁免設定停用該索引。如需豁免條件的實用案例,請參閱索引最佳做法。
使用 * 欄位路徑值,在集合群組的所有欄位中新增集合層級索引豁免。舉例來說,如果是集合群組 comments,請將欄位路徑設為 *,以比對 comments 集合群組中的所有欄位,並停用集合群組下所有欄位的索引功能。接著,您可以新增豁免項目,只為查詢所需的欄位建立索引。減少已建立索引的欄位數量,可降低儲存費用,並提升寫入效能。
如果您為地圖欄位建立索引豁免項目,地圖的子欄位會沿用這些設定。不過,您可以為特定子欄位定義索引豁免項目。如果刪除子欄位的豁免項目,子欄位會沿用父項的豁免項目設定 (如有),或沿用資料庫層級設定 (如果沒有父項豁免項目)。
如要建立及管理自動索引豁免項目,請參閱「管理索引」。
手動索引
手動索引會儲存集合內所有文件的對應關係,並按照已排序的待建立索引欄位清單加以整理。
Cloud Firestore 會使用手動索引,支援自動索引尚未支援的查詢。
根據預設,Cloud Firestore 會自動為集合中的每個欄位建立單一欄位索引。由於可能的欄位組合數量龐大,Cloud Firestore 不會自動為欄位組合建立索引。相反地,Cloud Firestore 可協助您在建構應用程式時找出並建立必要的索引。
每當您嘗試執行現有索引不支援的查詢時,Cloud Firestore 都會傳回錯誤訊息,並附上連結,讓您建立缺少的索引。
您也可以使用控制台或 Firebase CLI,手動定義及管理索引。如要進一步瞭解如何建立及管理手動索引,請參閱「管理索引」。
索引模式和查詢範圍
自動和手動索引的設定方式不同,但兩者都需要設定索引模式和查詢範圍。
索引模式
定義索引時,您需要為每個已建立索引的欄位選取索引模式。每個欄位的索引模式都支援該欄位的特定查詢子句。您可以選取下列任一索引模式:
| 索引模式 | 說明 |
|---|---|
| 遞增 | 支援 <、<=、==、>=、>、!=、in 和 not-in 欄位查詢子句,並支援根據這個欄位值以遞增順序排序結果。 |
| 遞減 | 支援欄位的 <、<=、==、>=、>、!=、in 和 not-in 查詢子句,並支援根據這個欄位值以遞減順序排序結果。 |
| Array‑contains | 支援欄位上的 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 和 regions 欄位:
網頁
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 單一欄位索引。下表中的每一列都代表單一欄位索引中的一個項目:
| 集合 | 已建立索引的欄位 | 查詢範圍 |
|---|---|---|
| 城市 | 名稱 | 集合 |
| 城市 | 州 | 集合 |
| 城市 | 個國家/地區 | 集合 |
| 城市 | 資金 | 集合 |
| 城市 | 人口 | 集合 |
| 城市 | 個區域 | 集合 |
| 城市 | 名稱 | 集合 |
| 城市 | 州 | 集合 |
| 城市 | 個國家/地區 | 集合 |
| 城市 | 資金 | 集合 |
| 城市 | 人口 | 集合 |
| 城市 | 個區域 | 集合 |
| 城市 | 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"])
手動索引支援的查詢
建立手動索引,支援自動單一欄位索引尚未支援的複合式查詢。舉例來說,下列查詢需要手動索引:
網頁
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")| 集合 | 已建立索引的欄位 | 查詢範圍 |
|---|---|---|
| 城市 | 個國家/地區, 人口 | 集合 |
| cities | 國家/地區, 人口 | 集合 |
為避免索引合併導致效能降低,建議您建立索引,將 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"]
自動索引
- city_name ASC
- city_name DESC
- neighborhoods ASC
- neighborhoods DESC
- temperatures ASC
- temperatures DESC
- temperatures.summer ASC
- temperatures.summer DESC
- temperatures.winter ASC
- temperatures.winter DESC
- neighborhoods 陣列包含
手動索引
- city_name ASC, neighborhoods ARRAY
- city_name DESC, neighborhoods ARRAY
索引項目
這項索引設定會為文件產生下列索引項目:
| 索引 | 已建立索引的資料 |
|---|---|
| 自動索引項目 | |
| city_name ASC | city_name: "San Francisco" |
| city_name DESC | city_name: "San Francisco" |
| neighborhoods ASC | neighborhoods: ["Mission", "Downtown", "Marina"] |
| neighborhoods DESC | neighborhoods: ["Mission", "Downtown", "Marina"] |
| temperatures ASC | temperatures: {summer: 67, winter: 55} |
| temperatures DESC | temperatures: {summer: 67, winter: 55} |
| 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 陣列包含 | neighborhoods: "Mission" |
| neighborhoods 陣列包含 | neighborhoods: "Downtown" |
| neighborhoods 陣列包含 | 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" |
索引和定價
索引會產生應用程式的儲存空間費用。如要進一步瞭解如何計算索引的儲存空間大小,請參閱「索引項目大小」。
使用索引合併
雖然 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, city, star_rating | 集合 |
| 餐廳 | category、 city、 editors_pick、 star_rating | 集合 |
更好的解決方案是利用 Cloud Firestore 合併等式子句索引的功能,減少索引數量:
| 集合 | 已建立索引的欄位 | 查詢範圍 |
|---|---|---|
| 餐廳 | 類別, 星級評等 | 集合 |
| 餐廳 | 城市, 星級評等 | 集合 |
| 餐廳 | 編輯首選, 星級評等 | 集合 |
這組索引不僅較小,還支援額外的查詢:
網頁
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 欄位必須是時間戳記。系統預設會啟用存留時間欄位的索引功能,這可能會影響高流量時的效能。最佳做法是為存留時間欄位新增自動索引豁免項目。 |
| 大型陣列或對應欄位 | 大型陣列或對應欄位可能會接近每份文件 40,000 個索引項目的限制。如果不是根據大型陣列或對應欄位進行查詢,請將其排除在索引之外。 |
如果您在多個欄位使用含有範圍和不等運算子的查詢,請參閱索引注意事項,瞭解如何最佳化 Cloud Firestore 查詢的效能和成本。
如要進一步瞭解如何解決索引問題 (索引展開、INVALID_ARGUMENT 錯誤),請參閱疑難排解頁面。