mongodb中unique: true对null失效是因设计上将null视为“缺失值”而跳过校验;需配合sparse: true创建稀疏唯一索引,使索引仅覆盖非空字段,从而真正约束唯一性,但此时null文档将不被索引覆盖。

为什么 unique: true 对 null 值失效
MongoDB 默认把多个 null 值视为“不冲突”,哪怕字段加了 unique: true,也能插入多条 { email: null }。这不是 bug,是设计行为:唯一索引只对**非空值**做排他校验,null 被当作“缺失值”跳过检查。
用 sparse: true 让唯一索引真正覆盖所有写入
要让唯一性约束对 null 也起作用,必须配合 sparse: true —— 它会让索引**只索引有该字段的文档**,从而把 null 或缺失字段的文档彻底排除在索引之外。这样,重复插入 { email: null } 就不会触发唯一冲突,但更重要的是:你本就不该允许 email 为 null。
- 业务上,注册用户必须填邮箱,
email字段应设为 required,数据库层也应拒绝null - 如果字段确实允许为空(比如可选的备用邮箱),那就别建唯一索引,改用应用逻辑 + 业务规则兜底
-
sparse: true和unique: true必须同时存在才有意义;单独用sparse不防重,单独用unique不拦null
建索引前必须清理已有重复数据
如果集合里已经存在多条 email: null 文档,执行 db.users.createIndex({ email: 1 }, { unique: true }) 会直接报错:duplicate key,因为 MongoDB 拒绝在有冲突数据的前提下建唯一索引。
- 先查:
db.users.countDocuments({ email: null })看有多少空值 - 再删或补全:比如用
db.users.updateMany({ email: null }, { $set: { email: "missing@domain.com" } })临时占位 - 确认无重复后再建索引,建完立刻跑
db.users.getIndexes()验证状态是否为"ready"且含"unique": true
Go 驱动中创建稀疏唯一索引的写法
官方驱动 go.mongodb.org/mongo-driver/mongo 创建索引时,sparse 是独立选项,不能漏掉:
collection.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{{"email", 1}},
Options: options.Index().
SetUnique(true).
SetSparse(true). // ← 关键!没有这行,null 还是能插多条
SetName("email_unique_sparse"),
})
注意:一旦用了 sparse,所有 email 为空或缺失的文档都不会进入该索引——查询 { email: "a@b.com" } 仍高效,但 { email: null } 的文档将完全不在索引覆盖范围内。这点常被忽略,导致误以为“索引没生效”。











