mongodb中出现_id字段重复记录的排查笔记

近期在使用mongodb的过程中遇到一次表中有几百条_id字段重复的记录(相同_id的有两条),着实吓了一大跳,因为_id字段在mongodb里面已经默认创建了唯一索引,理论上是不可能有重复记录的,因此特把排查过程记录下来。

1. 问题定位

    发现这个现象,是在定位一个问题的时候,发现了这批重复脏数据,bug出现的步骤:把一条记录中的某个字段修改后,再执行save方法,由于修改的字段是shard key,且保存的时候路由到另外一组shard(和原记录的shard不同),导致了重复_id的出现。

2. 问题复现

首先,准备测试元数据,插入脚本如下:

db.auth("test","test");
var total = 500;
var page = 1000;
for(i=1; i<=total; i++){
    for(var j 0= 1; j <= page; j++){
     db.users.save({'_id': 'user'+i+"-"+j,'age':j,"content":"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901223456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567823456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"});
    }
}

其中content字段的内容很长,1000个字符,这样50w条元数据的数据量是满足分片后的数据迁移条件的(数据量太小,mongodb是不会迁移的)。

把该文件保存在mongo可执行程序的目录,再执行数据插入:

/mongo 127.0.0.1:30000/test saveUser.js

 

随后对test集合创建索引,并进行分片:

    db.users.createIndex({"age":1})
sh.enableSharding("test") sh.shardCollection("test.user", { age: 1 } )

 

等待分片数据迁移结束后,查看分片状态:

sh.status()

user表的分片数据如下:

{  "_id" : "test",  "primary" : "repa",  "partitioned" : true }
                test.user
                        shard key: { "age" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                repa    14
                                repb    11
                        too many chunks to print, use verbose if you want to force print

 

基础数据已经准备完毕了,下面开始造数据,首先查询到第一条记录内容如下:

{ 
    "_id" : "user1-1", 
    "age" : 1.0, 
    "content" : "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901223456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567823456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
}

 然后把该记录的内容拷贝一份,并把age修改为1000,然后再保存到users集合中:


MongoDB Enterprise mongos> db.users.save({ "_id" : "user1-1", "age" : 1000.0, "content" : "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901223456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567823456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" })

此时_id为“user1-1”的记录已经有两条了:

    MongoDB Enterprise mongos> db.users.find({"_id":"user1-1"})
{ "_id" : "user1-1", "age" : 1000, "content" : "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901223456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567823456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
{ "_id" : "user1-1", "age" : 1, "content" : "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901223456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567823456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" }

 

3. 避免措施

从以上分析可知,在对分片集合进行修改操作或者新写入操作时,要特别注意,由于shard key的路由问题,可能会导致_id字段或者其他唯一字段重复记录(保存在不同的shard中),为了避免重复记录,选择shard key时,可以把唯一字段也加入到shard key中,以本次测试为例,shard key可以设置为{"age":1, "_id":1},如果不想把_id加入到shard key中,且业务上面不允许_id重复,则需要在写入前先执行查询。

上一篇:分布式搜索引擎的架构原理!


下一篇:聊聊MySQL、HBase、ES的特点和区别