MySQL主键冲突问题分析处理

背景

因公司业务及预算调整,系统部署从原有云服务提供商迁移到另外一家云服务提供商,在测试新服务能力的时候,发现应用系统某个功能不能正常使用,仅仅是第一次成功。

为了分析问题,笔者使用以下环境还原报错场景进行讲解。

  • Spring Boot: 3.0.2
  • MySQL: 5.7.31
  • MyBatis: 3.5.1

问题分析

通过查看服务日志,发现后端接口报SQL异常-主键冲突,如下图所示:

刚开始看到这个错误信息,直接就懵了,怎么在另外一个服务商那里跑得好好的,到这边就主键冲突了呢。一通百度、Google之后,突然之间有个想法,会不会是这两个云服务商提供的MySQL服务,某些参数有区别。

分析数据

因为数据是迁移过来的,表中有大量旧数据,不好确定到底是那个值冲突了。

分析代码

然后查看我们代码,找到对应的PO类代码,代码类似以下,主要关注主键属性 id 的数据类型。

public class UserPo {
    private int id;
    private String name;
    private String email;
    private String password;
}

代码中发现id属性是int类型的,我们知道,Java中int类型默认值为0,在新增数据的时候并没有为id属性设置值,代码类似下面这样:

UserPo user = new UserPo();
user.setEmail("test@qq.com");
user.setName("test");
userMapper.insertUser(user);

insert方法的代码如下所示:

@Insert("insert into tb_user(id,name,email,password) values (#{user.id}, #{user.name}, #{user.email}, #{user.password})")
int insertUser(@Param("user") UserPo user);

从代码上看,应该是id值传了0,第一次数据保存成功后,以后就会发生主键冲突异常了。

验证分析结果

通过代码分析,我们去查表中确实有一条id为0的数据,类似下图所示:

在新服务器上开启应用的DEBUG日志,也在日志中查看到SQL日志中id为0的日志。

原因分析

为了查找0值可以作为主键的原因,又是一通百度、Google,最后找到了sql_mode这个参数。这个参数中有个NO_AUTO_VALUE_ON_ZERO的值。通常我们可以通过在AUTO_INCREMENT列上插入0或null值来获取下一个序列的值,但是NO_AUTO_VALUE_ON_ZERO参数阻止了0值的这种行为。也即0值将会作为一个有效的值存入id列中。
可以通过SQL Modes页面查看详细的sql_mode参数的详情,Server System Variables页面查看sql_mode简要说明。

验证MySQL参数

验证下我们分析步骤中查到的sql_mode参数的具体现象。
我们创建一张测试表tb_user,建表语句如下:

CREATE TABLE tb_user (
  id int NOT NULL AUTO_INCREMENT,
  name varchar(64) NOT NULL DEFAULT '',
  email varchar(128) NOT NULL DEFAULT '',
  password varchar(128) NOT NULL DEFAULT '',
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

查看当前MySQL版本及sql_mode参数。我们看到当前的MySQL版本5.7.37,且sql_mode中没有NO_AUTO_VALUE_ON_ZERO,MySQL5.7中sql_mode默认没有NO_AUTO_VALUE_ON_ZERO参数。
在这里插入图片描述
插入两条数据看下效果

insert into tb_user values(0, 'Rock', 'rock@otc.cc', '123456');
insert into tb_user values(0, 'Kitty', 'kitty@otc.cc', '123456');

数据插入成功,且id值从1开始自增。

我们修改下sql_mode参数,在原有参数上加NO_AUTO_VALUE_ON_ZERO。

SET sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO';

然后再插入两条数据,我们就会看到主键冲突的报错。查看数据也是有一条id为0的记录。

解决办法

修改MySQL配置参数

为了能快速在新的云平台上部署应用,并且避免出现其他的未知问题,我们首先修改了MySQL的sql_mode参数和就平台的一致。
当然我们不能通过以上命令去设置我们的MySQL服务,而是通过云服务提供商的管理平台去设置,操作麻烦一点而以。

修改代码

为了后期维护方便,将对应的实体类id属性类型改为Integer,与其他的代码保持一致。

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值