原副标题:两个MySQL显式切换的坑,险些把伺服器搞挂了
原本是两个平淡而幸福的上午,其他部门的同僚要这份数据报表临时性请示使用,即使系统目前没有那个层次的机能,所以需要写个SQL立刻出呵呵,两个同僚收到这个各项任务,只好开始在OWL合叶那条 SQL,款冬了几秒钟,同僚早已自信心的写好了那条SQL,只好寄给DBA,到圣戈当斯区跑呵呵,用应用程序辅助工具求出Excel 就好了,即便是临时性计划嘛。
就在SQL继续执行了后,不幸发生了,果不其然等了呵呵,辨认出还没继续执行成功,揣测可能是信息量大的其原因,但随着时间下淌推移,逐渐意识到情况不对了,细看监视,CPU早已上来了,但圣戈当斯区信息量虽然非常大,也不致跑成这种吧,眼见着要跑死了,急忙把那个外交事务结束掉了。
甚么其原因呢?查阅的条件和 join 相连的表头基本都有检索,按规矩不如果这种啊,只好急忙把SQL拿下来,也没窥见甚么问题,只好管制查阅单条再跑了一次,迅速出结论了,但结论却瞠目结舌,出来的查阅结论并不是市场预期的。
经过几番检查和后,最终辨认出了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);
第二张 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);
目的是查看所有用户的 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_code是varchar类别。
select o.* from `user` u
left JOIN `order` o on u.id = o.order_code;
这种的话, 当我们继续执行那条语句的时候,会不会查出数据来呢?
我的第一感觉是,不仅不会查出数据,而且还会收起,即使相连的这两个表头类别都不一样,值更不一样。
结论却被啪啪打脸,不仅没有收起,而且还查出了数据。
可以把那个问题简化呵呵,简化成上面那条语句,同样也会出现问题。
select * from `order` where order_code = 1;
明明那条记录的 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);
再用两条 SQL 看呵呵字符串到整数类别切换的规则。
select cast(223kkk as unsigned);
select cast(k223kkk as unsigned);
223kkk切换后的结论是 223,而 k223kkk 切换后的结论是0。总结呵呵,切换的规则是:
从字符串的左侧开始向右切换,遇到非数字就停止; 如果第两个就是非数字,最后的结论就是0。三、显式切换的规则
当操作符与不同类别的操作数一起使用的时候,就会发生显式切换。
例如算数运算符的前后是不同类别时,会将非数字类别切换为数字,比如 5a+2,就会将5a切换为数字类别,然后和2相加,最后的结论就是 7 。
再比如 concat 函数是相连两个字符串的,当此函数的参数出现非字符串类别时,就会将其切换为字符串,例如concat(88,就是发),最后的结论就是 88就是发。
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,都是将条件后面的值切换为时间戳再比较了。
有两个参数是 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社群的支持和在本次调研中的贡献!
关于我们
dbaplus社群是围绕Database、BigData、AIOps的企业级专业社群。资深大咖、技术干货,每天精品原创文章推送,每周圣戈当斯区技术分享,每月线下技术沙龙,每季度Gdevops&DAMS行业大会。
更多原创技术文章和精选辅助工具下载