一个MySQL隐式转换的坑,差点把服务器搞挂了

2023-01-13 0 848

原副标题:两个MySQL显式切换的坑,险些把伺服器搞挂了

原本是两个平淡而幸福的上午,其他部门的同僚要这份数据报表临时性请示使用,即使系统目前没有那个层次的机能,所以需要写个SQL立刻出呵呵,两个同僚收到这个各项任务,只好开始在OWL合叶那条 SQL,款冬了几秒钟,同僚早已自信心的写好了那条SQL,只好寄给DBA,到圣戈当斯区跑呵呵,用应用程序辅助工具求出Excel 就好了,即便是临时性计划嘛。

就在SQL继续执行了后,不幸发生了,果不其然等了呵呵,辨认出还没继续执行成功,揣测可能是信息量大的其原因,但随着时间下淌推移,逐渐意识到情况不对了,细看监视,CPU早已上来了,但圣戈当斯区信息量虽然非常大,也不致跑成这种吧,眼见着要跑死了,急忙把那个外交事务结束掉了。

甚么其原因呢?查阅的条件和 join 相连的表头基本都有检索,按规矩不如果这种啊,只好急忙把SQL拿下来,也没窥见甚么问题,只好管制查阅单条再跑了一次,迅速出结论了,但结论却瞠目结舌,出来的查阅结论并不是市场预期的。

一个MySQL隐式转换的坑,差点把服务器搞挂了

经过几番检查和后,最终辨认出了Vizille,是 join 相连两个表头弄错了,即使这两个表头有一部分中文名称是完全相同的,只好智能化的 SQL 应用程序得出了提示信息,随手就给敲上去了。但接下去,最让人蒙蔽了,即使要相连的表头是 int 类别,而弄错的那个表头是 varchar 类别,何况不如果收起吗?怎么还能正常继续执行,并且还有市场预期外的查阅结论?

何况是 MySQL 有 bug 了,必须要研究呵呵了。

一、Cadours当时的情境

假定有三张表,这三张表的结构和数据是上面这种的。

第三张 user表:

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`name` varchar(50) COLLATE utf8_bin DEFAULT NULL,

`age` int(3) DEFAULT NULL,

`create_time` datetime DEFAULT NULL,

`update_time` datetime DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

INSERT INTO `user` VALUES (1, 张三, 28, 2022-09-06 07:40:56, 2022-09-06 07:40:59);

一个MySQL隐式转换的坑,差点把服务器搞挂了

第二张 order 表:

CREATE TABLE `order` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`user_id` int(11) DEFAULT NULL,

`order_code` varchar(64) COLLATE utf8_bin DEFAULT NULL,

`money` decimal(20,0) DEFAULT NULL,

`title` varchar(255) COLLATE utf8_bin DEFAULT NULL,

`create_time` datetime DEFAULT NULL,

`update_time` datetime DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

INSERT INTO `order` VALUES (1, 2, 1d90530e-6ada-47c1-b2fa-adba4545aabd, 100, xxx购买两件商品, 2022-09-06 07:42:25, 2022-09-06 07:42:27);

一个MySQL隐式转换的坑,差点把服务器搞挂了

目的是查看所有用户的 order 记录,假定信息量比较少,可以直接查,不考虑性能问题。

原本的 SQL 语句如果是这种子的,查阅 order 表中用户id user_id user 表的记录。

select o.* from `user` u

left JOIN `order` o on u.id = o.user_id;

但呢,即使手抖,将 on 后面的条件写成了 u.id = o.order_code,完全关联错误,这两个表头完全没有联系,而且u.id int 类别,o.order_codevarchar类别。

select o.* from `user` u

left JOIN `order` o on u.id = o.order_code;

这种的话, 当我们继续执行那条语句的时候,会不会查出数据来呢?

我的第一感觉是,不仅不会查出数据,而且还会收起,即使相连的这两个表头类别都不一样,值更不一样。

结论却被啪啪打脸,不仅没有收起,而且还查出了数据。

一个MySQL隐式转换的坑,差点把服务器搞挂了

可以把那个问题简化呵呵,简化成上面那条语句,同样也会出现问题。

select * from `order` where order_code = 1;

一个MySQL隐式转换的坑,差点把服务器搞挂了

明明那条记录的 order_code 表头的值是 1d90530e-6ada-47c1-b2fa-adba4545aabd,怎么用 order_code=1的条件就把它给查出来了。

二、根源所在

相信有的同学早已猜出来了,这里是 MySQL 进行了显式切换,由于查阅条件后面跟的查阅值是整型的,所以 MySQL 将 order_code 表头进行了字符串到整数类别的切换,而切换后的结论正好是 1

通过 cast函数切换验证呵呵结论。

select cast(1d90530e-6ada-47c1-b2fa-adba4545aabd as unsigned);

一个MySQL隐式转换的坑,差点把服务器搞挂了

再用两条 SQL 看呵呵字符串到整数类别切换的规则。

select cast(223kkk as unsigned);

select cast(k223kkk as unsigned);

一个MySQL隐式转换的坑,差点把服务器搞挂了

223kkk切换后的结论是 223,而 k223kkk 切换后的结论是0。总结呵呵,切换的规则是:

从字符串的左侧开始向右切换,遇到非数字就停止; 如果第两个就是非数字,最后的结论就是0。

三、显式切换的规则

当操作符与不同类别的操作数一起使用的时候,就会发生显式切换。

例如算数运算符的前后是不同类别时,会将非数字类别切换为数字,比如 5a+2,就会将5a切换为数字类别,然后和2相加,最后的结论就是 7 。

一个MySQL隐式转换的坑,差点把服务器搞挂了

再比如 concat 函数是相连两个字符串的,当此函数的参数出现非字符串类别时,就会将其切换为字符串,例如concat(88,就是发),最后的结论就是 88就是发。

一个MySQL隐式转换的坑,差点把服务器搞挂了

MySQL 官方文档有以下几条关于显式切换的规则:

两个参数至少有两个是 NULL 时,比较的结论也是 NULL,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类别切换;

也就是两个参数中如果只有两个是NULL,则不管怎么比较结论都是 NULL,而两个 NULL 的值不管是判断大于、小于或等于,其结论都是1。

两个参数都是字符串,会按照字符串来比较,不做类别切换; 两个参数都是整数,按照整数来比较,不做类别切换; 十六进制的值和非数字做比较时,会被当做二进制字符串;

例如上面那条语句,查阅 user 表中name表头是 0x61 的记录,0x是16进制写法,其对应的字符串是英文的 a,也就是它对应的 ASCII 码。

select * from user where name = 0x61;

所以,上面那条语句其实等同于上面那条

select * from user where name = a;

可以用 select 0x61; 验证呵呵。

有两个参数是 TIMESTAMP 或 DATETIME,并且另外两个参数是常量,常量会被切换为 时间戳;

例如上面这两条SQL,都是将条件后面的值切换为时间戳再比较了。

一个MySQL隐式转换的坑,差点把服务器搞挂了

有两个参数是 decimal 类别,如果另外两个参数是 decimal 或者整数,会将整数切换为 decimal 后进行比较,如果另外两个参数是浮点数(一般默认是 double),则会把 decimal 切换为浮点数进行比较;

在不同的数值类别之间,总是会向精度要求更高的那两个类别切换,但有一点要注意,在MySQL 中浮点数的精度只有53 bit,超过53bit后的话,如果后面1位是1就进位,如果是0就直接舍弃。所以超大浮点数在比较的时候其实只是取的近似值。

所有其他情况下,两个参数都会被切换为浮点数再进行比较;

如果不符合上面6点规则,则统一转成浮点数再进行运算。

四、避免进行显式切换

我们在平时的开发过程中,尽量要避免显式切换,即使一旦发生显式切换除了会降低性能外, 还有很大可能会出现不期望的结论,就像我最开始遇到的那个问题一样。

之所以性能会降低,还有两个其原因就是让原本有的检索失效。

select * from `order` where order_code = 1;

order_code 是 varchar 类别,假定我早已在 order_code 上建立了检索,如果是用“=”做查阅条件的话,如果直接命中检索才对,查阅速度会迅速。但,当查阅条件后面的值类别不是 varchar,而是数值类别的话,MySQL 首先要对 order_code 表头做类别切换,切换为数值类别,这时候,之前建的检索也就不会命中,只能走全表扫描,查阅性能指数级下降,搞不好,数据库直接查崩了。

吃一堑长一智,愿各位吃不到那个堑,但能长那个智。

作者丨风筝

古时的风筝(ID:gushidefengzheng)

dbaplus社群欢迎广大技术人员投稿,投稿邮箱:[email protected]

《国产分布式数据库应用现状调研报告》邀您填写调研问卷——

汇集广大数据库从业者的经验和建议,从现状出发,分析真实诉求,洞察发展趋势,给国产数据库全产业链的高质量发展提供一些参考和启发。

调研对象:本次调研面向各行各业数据库使用方的IT决策者、技术主管、架构师、研发工程师、DBA等。

问卷链接:https://www.wjx.cn/vm/wr9dZFd.aspx?udsid=721095

《国产分布式数据库应用现状调研报告》邀您填写调研问卷

现诚挚邀请复制上方链接,抽出3-5分钟填写问卷。所有参与者将免费获得本次调研的完整报告(邮件发送),并有机会获赠精美礼品这份,感谢您对dbaplus社群的支持和在本次调研中的贡献!

一个MySQL隐式转换的坑,差点把服务器搞挂了

关于我们

dbaplus社群是围绕Database、BigData、AIOps的企业级专业社群。资深大咖、技术干货,每天精品原创文章推送,每周圣戈当斯区技术分享,每月线下技术沙龙,每季度Gdevops&DAMS行业大会。

更多原创技术文章和精选辅助工具下载

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务