首页/数据/NoSQL 速查/唯一索引与重复键报错
MongoDB99 条

唯一索引与重复键报错

唯一索引阻止字段组合重复;写入冲突抛 E11000 duplicate key error,需要业务侧捕获并给出明确错因。

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

唯一索引是什么

唯一索引保证指定字段组合在集合内不重复。单字段写法是 createIndex({ field: 1 }, { unique: true }),多字段则构成复合唯一索引,只有整体组合重复才会冲突,单个字段重复是允许的。写入冲突时服务端抛出 E11000 duplicate key error 并在错误信息里带上冲突的键值,应用侧必须捕获它并翻译成业务语义(例如「该邮箱已注册」)。索引本身也有内存与写入开销,按真实业务约束设置即可。

基本信息

单字段db.users.createIndex({ email: 1 }, { unique: true })
复合createIndex({ tenantId: 1, code: 1 }, { unique: true })
冲突报错E11000 duplicate key error(错误码 11000)
稀疏选项sparse: true 让不含该字段的文档不参与约束
部分索引partialFilterExpression 只约束满足条件的文档
权限需要集合上的 createIndex 权限

典型使用场景

  • 账号唯一:邮箱、手机号、用户名不允许重复注册
  • 业务单号唯一:同一租户下的订单号、单据号不重复
  • 幂等写入:用唯一键配合 upsert 实现重复请求只落一条记录
  • 同步去重:数据重放或迁移时靠唯一索引兜住重复写入
  • 复合业务约束:同一租户下同一编码只允许存在一条配置

创建与冲突处理

创建与冲突处理
db.users.createIndex({ email: 1 }, { unique: true })              // 邮箱唯一

db.users.createIndex({ tenantId: 1, code: 1 }, { unique: true })   // 组合唯一

db.users.createIndex({ phone: 1 }, { unique: true, sparse: true }) // 缺失该字段的文档不受约束

// 捕获冲突并翻译成业务错误
try {
  db.users.insertOne({ email: "a@example.com" })
} catch (e) {
  if (e.code === 11000) { print("邮箱已存在") }   // 唯一键冲突
  else { throw e }
}

选项与报错读法

项含义注意
unique: true字段组合不允许重复集合已有重复数据时建索引会失败
E11000重复键错误码应用必须捕获并翻译,不能直接抛给用户
sparse跳过缺失该字段的文档只跳过缺失,不跳过值为 null
partialFilterExpression只约束满足条件的文档条件之外的重复值不受限
索引构建大集合建索引会持续占资源注意时段与进度,避免叠加业务高峰
缺失字段在唯一索引里按 null 处理,所以同一集合最多只允许一条缺少该字段的文档。若希望缺字段的文档完全不受约束,需要加 sparse 或改用部分索引。
对已有重复数据的集合直接创建唯一索引会失败,需要先按业务规则清理重复(保留哪一条要有明确依据)再建索引,清理前要备份。

易错点

  • 缺失字段按 null 处理,第二条不含该字段的文档会写入失败,很多「莫名 E11000」都来自这里
  • 并发下先 findOne 再 insert 的检查挡不住重复,必须捕获 E11000 或改用带唯一键的 upsert
  • 复合唯一索引只保证组合不重复,误以为单个字段也唯一会导致业务校验出现漏洞
  • 删除或重建唯一索引的窗口内会短暂失去约束,期间写入的重复数据很难事后发现

数据来源与参考

  • MongoDB Manual: Unique Indexes
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌