You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用MongoTemplate save方法偶发DuplicateKeyException问题求助

问题:MongoTemplate.save偶发DuplicateKeyException排查与解决

环境版本

  • MongoDB: 3.4.12
  • mongodb-driver-sync: 4.6.0
  • spring-boot-starter-data-mongodb: 2.7.0
  • Spring Boot: 2.7.0

问题现象

使用MongoTemplate的save方法存储记录时,偶发抛出DuplicateKeyException。按预期save内部应执行upsert逻辑,正常情况下不应出现该问题。此问题仅在生产环境偶现,测试环境无法复现。

错误日志如下:

org.springframework.dao.DuplicateKeyException: Write operation error on server mongodb1:27017. Write error: WriteError{code=11000, message='E11000 duplicate key error collection: push-db.devices index: _id_ dup key: { : { platformId: 15, uuid: "eW9Zale3ST2EPPtFLQf3R5:APA91bGcXIc4ZIQZ3qVNTp1nDo981oPmj4CR5EXJspU-Ge_oQq1b12v0HLP7E-CPjF5qrK44K7Zr5Fszl0c6tGEmboKg2QrZoQpwFTAb6a_pICvX8V8ZJwbVIW1aMrC..." } }', details={}}.; 
nested exception is com.mongodb.MongoWriteException: Write operation error on server mongodb1:27017. Write error: WriteError{code=11000, message='E11000 duplicate key error collection: push-db.devices index: _id_ dup key: { : { platformId: 15, uuid: "eW9Zale3ST2EPPtFLQf3R5:APA91bGcXIc4ZIQZ3qVNTp1nDo981oPmj4CR5EXJspU-Ge_oQq1b12v0HLP7E-CPjF5qrK44K7Zr5Fszl0c6tGEmboKg2QrZoQpwFTAb6a_pICvX8V8ZJwbVIW1aMrC..." } }', details={}}. at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:106) at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:3044) at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:600) at org.springframework.data.mongodb.core.MongoTemplate.saveDocument(MongoTemplate.java:1595) at org.springframework.data.mongodb.core.MongoTemplate.doSave(MongoTemplate.java:1530) at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:1473) at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:1458) 

已确认信息

1. _id索引存在

{
    "v" : 1,
    "key" : {
        "_id" : 1
    },
    "name" : "_id_",
    "ns" : "push-db.devices"
}

2. 集合数据结构

集合的_id为嵌套对象,由platformId和uuid组成:

{
    "_id" : {
        "platformId" : 15,
        "uuid" : "eW9Zale3ST2EPPtFLQf3R5:APA91bGcXIc4ZIQZ3qVNTp1nDo981oPmj4CR5EXJspU-Ge_oQq1b12v0HLP7E-CPjF5qrK44K7Zr5Fszl0c6tGEmboKg2QrZoQpwFTAb6a_pICvX8V8ZJwbVIW1aMrCasq2323232"
    },
    "appId" : 32342,
    "appVersion" : "33.556.66",
    "tags" : [ 
        {
            "_id" : 39391,
            "lastLogin" : NumberLong(1666903871334),
            "loginCount" : 1
        }, 
        {
            "_id" : 34269,
            "lastLogin" : NumberLong(1666903871526),
            "loginCount" : 1
        }
    ],
    "_class" : "com.myCompany.device.PushDevice"
}

3. 存储逻辑代码

private final MongoTemplate mongoTemplate;

@Override
public void storeDevice(PushDevice device) {
    mongoTemplate.save(device);
}

排查思路与解决方案

1. 明确save方法的执行逻辑

Spring Data MongoDB的save方法逻辑分两种情况:

  • 若实体_id不为null,执行upsert(存在则更新,不存在则插入)
  • 若实体_id为null,执行insert

偶发的重复键错误,大概率是高并发下多个线程同时处理同一设备记录,且都判断记录不存在,执行insert操作导致冲突。

  • 检查PushDevice的_id生成逻辑:是否存在多线程同时为同一platformId+uuid生成_id,但未先查询记录是否存在就执行save?
  • 模拟高并发场景验证:用压测工具模拟多线程同时存储同一设备,看是否能复现问题。

2. 显式使用upsert替代save

save的逻辑依赖_id的存在性判断,不如显式调用upsert更可靠,无论记录是否存在都会执行插入或更新:

@Override
public void storeDevice(PushDevice device) {
    // 基于_id的嵌套字段构建查询条件
    Query query = Query.query(Criteria.where("_id.platformId").is(device.getId().getPlatformId())
            .and("_id.uuid").is(device.getId().getUuid()));
    // 构建更新字段
    Update update = Update.update("appId", device.getAppId())
            .set("appVersion", device.getAppVersion())
            .set("tags", device.getTags());
    // 执行upsert
    mongoTemplate.upsert(query, update, PushDevice.class);
}

3. 调整副本集写入一致性

MongoDB副本集默认writeConcern为w=1(仅确认主节点写入成功),若主节点写入后未同步到从节点就发生切换,可能引发一致性问题。可调整为w=majority,确保多数节点写入成功:

// 配置MongoTemplate的write concern
mongoTemplate.setWriteConcern(WriteConcern.MAJORITY);

4. 检查UUID的完整性与合法性

错误日志中UUID被截断(末尾带...),需确认:

  • 实际传入的UUID是否存在长度差异、前后空格或不可见字符,导致生成的_id看似相同实际不同,但插入时触发重复键?
  • 验证PushDevice中UUID字段的存储逻辑,是否存在截断或转义问题。

5. 版本兼容性排查

MongoDB 3.4.12已停止官方支持,与4.6.0版本的驱动可能存在兼容性隐患:

  • 尝试升级MongoDB到3.6及以上版本(推荐4.x系列),或降级驱动到3.x系列(匹配MongoDB 3.4的版本),观察问题是否消失。

6. 引入分布式锁控制并发

高并发场景下,可针对同一platformId+uuid引入分布式锁,确保存储操作串行执行:

  • 基于Redis或MongoDB实现分布式锁,在storeDevice方法执行前加锁,处理完成后释放锁,避免多线程同时插入同一记录。

内容的提问来源于stack exchange,提问作者George Anil John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 06:05:23