问题描述

软件版本:

MySQL: 8.0.24

SpringBoot:2.6.5

Mybatis-plus:3.5.0

在一个项目的新建账号业务中用户反馈出现了bug,该业务是给一个课程中添加学生,输入学号姓名添加,如果系统中存在以该学生学号为主键的账号,那么就使用该账号记录,若没有则在账号表中新建账号

bug最终定位到是一张存储用户账号信息的表出现了重复的脏数据,如下图所示(图中学号为测试数据,无实际意义)

image-20240912154106003

下图为该账号表表结构,没有分库分表,除主键没有其他约束,没有触发器:

image-20240912153735442

但有一点奇怪的问题,这张表用户id(stu_id)是主键,用的varchar字段类型,但出现了重复的记录

在进一步的尝试探索中,我发现好像一条记录主键是数字,一条记录主键是字符串,如下图所示,加引号就少查出来一条

image-20240912153844268

于是我决定删除数字的那条脏数据,输入了

1
delete from t_student where stu_id = 2406249104 and wechat is null

但结果是报错:ERROR 1292(22007):Truncated incorrect DouBLE value ,我去查了一下,是说条件字段的类型不匹配,也就是stu_id = 2406249104 的字段类型有误,但如果我输入加引号的学号

1
delete from t_student where stu_id = '2406249104' and wechat is null

则删不掉该条记录,直接 “Query OK, 1 row affected (0.01 sec) ” 影响0条记录,这条记录好像真的是数字,我也不明白为什么

最终为了保证业务正常,我用其他辅助字段删除了这条记录,系统就恢复正常了

后续用同样的操作试图复现这个bug,都失败了,就算命令行敲sql命令也不能给varchar字段插入数字,已有一条正常记录再插入就数据库报错重复的主键

可能是导致异常的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// controller层
@PostMapping("/xxxxxxxx")
public Result addNewCourseStudent(HttpServletRequest request,String stuId,String stuName,String stuSex,int courseId){
if (xxxxx){
return UserService.addNewCourseStudent(stuId,stuName,stuSex,courseId);
}else{
return Result.error("xxxxx","身份信息不匹配");
}
}

//service层 studentMapper为baseMapper实现的mapper层
public Result addNewCourseStudent(String stuId,String stuName,String stuSex,int courseId){
Student student = studentMapper.selectById(stuId);
if (student != null){
// 关联课程业务代码
// ...............
}else {
studentMapper.insert(new Student(stuId,stuName,stuSex,EncryptionUtil.inputPassToMd5Pass(defaultPasswordStudent)));
// ........................
}
}

// 实体类
@Data
public class Student {
@TableId
private String stuId;
private String stuName;
private String stuSex;
private String wechat;
private String password;

public Student(){

}
public Student(String stuId, String stuName, String stuSex, String password){
this.stuId = stuId;
this.stuName = stuName;
this.stuSex = stuSex;
this.password = password;
}
}


更新 - 已解决

数据库里两条记录看似一样,其实其中一条记录的结尾多了个空格,所以能够存在两条记录

(踩坑)两条记录能同时被 where stu_id = 20xxxx 查出来,是因为mysql查询varchar类型的字段时,如果执行的条件句是数字时,mysql会尝试把数据库中该条字段的值转换成同样的数字类型,再进行比较,因此字符串空格会被忽略,并且当查询条件为字符串时,也只会正常的出现一个结果,因为另一个结果中结尾多个空格

如果该字段值转换成功的话会出现上述情况,变成数字比较,如果转换不成功的话,比如该varchar中有字母或其他任意符号,那么就会被默认成数字0,如下图所示

image-20240913133819929

所以执行查询条件的时候要注意了,这个机制可能会导致一些预期之外的结果

PS:在使用mybatis时,即使你实体类该字段中使用了正确的String类型,如果执行查询时提供的参数类型为int类型(QueryWapper或传参在xml中写sql),那么同样会有上述的问题

总结:

系统设计的不规范,用户输入的值别轻易做主键,至少要处理一下,去掉空格、正则匹配