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
进行比较,结果为真。不需要进行转换。 -
如果比较操作中的两个参数都是字符串 ,会作为字符串进行比较,不会进行类型转换。
-
如果两个参数都是整数 ,会作为整数进行比较,不会进行类型转换。
-
十六进制值如果没有与数字比较,将被视作二进制字符串。
-
如果一个参数是
TIMESTAMP
或DATETIME
列,另一个参数是常量,常量会在比较之前转换为时间戳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参数包含多个类型;
- 字符集类型或校对规则不一致等。
隐式转换可能会导致索引失效 、查询结果不准确 等,因此在使用时需要注意数字类型的定义,以及表关联时关联字段的一致性。