一条不正确的select语句造成的灵异事件

前言:在公司帮业务部门查历史数据的时候,登mysql用原生语句去执行查询,结果因为不细心写出的语句导致查出来的数据有异常,险些造成事故。所以写下本文章做下记录,也算是知识累积了。

这里先给出用到的表数据和语句,出于去除敏感信息,表数据和语句都经过了精简。 表数据如下:

1.png

查询语句如下:

SELECT * FROM `head` where entry_id = 514120211411285517;
复制代码

很多人第一反应就是,这不就是查询entry_id等于514120211411285517(即id=3)这一条嘛,结果肯定就是这条了。 然而结果是:

2.png

不好意思,结果是11条,不是1条。

为什么?,很多人转身又想,这肯定是数据类型的问题了。是的没错,现在来看下表结构(顺便给出表的数据了):

CREATE TABLE `head`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `entry_id` varchar(18) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

INSERT INTO `head` VALUES (1, '514120211411285512');INSERT INTO `head` VALUES (2, '514120211411285514');INSERT INTO `head` VALUES (3, '514120211411285517');INSERT INTO `head` VALUES (4, '514120211411285519');INSERT INTO `head` VALUES (5, '514120211411285529');INSERT INTO `head` VALUES (6, '514120211411285537');INSERT INTO `head` VALUES (7, '514120211411285540');INSERT INTO `head` VALUES (8, '514120211411285544');INSERT INTO `head` VALUES (9, '514120211411285553');INSERT INTO `head` VALUES (10, '514120211411285561');INSERT INTO `head` VALUES (13, '514120211411285567');INSERT INTO `head` VALUES (14, '531420201140063083');INSERT INTO `head` VALUES (15, '530320201030036387');INSERT INTO `head` VALUES (17, '530320201030036259');INSERT INTO `head` VALUES (18, '530320201030036217');INSERT INTO `head` VALUES (20, '530320201030036093');INSERT INTO `head` VALUES (21, '530320201030036015');INSERT INTO `head` VALUES (23, '530320201030035947');INSERT INTO `head` VALUES (25, '514120211411285510');INSERT INTO `head` VALUES (26, '514120211411285511');INSERT INTO `head` VALUES (27, '514120211411285513');INSERT INTO `head` VALUES (28, '514120211411285520');INSERT INTO `head` VALUES (29, '514120211411285509');INSERT INTO `head` VALUES (30, '514120211411285508');

复制代码

entry_id是varchar(18),也就是字符串,但是查询语句是entry_id = 514120211411285517是用整数int而不是传入字符型的,肯定有问题了。 现在改为正确的查一下:

SELECT * FROM `head` where entry_id = '514120211411285517';
复制代码

3.png

正确的结果出来了。嗯,好了文章结束了。

不是的,回到错误的语句:where entry_id = 514120211411285517;

为什么查出来是11条而不是1条,大部分人大概也说得出:因为表中字段类型和传入值的数据类型不同,造成mysql查询时进行了类型转换,然而类型转换后数值会有精度缺失,导致了模糊匹配,比如只匹配到了倒数第2位,最后1位忽略,所以就会出来了多条,而不是1条;这也是我当时的第一想法。

然而细心一点看就会发现,这样的解释也不对。

4.png

如果像上面说的话,那514120211411285529,514120211411285520,514120211411285509,514120211411285508这几条都不会出来,但是事实是出来了,所以肯定是另外的规则导致的。

于是我各种百度谷歌查找真正原因,然后翻到了小米技术团队的这篇文章 xiaomi-info.github.io/2019/12/24/…

下面不卖关子了,直接说明上面语句匹配出多条结果的原因。

小米团队文章的这段揭晓了两种数据类型比较造成的隐式转换的比对规则,注意看最后一条;

5.png

以及下面这段详细说明:

6.png

遂上用一句最精简的话来概述原因就是:Mysql的select语句中,查询时如果是字符和整型做比较的话,会统一转浮点数比较;因为Mysql里浮点数是不精确的,精度位只有53比特而大于53比特的位被忽略,所以造成精度缺失,最后的比对结果就给人以模糊匹配的感觉。

下面来实践一下证明:

select '514120211411285512' = 514120211411285517;
select '514120211411285514' = 514120211411285517;
select '514120211411285529' = 514120211411285517;
select '514120211411285537' = 514120211411285517;
select '514120211411285509' = 514120211411285517;
复制代码

7.png

这几句sql证明,在mysql眼里,除了字符'514120211411285537'外,其他4个字符跟整型的514120211411285517比对是一样的,这跟最开始的select相符。

用 select 514120211411285517+'0'; 得出整型514120211411285517转浮点的值 5.141202114112855e17 ;

8.png

用下面语句得出这5个字符转浮点的值

select '514120211411285512'+0.0;
select '514120211411285514'+0.0;
select '514120211411285529'+0.0;
select '514120211411285537'+0.0;
select '514120211411285509'+0.0;
复制代码

9.png

可以看出除了字符'514120211411285537'外,其他4个的结果跟上一条的是一样的,这又跟最开始的select相符。

The Last, 当你敲下这句sql的时候;

SELECT * FROM `head` where entry_id = 514120211411285517;
复制代码

Mysql内部会帮你处理成这种语句并执行(两边都转浮点并比对);

SELECT * from `head` where cast(entry_id + 0.0 as unsigned) = cast(514120211411285517+'0' as unsigned);
复制代码

10.png

你看,执行结果还是11条,跟最开始的是完全一模一样的,到这里,疑问点都解答了。来多说几句附加的话:

1.感谢小米团队这篇文章的答疑,这是我能搜到的对这块知识点最详细解答的中文文章了。

2.Mysql官方文档对隐式转换的描述(5.7版本) dev.mysql.com/doc/refman/…

3.也提醒下自己和各位,写sql的时候要小心再细心,一个不注意可能都会导致结果千差万别。

好了,本文也差不多结束了。最后的最后,希望读到这里的你给一点正能量反馈,点个赞转发一下,毕竟原创不易,谢谢。

Supongo que te gusta

Origin juejin.im/post/7047106415231500296
Recomendado
Clasificación