你在 Firestore 写了一段看起来很普通的查询:
const q = query(
collection(db, 'posts'),
where('authorId', '==', uid),
where('status', '==', 'published'),
orderBy('createdAt', 'desc')
);
本地 emulator 跑得好好的,部署到生产爆:
FAILED_PRECONDITION: The query requires an index. You can create
it here: https://console.firebase.google.com/...
这是 Firestore 的硬性规则——任何涉及多字段 where、或 where + orderBy 不同字段的查询,都需要预先创建复合索引。Firestore 没有 PostgreSQL 那种”自动用索引”机制,索引是显式 schema 的一部分。
常见原因
按命中率从高到低:
1. where + orderBy 用了不同字段
// 需要复合索引:(authorId, createdAt)
query(coll, where('authorId', '==', x), orderBy('createdAt', 'desc'))
任何 where 字段 + 非该字段的 orderBy = 需要复合索引。
如何判断:query 里 where 和 orderBy 的字段不一样?
2. 多个不等值 where(!=, <, <=, >, >=, NOT_IN)
// 需要索引
query(coll, where('status', '!=', 'archived'), orderBy('createdAt'))
不等值过滤本身就要索引;多个不等值更要。
如何判断:where 里有 !=, <, >, not-in, array-contains-any 这些?
3. 多个 where + 任意 orderBy
// 需要复合索引:(status, authorId, createdAt)
query(coll,
where('status', '==', 'published'),
where('authorId', '==', x),
orderBy('createdAt', 'desc')
)
两个以上 where(即使都是等值)+ orderBy = 复合索引。
如何判断:where 子句 ≥ 2 个?
4. 生产第一次跑这条查询
emulator 自动建虚拟索引(用得到就建),生产必须手动建。代码合并、deploy、用户第一次访问 → 报错。
如何判断:emulator 正常、生产报错。
5. 索引建好但还在 build 中
新建索引需要几分钟到几小时(看数据量),build 期间查询仍报 FAILED_PRECONDITION。
如何判断:Firebase Console → Firestore → Indexes → 看状态是 “Building” 还是 “Enabled”。
6. 索引建错方向(asc vs desc)
query(coll, where('authorId', '==', x), orderBy('createdAt', 'desc'))
索引必须建成 (authorId asc, createdAt desc)。如果建成 desc + asc 也会 mismatch。
如何判断:报错链接点开看建议的 schema,对比已有索引。
最短修复路径
Step 1:点报错里的 URL 自动创建
FAILED_PRECONDITION: The query requires an index.
You can create it here:
https://console.firebase.google.com/project/your-app/database/firestore/indexes?create_composite=...
URL 已经预填好需要的 schema,登录 Firebase Console 点 “Create Index” 就行。建好后等几分钟到几小时(看数据量)。
Step 2:把索引同步到 firestore.indexes.json
只在 Console 建会丢失(重新 deploy 可能覆盖)。导出到代码:
firebase firestore:indexes > firestore.indexes.json
或手动维护:
{
"indexes": [
{
"collectionGroup": "posts",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "authorId", "order": "ASCENDING" },
{ "fieldPath": "createdAt", "order": "DESCENDING" }
]
}
]
}
部署:
firebase deploy --only firestore:indexes
Step 3:等 build 完成
firebase firestore:indexes --project my-prod
# 或在 Console 看 status: Building / Enabled
数百万 doc 的集合可能要几小时。期间错误不会消失。
Step 4:可以的话简化查询
// 复杂查询(需要复合索引)
query(coll,
where('status', '==', 'published'),
where('authorId', '==', x),
orderBy('createdAt', 'desc')
)
// 简化版(单字段,不需要复合索引)
query(coll,
where('authorId_status', '==', `${x}_published`), // 把两个字段合并成一个
orderBy('createdAt', 'desc')
)
这种”复合 key”技巧能省一个索引,但要保证写入时同步生成 authorId_status。
Step 5:emulator 提前发现
# 本地启 emulator
firebase emulators:start --only firestore
# 跑你的应用 + e2e 测试
# emulator 报 FAILED_PRECONDITION 时把建议的索引加进 firestore.indexes.json
# deploy 前 dry-run 看索引差异
firebase firestore:indexes
Step 6:方向对齐
query(..., orderBy('a', 'desc'), orderBy('b', 'asc'))
对应索引必须 (a DESCENDING, b ASCENDING)。Console 自动生成的链接已经对齐了,手写 firestore.indexes.json 要小心。
预防建议
- 所有新查询先在 emulator 跑一次,把需要的索引提前加进 firestore.indexes.json
- firestore.indexes.json 走 git,PR review 能看到索引变化
- CI 加一步
firebase firestore:indexes --dry-run对比 prod 和代码里的差异 - 大数据集(百万级 doc)新加索引在低峰期,避免 build 影响读
- 查询模式相对稳定的写一份”查询模式 → 索引”对照表,新人参考
- 不要为每个 ad-hoc 查询建索引,定期 audit 删除没用的(每个索引都占存储)
- 复杂查询考虑反范式(把数据冗余到一起),减少多 where 需要