是什么
稀疏索引(sparse index)只收录「该字段存在」的文档,字段完全缺失的文档不会在索引里留下条目。它的目的是让可选字段的索引变小:一个只有少数文档才有的字段,建普通索引会为每条文档都写一个 null 条目,浪费空间又降低选择性。新项目里更常见的替代方案是部分索引,因为后者的过滤条件是显式写出来的。
基本信息
| 创建 | db.users.createIndex({ phone: 1 }, { sparse: true }) |
|---|---|
| 收录规则 | 字段缺失的文档不入索引;字段存在但值为 null 的文档仍会被收录 |
| 复合稀疏索引 | 只要文档含有其中至少一个索引字段,就会写入索引条目 |
| 查询限制 | 反向条件(如 $exists: false)无法利用稀疏索引 |
| 替代方案 | 部分索引:createIndex({ phone: 1 }, { partialFilterExpression: { phone: { $exists: true } } }) |
典型使用场景
- 可选字段的唯一性约束:只对填写了手机号的用户去重,未填写的用户不参与
- 稀疏字段检索:日志里只有少数文档带 traceId,索引越小检索越快
- 控制索引体积:文档量很大但字段覆盖率低时,稀疏或部分索引能显著减少索引存储
- 写入优化:减少索引条目意味着写入时维护成本下降,尤其在批量导入场景
- 迁移过渡:历史文档缺少新字段,先把索引设为稀疏避免全量回填
创建与验证
创建与验证
db.users.createIndex({ phone: 1 }, { sparse: true }) # 只索引填写了 phone 的文档
db.users.getIndexes().find(i => i.name === "phone_1") # 确认 sparse 标记已生效
db.users.find({ phone: { $exists: true, $ne: null } }).explain("executionStats") # 正向条件可命中
db.users.find({ phone: { $exists: false } }).explain("executionStats") # 反向条件必然走扫描
db.users.createIndex({ phone: 1 }, { partialFilterExpression: { phone: { $exists: true } }, unique: true }) # 更明确的等价写法字段取值与索引收录
| 文档形态 | 普通索引 | 稀疏索引 |
|---|---|---|
| 字段存在且有值 | 收录 | 收录 |
| 字段存在但为 null | 收录 null 条目 | 仍收录 null 条目 |
| 字段完全缺失 | 收录 null 条目 | 不收录 |
| 稀疏复合索引部分字段存在 | 收录 | 包含任一字段即收录 |
稀疏索引与 $exists: false 这类反向条件是天然冲突的:需要找出「没有该字段」的文档时,稀疏索引帮不上忙,仍要全表扫描。
同一个字段上不要既建稀疏索引又建部分索引:两者过滤条件重叠会造成冗余索引,写入时同时维护两份,得不偿失;新场景优先用部分索引,条件写在创建语句里更可读。