首页/数据/NoSQL 速查/稀疏索引与缺失字段
MongoDB99 条

稀疏索引与缺失字段

sparse:true 的索引跳过不含该字段的文档;适合可选字段较多、只想加速“存在值”查询的集合。

NoSQL 速查数据更新于 2026-10-13

是什么

稀疏索引(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 这类反向条件是天然冲突的:需要找出「没有该字段」的文档时,稀疏索引帮不上忙,仍要全表扫描。
同一个字段上不要既建稀疏索引又建部分索引:两者过滤条件重叠会造成冗余索引,写入时同时维护两份,得不偿失;新场景优先用部分索引,条件写在创建语句里更可读。

易错点

  • 把稀疏索引当成「对 null 值也过滤」:字段显式为 null 的文档仍会进入索引,语义与预期不符时要在应用层统一写入规范
  • 复合稀疏索引按「所有字段都存在」来理解:实际上任一字段存在就会建条目,多字段组合下索引体积会比预想的大
  • 用稀疏索引支撑唯一约束却没检查反向查询:唯一性达成了,但「未填写」的统计查询依然慢
  • 为稀疏字段建普通唯一索引:多条文档字段缺失都会写入 null,第二个缺失文档就会触发重复键错误

数据来源与参考

  • MongoDB Manual: Sparse Indexes
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌