一条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的逻辑改成正确的就行。

为什么测试环境没有发现

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

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

最后

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

相关推荐
parafeeee5 小时前
程序人生-Hello’s P2P
数据库·后端·asp.net
iPadiPhone5 小时前
流量洪峰下的数据守护者:InnoDB MVCC 全实现深度解析
java·数据库·mysql·面试
bug攻城狮6 小时前
Spring Boot应用内存占用分析与优化
java·jvm·spring boot·后端
今天你TLE了吗6 小时前
JVM学习笔记:第八章——执行引擎
java·jvm·笔记·后端·学习
泯仲7 小时前
从零起步学习MySQL 第一章:初识MySQL及深入理解内部数据类型
数据库·mysql
XPoet7 小时前
AI 编程工程化:Rule——给你的 AI 员工立规矩
前端·后端·ai编程
雨后的天空@7 小时前
Mac 安装多个版本的mysql
mysql
韩立学长7 小时前
基于Springboot校园志愿者服务平台77pz7812(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
Java基基7 小时前
Spring让Java慢了30倍,JIT、AOT等让Java比Python快13倍,比C慢17%
java·开发语言·后端·spring
qq_12498707537 小时前
基于SpringBoot微信小程序的智能在线预约挂号系统(源码+论文+部署+安装)
spring boot·后端·微信小程序·毕业设计·计算机毕设·毕业设计源码