索引失效?查询结果不正确?原来都是隐式转换惹的祸

1. 问题描述

定义一张student表,其中id字段使用varchar类型定义,索引也加在id字段上,表结构的详细定义如下:

表中的数据如下:

sql 复制代码
-- 建表语句
CREATE TABLE `student`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `id_index`(`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- 插入数据
INSERT INTO `student` VALUES ('1834411856582451200', '张三', 22);
INSERT INTO `student` VALUES ('1834524841049149440', '李四', 25);
INSERT INTO `student` VALUES ('1834524841049149441', '王五', 27);

1.1 问题场景一:失效的索引

使用Explain执行计划查看SQL运行情况,其中筛选条件为 where id = 1834411856582451200,即参数为数字

sql 复制代码
explain select * from student where id = 1834411856582451200

从结果可以看到执行结果中type字段的值为all,代表这条查询语句并没有走索引,直接进行了全表扫描

而如果将筛选条件改为 where id = '1834411856582451200',即参数类型为字符,执行Explain查看运行情况。

sql 复制代码
explain select * from student where id = '1834411856582451200'

从结果可以看到type字段的值为const,代表查询走了索引。

【总结】上述对于id字段数据类型的定义为字符型,针对上述例子,如果参数是数字,不走索引;参数为字符,则走索引。

1.2 问题场景二:不准确的查询结果

如果参数值类型是数字,会导致查询结果不正确。

sql 复制代码
select * from student where id = 1834524841049149440

如果参数为字符,则查询结果正确。

sql 复制代码
select * from student where id = '1834524841049149440'

为什么会有上述两种问题的发生呢? 这就涉及到了MySQL的一个机制 -- 隐式转换

2. 隐式转换

2.1 什么是隐式转换?

当两个不同类型 的操作数进行运算 时,MySQL为了使操作数兼容,可能会执行隐式 的数据类型转换。例如,MySQL会根据需要自动将字符串转换为数字

上述例子中,当执行 1+'1'操作时,MySQL会将字符串'1'转换为数字1进行运算。

2.2 隐式转换的规则

  • 如果一个或两个参数为NULL,比较结果为NULL,除了使用<=>对两个NULL进行比较,结果为真。不需要进行转换。

  • 如果比较操作中的两个参数都是字符串 ,会作为字符串进行比较,不会进行类型转换

  • 如果两个参数都是整数 ,会作为整数进行比较,不会进行类型转换

  • 十六进制值如果没有与数字比较,将被视作二进制字符串。

  • 如果一个参数是TIMESTAMPDATETIME列,另一个参数是常量,常量会在比较之前转换为时间戳TIMESTAMP。为了安全起见,进行比较时总是使用完整的日期、时间或时间戳字符串 。例如,为了在使用BETWEEN进行日期或时间值比较时获得最佳结果,请使用CAST()显式转换值为所需的数据类型。

  • 如果一个参数是小数值,比较方式取决于另一个参数。如果另一个参数是小数或整数值,则两个参数作为小数值进行比较;如果另一个参数是浮点值,则作为浮点值进行比较。

  • 所有其他情况下,操作数将作为浮点(双精度)数进行比较。例如,字符串和数字操作数的比较会作为浮点数比较

3. 隐式转换为什么会导致上述问题的发生?

上述规则中提到,如果字符串和数字比较时,会将字符串转换为浮点数与数字比较。

SQL 复制代码
-- 0为假,1为真
SELECT 
    '123abc' = 123,
    'abc123' = 0,
    '0.5' = 0.5 ;

MySQL在对字符串'123abc'和数字123比较时,将字符串'123abc'转成了数字123,因此字符串'123abc'和数字123的比较结果为相等。因此在执行查询的时候,如果传入的参数为123,那么匹配的值可能是'123abc'、'123aaa'、'123'等等,因此可能会导致查出多条不匹配数据的情况。

转换规则

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

【例如】

'123abc' --> 123

'abc123' --> 0

sql 复制代码
SELECT 
    '1834524841049149440' + 0.0,
    '1834524841049149441'+ 0.0,
    '1834524841049149440' = 1834524841049149440,
    '1834524841049149441' = 1834524841049149440

针对于上面根据ID查询查出了结果不匹配的例子,MySQL 在处理超出整数范围的大数字时,会将其转换为浮点数,而浮点数的精度有限,使用浮点数进行比较可能导致精度损失,可以看到 1834524841049149440 和 1834524841049149441 进行转换后,结果是一样的。

而针对于索引失效 的问题,是因为MySQL会先把id字段的值隐式转换成浮点数,然后再跟参数进行比较,然而索引是建在id上的,并不是在转换后的id上的,所以进行转换后的id相当于没有索引,因此查询时会进行全表扫描。

4. 总结

在对数据库表结构设计时,要避免隐式类型转换,隐式转换发生的情景可能有:

  • 字段类型不一致;
  • in参数包含多个类型;
  • 字符集类型或校对规则不一致等。

隐式转换可能会导致索引失效查询结果不准确 等,因此在使用时需要注意数字类型的定义,以及表关联时关联字段的一致性

参考资料

相关推荐
胡图蛋.几秒前
什么是事务
数据库
秋意钟几秒前
Spring新版本
java·后端·spring
小黄人软件3 分钟前
20241220流水的日报 mysql的between可以用于字符串 sql 所有老日期的,保留最新日期
数据库·sql·mysql
张声录18 分钟前
【ETCD】【实操篇(三)】【ETCDCTL】如何向集群中写入数据
数据库·chrome·etcd
无为之士14 分钟前
Linux自动备份Mysql数据库
linux·数据库·mysql
小蜗牛慢慢爬行20 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
小汤猿人类27 分钟前
open Feign 连接池(性能提升)
数据库
A小白590843 分钟前
Docker部署实践:构建可扩展的AI图像/视频分析平台 (脱敏版)
后端
阳冬园1 小时前
mysql数据库 主从同步
数据库·主从同步
goTsHgo1 小时前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc