MongoDB 索引限制(Index Restrictions)完全指南
核心结论:索引不是越多越好 —— 了解 硬限制 + 性能陷阱 + 设计禁忌,才能避免 写性能崩盘、磁盘爆炸、查询失效。
一、硬性限制(Hard Limits)
| 限制项 | 限制值 | 说明 |
|---|
| 单个集合索引数量 | 64 | 超过报错 |
| 单个索引键大小 | 1024 bytes | 超过无法建索引 |
| 索引命名长度 | 128 字符 | 自动生成可超 |
| 索引总大小 | 无硬限制 | 受磁盘限制 |
| 文档大小 | 16 MB | 含索引键 |
| 排序内存限制 | 100 MB | 超出需磁盘排序 |
二、索引键大小限制(1024 bytes)
失败案例
// 字段过长 → 建索引失败
db.users.createIndex({ long_field: 1 }) // long_field = "a".repeat(2000)
错误:
Index key too large: 2000 bytes > 1024 bytes
解决方案
| 方案 | 说明 |
|---|
| 哈希索引 | createIndex({ field: "hashed" }) |
| 部分索引 | 只索引短值 |
| 字段截断 | 存 hash(field) |
// 推荐:哈希索引
db.users.createIndex({ email: "hashed" })
// 或:部分索引(短 email)
db.users.createIndex(
{ email: 1 },
{ partialFilterExpression: { email: { $lte: "z" } } }
)
三、写性能限制:索引是写放大器
| 操作 | 无索引 | 1 个索引 | 5 个索引 |
|---|
| 插入 | 1× | 2× | 6× |
| 更新索引字段 | 1× | 3× | 10×+ |
实测:5 个索引 → 插入性能下降 80%
建议
- 高并发写入:≤ 3 个索引
- 日志系统:只建 必要索引
- 批量导入:先删索引 → 导入 → 重建
// 导入前删除
db.collection.dropIndexes()
// 导入后重建
db.collection.createIndex({ timestamp: -1 })
四、内存与磁盘限制
| 资源 | 限制 | 影响 |
|---|
| 索引驻留内存 | 工作集 > RAM | 页面错误激增 |
| 索引总大小 | > 可用磁盘 | 无法建新索引 |
| 排序内存 | 100 MB | 超出 → 磁盘排序 → 慢 |
监控命令
// 索引大小
db.collection.stats().indexSize
// 页面错误
db.serverStatus().extra_info.page_faults
// 工作集
db.serverStatus().wiredTiger.cache
五、分片集群索引限制
| 限制 | 说明 |
|---|
| 分片键不能是数组 | 多键索引不可作分片键 |
| 唯一索引需含分片键 | 否则只能在 mongos 上建 |
| hashed 分片键不能复合 | 只能单字段 |
| 标签索引冲突 | 需手动管理 |
// 错误:数组作分片键
sh.shardCollection("mydb.logs", { tags: 1 }) // 失败
// 正确:用 _id 或 hashed
sh.shardCollection("mydb.logs", { _id: "hashed" })
六、索引类型限制
| 类型 | 限制 |
|---|
| 多键索引 | 不能与 hashed 共存 |
| 文本索引 | 每个集合 1 个 |
| 2d 索引 | 不支持 compound |
| TTL 索引 | 只能单字段 + Date 类型 |
| 通配符索引 | 不支持 unique |
// 错误:文本索引重复
db.articles.createIndex({ title: "text" })
db.articles.createIndex({ content: "text" }) // 失败
七、查询与索引匹配限制
| 限制 | 说明 |
|---|
| 前缀原则 | 复合索引只支持前缀查询 |
| 范围中断 | 范围查询后字段不可用 |
| 排序方向不一致 | 无法使用索引 |
// 索引:{ a: 1, b: 1, c: 1 }
// 支持
{ a: 1 }
{ a: 1, b: 1 }
{ a: 1, b: { $gt: 10 } }
// 不支持(b 范围后 c 失效)
{ a: 1, b: { $gt: 10 }, c: 1 }
八、索引构建限制
| 限制 | 说明 |
|---|
| 前台建索引 | 阻塞所有写操作 |
| 后台建索引 | 耗时长,占用 CPU |
| 在线重建 | 不支持修改键 |
// 推荐:后台建
db.collection.createIndex({ field: 1 }, { background: true })
九、索引管理限制
| 操作 | 限制 |
|---|
dropIndex() | 不能删 _id 索引 |
collMod | 不能改索引键 |
reIndex() | 阻塞写 |
十、生产级索引限制 Checklist
| 检查项 | 命令 | 阈值 |
|---|
| 索引数量 | getIndexes().length | ≤ 10 |
| 单个索引大小 | stats().indexSize | < 10GB |
| 写放大 | 压测插入 | < 3× |
| 索引命中率 | serverStatus().indexCounters | > 95% |
| 内存驻留 | cache.bytes currently in the cache | > 70% |
十一、终极案例:避免索引爆炸
// 错误:为每个查询建索引
db.logs.createIndex({ app: 1 })
db.logs.createIndex({ level: 1 })
db.logs.createIndex({ timestamp: 1 })
db.logs.createIndex({ message: 1 }) // 64 个上限!
// 正确:复合 + 部分索引
db.logs.createIndex({
app: 1,
level: 1,
timestamp: -1
}, {
partialFilterExpression: { level: "error" }
})
// 覆盖 90% 查询
十二、索引限制黄金三问
| 问题 | 检查点 |
|---|
| 1. 索引是否必要? | explain() 是否 IXSCAN |
| 2. 索引是否过大? | indexSize > 10GB |
| 3. 写性能是否下降? | 插入 TPS 下降 > 50% |
你现在可以:
// 一行检查索引健康
db.collection.stats().indexSize
db.collection.getIndexes().length
把你的集合贴给我,我 30 秒出索引精简方案:
- 集合名
- 文档数 + 平均大小
- 当前索引(
getIndexes())
回复 3 行输出,我生成:删除冗余索引 + 合并建议 + 性能提升预估!