一条SQL差点引发离职

文章首发于微信公众号:云舒编程

关注公众号获取: 1、大厂项目分享 2、各种技术原理分享 3、部门内推

背景

最近组里的小伙伴在开发一个更新功能时踩了MySQL的一个类型转换的坑,差点造成线上故障。

本来是一个很简单的逻辑,就是根据唯一的id去更新对应的MySQL数据,代码简化后如下:

go 复制代码
​var updates []*model.Goods
for id, newGoods := range update {
 if err := model.GetDB().Model(&model.Goods{}).Where("id = ?", id).Updates(map[string]interface{}{
  "selling_price":  newGoods.SellingPrice,
  "sell_type":      newGoods.SellType,
  "status":         newGoods.Status,
  "category_id":    newGoods.CategoryID,
 }).Error; err != nil {
  return nil, err
 }
}

很明显, <math xmlns="http://www.w3.org/1998/Math/MathML"> u p d a t e s [ ] ∗ m o d e l . G o o d s \color{red}{updates []*model.Goods} </math>updates[]∗model.Goods本来应该是想声明为 <math xmlns="http://www.w3.org/1998/Math/MathML"> m a p [ s t r i n g ] ∗ m o d e l . G o o d s \color{red}{map[string]*model.Goods} </math>map[string]∗model.Goods类型的,然后key是唯一id。这样下面的更新逻辑才是对的,否则拿到的id其实是数组的下标。

但是code review由于跟着一堆代码一起评审了,并且这段更新很简单,同时测试的时候也测试过了(能测试通过也是"机缘巧合"),所以没有发现这段异常。

发到线上后,进行了灰度集群的测试,这个时候发现只要调用了这个接口,灰度集群的数据全部都变成了一样,回滚后正常。

分析

回滚后在本地进行复现,由于本地环境是开启了SQL打印的,于是看到了这么一条SQL:很明显是拿数组的下标去比较了

sql 复制代码
update db_name set selling_price = xx,sell_type = xx where id = 0;

由于我们的id是全部是通过uuid生成的,所以下意识的认为这条sql应该啥也不会更新才对,但是本地的确只执行了这条sql,没有别的sql,并且db中的数据全部都被修改了。

这个时候想起福尔摩斯的名言 <math xmlns="http://www.w3.org/1998/Math/MathML"> "排除一切不可能的,剩下的即使再不可能,那也是真相" \color{blue}{"排除一切不可能的,剩下的即使再不可能,那也是真相"} </math>"排除一切不可能的,剩下的即使再不可能,那也是真相" ,于是抱着试一试的心态直接拿这条sql去db控制台执行了一遍,发现果然所有的数据又都被修改了。

也就是 <math xmlns="http://www.w3.org/1998/Math/MathML"> w h e r e i d = 0 \color{red}{where id = 0} </math>whereid=0 这个条件对于所有的记录都是恒为true,就会导致所有记录都被更新。在这个时候,想起曾经看到过MySQL对于不同类型的比较会有 <math xmlns="http://www.w3.org/1998/Math/MathML"> 【隐式转换】 \color{red}{【隐式转换】} </math>【隐式转换】,难道是这个原因导致的?

隐式转换规则

在MySQL官网找到了不同类型比较的规则:

最后一段的意思是:对于其他情况,将按照浮点(双精度)数进行比较。例如,字符串和数字的比较就按照浮点数规则进行比较。

也就是id会首先被转换成浮点数,然后再跟0进行比较。

MySQL字符转为浮点数时会按照如下规则进行:

1.如果字符串的第一个字符就是非数字的字符,那么转换结果就是0;

2.如果字符串以数字开头:

(1)如果字符串都是数字,转换结果就是整个字符串对应的数字;

(2)如果字符串中存在非数字,转换结果就是开头的那些数字对应的值;

举例说明:

"test" -> 0

"1test" -> 1

"12test12" -> 12

由于我们生成的uuid没有数字开头的字符串,于是都会转变成0。那么这条SQL就变成了:

sql 复制代码
update db_name set selling_price = xx,sell_type = xx where 0 = 0;

就恒为true了。

修复就很简单了,把取id的逻辑改成正确的就行。

为什么测试环境没有发现

前面有提到这段代码在测试环境是测试通过了的,这是因为开发和测试同学的环境里都只有一条记录,每次更新他发现都能正常更新就认为是正常的了。同时由于逻辑太简单了,所以都没有重视这块的回归测试。

幸好在灰度集群就发现了这个问题,及时进行了回滚,如果发到了线上影响了用户数据,可能就一年白干了。

最后

代码无小事,事事需谨慎啊。一般致命问题往往是一行小小的修改导致的。

相关推荐
hello 早上好1 分钟前
MsSql 其他(2)
数据库·mysql
高压锅_12209 分钟前
SQLAlchemy数据库连接密码特殊字符处理完全指南
数据库·mysql·django·sqlalchemy
tan180°8 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
DuelCode9 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
优创学社29 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
why技术9 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理9 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
ai小鬼头10 小时前
AIStarter如何助力用户与创作者?Stable Diffusion一键管理教程!
后端·架构·github
简佐义的博客11 小时前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang
爬山算法11 小时前
MySQL(116)如何监控负载均衡状态?
数据库·mysql·负载均衡