bigywater.tech
MongoDB · 78

分片集合上唯一索引的限制

分片之后,唯一索引还管用吗?_id 还是唯一的吗?怎样才能保证 email 全局唯一?这篇在两个分片上逐个情形验证。

code 67
分片集合上对非分片键建唯一索引的错误码
[1, 1]
分片键不是 _id 时,同一个 _id 在两个分片上各有一个
code 72
hashed 分片键声明 unique 的错误码

不能随便 unique

实测 · 本机执行输出
  create_index({email: 1}, unique) → code 67: cannot create index with 'unique' or 'prepareUnique' option over { email: 1 } with shard key pattern { uid: 1 } and collation {}", '$clusterTime': {'c

分片集合上对非分片键建唯一索引,服务端直接拒绝(code 67),因为每个分片只能保证自己的那一份。

必须以分片键为前缀

实测 · 本机执行输出
  create_index({uid: 1, email: 1}, unique) → uid_1_email_1
  {email: 1, uid: 1}(分片键不在前缀)→ code 67: cannot create index with 'unique' or 'prepareUnique' option over { email: 1, uid: 1 } with shard key pattern { uid: 1 } and collat

唯一索引要以分片键为前缀({uid, email} 可以,{email, uid} 不行)。

前缀方案的含义

实测 · 本机执行输出
  同一个 uid、同一个 email                 → E11000 E11000 duplicate key error collection: test.u index: uid_1_email_1 dup
  不同 uid(在另一个分片)、同一个 email          → 成功
  同一个分片内不同 uid、同一个 email            → 成功
  现在 email = a@x.com 的文档在各分片上的数量: [2, 1](全局并不唯一)

{uid, email} 的唯一索引只保证同一个 uid 下 email 不重复;不同 uid(在不同分片)可以有同样的 email。

_id

实测 · 本机执行输出
  第二个 _id = "same-id"(uid = 1600,落在另一个分片)→ 插入成功
  _id = "same-id" 的文档在各分片上的数量: [1, 1];经 mongos 查 2 个
  第三个 _id 相同、uid = 20(和第一个同一个分片)→ E11000

分片键不是 _id 时,_id 的唯一性只在单个分片内保证,不同分片上可以有相同的 _id。

全局唯一

实测 · 本机执行输出
  同样的 email → E11000(全局唯一)
  a@x.com 与 z@x.com 分别在各分片上的数量: [1, 0] [0, 1]

让需要唯一的字段做分片键(或其前缀),并在 shardCollection 时指定 unique: true。

哈希

实测 · 本机执行输出
  shardCollection(key={k: "hashed"}, unique=True) → code 72: Hashed shard keys cannot be declared unique. It's possible to ensure uniqueness on the hashed field by declaring an additional (non-hashed) unique index on the field., full error: {'ok': 0.0, 'errmsg': "Hashed shard keys cannot be
  _id 用哈希做分片键:重复 _id → E11000(同一个 _id 哈希后总在同一个分片)

哈希分片键不能声明 unique;但 _id 用哈希分片时,相同的 _id 必然落在同一个分片,实际上全局唯一。

分步演示

分片集合上的唯一索引
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。2 个分片(各为单节点副本集),分片范围手工 split 与 moveChunk 得到
    • 没有验证:chunk 迁移时唯一冲突的处理;分片键后缀的 refineCollectionShardKey;唯一性代理集合的做法(应用层维护一个以唯一字段为分片键的映射集合);事务里跨分片的唯一性检查;MongoDB 对 unique 部分索引在分片集合上的限制
    京ICP备2026067502号-1京公网安备11010502063487号