​​​​​​一、项目场景:

MyBatis-Plus 版本 3.5.1,使用Springboot搭建的项目工程。使用MyBatis-Plus中自动生成ID主键,类型为:ASSIGN_ID (使用的雪花算法生成唯一主键ID)。

部署环境:采用一台服务器Docker多节点,多线程批量插入数据。

mybatis-plus:
  global-config:
    #数据库相关配置
    db-config:
      # 主键类型
      id-type: ASSIGN_ID

二、问题描述

MyBatis-Plus多节点多线程批量插入(insertBatch) 唯一主键ID冲突(Duplicate entry 'xxxx' for key 'PRIMARY')

2022-06-01 01:06:18,038 [http-nio-16010-exec-7-asyncPool-203]-[ai.xxx.commons.tools.event.EventListener:34]-ERROR 监听事件[ai.xxx.event.listener.logistics.PullFullMbOrderDataListener@34abdee4]异常:ai.xxx.dao.logistics.tk.TkExpressTrackingProviderEventDao.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry '1531683498452185090' for key 'PRIMARY'
; Duplicate entry '1531683498452185090' for key 'PRIMARY'; nested exception is java.sql.BatchUpdateException: Duplicate entry '1531683498452185090' for key 'PRIMARY'
org.springframework.dao.DuplicateKeyException: ai.westyle.dao.logistics.tk.TkExpressTrackingProviderEventDao.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry '1531683498452185090' for key 'PRIMARY'
; Duplicate entry '1531683498452185090' for key 'PRIMARY'; nested exception is java.sql.BatchUpdateException: Duplicate entry '1531683498452185090' for key 'PRIMARY'
        at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:247)
        at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70)
        at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:91)
        at com.baomidou.mybatisplus.extension.toolkit.SqlHelper.executeBatch(SqlHelper.java:192)
        at com.baomidou.mybatisplus.extension.toolkit.SqlHelper.executeBatch(SqlHelper.java:217)
        at ai.xxxx.commons.mybatis.service.impl.BaseServiceImpl.executeBatch(BaseServiceImpl.java:230)
        at ai.xxxx.commons.mybatis.service.impl.BaseServiceImpl.insertBatch(BaseServiceImpl.java:214)
        at ai.xxx.commons.mybatis.service.impl.BaseServiceImpl$$FastClassBySpringCGLIB$$3b2a4f81.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)

官方的标记为这个Bug已修复

批量插入,主键冲突 · Issue #3289 · baomidou/mybatis-plus · GitHubicon-default.png?t=M4ADhttps://github.com/baomidou/mybatis-plus/issues/3289#issue-791890470

 

三、原因分析

在单机单线程使用雪花算法生成的ID,是不会出现这个问题。在集群环境下,特别是在一台机器中使用Docker 部署了多个节点,同时使用多线程批量插入数据,出现唯一主键ID出现的概率非常大。

ID生成规则:

  • 服务器时间
  • workId(服务器机器ID)
  • dataCenterId(数据标识ID部分)

IdType 采用 ASSIGN_ID雪花算法生成ID的实现

 DefaultIdentifierGenerator 默认实现类

 

 

四、解决方案

1、新版MyBatis-Plus解决方案

自定义 DefaultIdentifierGenerator 中的

  • workId(服务器机器ID)
  • dataCenterId(数据标识ID部分)

保证每个节点拿到的workId 和 dataCenterId 都是不一样的,这里采用简单的随机数的方法。

注意 workId 和 dataCenterId的值范围为 1-31。

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @description: mybatis-plus配置
 * @author: flygo
 * @time: 2022/2/25 7:02 下午
 */
@Configuration
public class MybatisPlusConfig {

  /**
   * description: 自定义ID主键生成器 <br>
   * date: 2022/6/1 11:38 <br>
   * author: flygo <br>
   *
   * @return com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator
   */
  @Bean
  public DefaultIdentifierGenerator defaultIdentifierGenerator() {
    // 以免多线程批量插入,ID主键生成有重复,主键冲突错误
    // 1-31 随机数
    long workerId = RandomUtil.randomLong(1, 31);
    // 1-31 随机数
    long dataCenterId = RandomUtil.randomLong(1, 31);

    return new DefaultIdentifierGenerator(workerId, dataCenterId);
  }
}

2、旧版本的MyBatis-plus 主键ID生成类型ID_WORKER方式

特别注意:这个方式只适应与旧版本的MyBatis-plus 主键ID生成类型ID_WORKER方式

可以采用配置文件形式,在新版本的MyBatis-Plus中去重了这种方式,workId 和 dataCenterId已经不暴露在配置文件中配置了。

mybatis-plus:
  global-config:
    #数据库相关配置
    db-config:
      # 主键类型
      id-type: ASSIGN_ID
    worker-id: ${random.int(1,31)}
    datacenter-id: ${random.int(1,31)}

五、参考文章

mybatis-plus内置雪花算法主键重复问题_简单简单小白的博客-CSDN博客_mybatis主键重复异常Mybatis-Plus 使用ID_WORKER生成主键id重复问题描述目前项目使用的id是mybatis-plus 内置的主键生成策略 ID_WORKER ,最近测试在做性能压测,部署架构是单服务集群的部署方式,然后就发现了id重复的异常,异常如下问题分析首先分析的是id生成是不是就是重复了,先关掉其中一台机器,单机跑,这个时候发现压到1000的并发都没有出现过id重复,这个说明单机情况下不存在id重复问题,说明只有集群的情况下才会出现。再分析一下id生成的几个要素,雪花算法的核心能影响https://blog.csdn.net/wagnteng/article/details/117064242

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐