惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
J
Java Code Geeks
S
SegmentFault 最新的问题
L
LangChain Blog
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
MongoDB | Blog
MongoDB | Blog
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
H
Help Net Security
阮一峰的网络日志
阮一峰的网络日志
Jina AI
Jina AI
N
Netflix TechBlog - Medium
A
About on SuperTechFans
博客园 - 叶小钗
美团技术团队
人人都是产品经理
人人都是产品经理
D
DataBreaches.Net

博客园 - xinghebuluo

在云主机上部署OpenClaw并集成微信插件:从零到上手完整指南 mongodb 数据块迁移的源码分析 mongodb 数据块的迁移流程介绍 Guava Cache源码浅析 mongodb在双活(主备)机房的部署方案和切换方案设计 mongodb oplog日志详解和格式分析 Redis之ziplist源码分析 Redis之quicklist源码分析 Redis LRU源码分析 JAVA中int转string及String.valueOf()的使用 String,StringBuilder和StringBuffer的特点和使用场景 ReentrantLock的原理解析 mongodb的TTL索引介绍(超时索引) JBoss AS7(Application Server 7)的Standalone模式和Domain模式 mongodb 配置均衡器的运行窗口 mongodb添加延时节点 AFNetworking 遇到错误 Code=-1016 "Request failed: unacceptable content-type: text/plain" 使用cocoa捕获dock栏中的“退出”事件,解决qt开发的应用程序退出异常的问题 【Grails 代理Proxy设置】部署Grails遇到Error Resolve error obtaining dependencies:错误,及解决方法
mongodb中出现_id字段重复记录的排查笔记
xinghebuluo · 2020-04-06 · via 博客园 - xinghebuluo

近期在使用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 } )

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

user表的分片数据如下:

{  "_id" : "test",  "primary" : "rep1",  "partitioned" : true }
                test.user
                        shard key: { "age" : 1 }
                        unique: false
                        balancing: true
                        chunks:
                                rep1    14
                                rep2    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重复,则需要在写入前先执行查询。