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" : "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重復,則需要在寫入前先執行查詢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM