记录一次MySQL中varchar主键踩坑
问题描述
软件版本:
MySQL: 8.0.24
SpringBoot:2.6.5
Mybatis-plus:3.5.0
在一个项目的新建账号业务中用户反馈出现了bug,该业务是给一个课程中添加学生,输入学号姓名添加,如果系统中存在以该学生学号为主键的账号,那么就使用该账号记录,若没有则在账号表中新建账号
bug最终定位到是一张存储用户账号信息的表出现了重复的脏数据,如下图所示(图中学号为测试数据,无实际意义)
下图为该账号表表结构,没有分库分表,除主键没有其他约束,没有触发器:
但有一点奇怪的问题,这张表用户id(stu_id)是主键,用的varchar字段类型,但出现了重复的记录
在进一步的尝试探索中,我发现好像一条记录主键是数字,一条记录主键是字符串,如下图所示,加引号就少查出来一条
于是我决定删除数字的那条脏数据,输入了
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 | // controller层 |
更新 - 已解决
数据库里两条记录看似一样,其实其中一条记录的结尾多了个空格,所以能够存在两条记录
(踩坑)两条记录能同时被 where stu_id = 20xxxx 查出来,是因为mysql查询varchar类型的字段时,如果执行的条件句是数字时,mysql会尝试把数据库中该条字段的值转换成同样的数字类型,再进行比较,因此字符串空格会被忽略,并且当查询条件为字符串时,也只会正常的出现一个结果,因为另一个结果中结尾多个空格
如果该字段值转换成功的话会出现上述情况,变成数字比较,如果转换不成功的话,比如该varchar中有字母或其他任意符号,那么就会被默认成数字0,如下图所示
所以执行查询条件的时候要注意了,这个机制可能会导致一些预期之外的结果
PS:在使用mybatis时,即使你实体类该字段中使用了正确的String类型,如果执行查询时提供的参数类型为int类型(QueryWapper或传参在xml中写sql),那么同样会有上述的问题
总结:
系统设计的不规范,用户输入的值别轻易做主键,至少要处理一下,去掉空格、正则匹配