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

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参数包含多个类型;
  • 字符集类型或校对规则不一致等。

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

参考资料

相关推荐
nongcunqq17 分钟前
abap 操作 excel
java·数据库·excel
rain bye bye1 小时前
calibre LVS 跑不起来 就将setup 的LVS Option connect下的 connect all nets by name 打开。
服务器·数据库·lvs
冻咸鱼1 小时前
MySQL的配置
mysql·配置
Kiri霧2 小时前
Rust开发环境搭建
开发语言·后端·rust
阿里云大数据AI技术2 小时前
云栖实录|MaxCompute全新升级:AI时代的原生数据仓库
大数据·数据库·云原生
不剪发的Tony老师3 小时前
Valentina Studio:一款跨平台的数据库管理工具
数据库·sql
间彧3 小时前
Spring事件监听与消息队列(如Kafka)在实现解耦上有何异同?
后端
间彧3 小时前
Java如何自定义事件监听器,有什么应用场景
后端
叶梅树3 小时前
从零构建A股量化交易工具:基于Qlib的全栈系统指南
前端·后端·算法
间彧3 小时前
CopyOnWriteArrayList详解与SpringBoot项目实战
后端