唯一索引是什么
唯一索引保证指定字段组合在集合内不重复。单字段写法是 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 或改用部分索引。
对已有重复数据的集合直接创建唯一索引会失败,需要先按业务规则清理重复(保留哪一条要有明确依据)再建索引,清理前要备份。