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

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

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

参考资料

相关推荐
porkczr29 分钟前
oracle rac多个实例就相当于多个数据库系统程序
数据库·oracle
码java的秃头阿姨31 分钟前
SpringBoot设置mysql的ssl连接
spring boot·mysql·ssl
程序员大金35 分钟前
基于SpringBoot+Vue+MySQL的养老院管理系统
java·vue.js·spring boot·vscode·后端·mysql·vim
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS网上购物商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
大白菜和MySQL1 小时前
mysql mha高可用集群搭建
数据库·mysql
Ylucius1 小时前
JavaScript 与 Java 的继承有何区别?-----原型继承,单继承有何联系?
java·开发语言·前端·javascript·后端·学习
QQ爱剪辑1 小时前
MySQL基础(13)- MySQL数据类型
数据库·mysql
ღ᭄ꦿ࿐Never say never꧂2 小时前
微服务架构中的负载均衡与服务注册中心(Nacos)
java·spring boot·后端·spring cloud·微服务·架构·负载均衡
.生产的驴2 小时前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq