【问题集锦 -02】MyBatis-Plus多节点多线程批量插入(insertBatch) 唯一主键ID冲突(Duplicate entry ‘xxxx‘ for key ‘PRIMARY‘)
MyBatis-Plus 版本 3.5.1,使用Springboot搭建的项目工程。使用MyBatis-Plus中自动生成ID主键,类型为:ASSIGN_ID (使用的雪花算法生成唯一主键ID)。部署环境:采用一台服务器Docker多节点,多线程批量插入数据。
一、项目场景:
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已修复
三、原因分析
在单机单线程使用雪花算法生成的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)}
五、参考文章
更多推荐
所有评论(0)