【MySQL系列】谈谈MySQL类型转换对索引的影响

2022/04/20 数据库 共 2631 字,约 8 分钟

最近一个后台开发的朋友去面试,面试官问他,“mysql数据库某字段为int类型,已建立该字段的索引,在查询的时候字段值为数字加了引号,会用到索引么?”。

如果继续问,“某字段varchar类型,建立索引后,字段值为纯数字不加引号,会用到索引么?”。

这两个问题看似差不多啊,其实很容易让人混淆,实际上这两个问题的答案是相反的。

先说结论:问题1索引生效,问题2索引不生效。

为了搞清楚其中的原因,我们先写个demo测试验证下是否正确。

MySQL建表

mysql数据库创建一张表,id是主键,username字段为varchar类型,sex字段为char类型,以上字段均有索引。DDL语句如下:

CREATE TABLE `t_user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(16) NOT NULL,
  `sex` char(2) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `usernmae` (`username`) USING BTREE,
  KEY `sex` (`sex`) USING BTREE
) ENGINE=InnoDB;

我们插入几条数据用于测试

INSERT INTO `test`.`t_user`(`id`, `username`, `sex`) VALUES (1, 'yindongxu', '0');
INSERT INTO `test`.`t_user`(`id`, `username`, `sex`) VALUES (2, 'test', '1');
INSERT INTO `test`.`t_user`(`id`, `username`, `sex`) VALUES (3, '1', '2');

测试一:表字段为int类型

我们使用explain来查看 id=1 和 id=’1’ 这两种查询方式的执行计划。我们用的主键索引,暂不考虑其他字段建立索引查询条数较多导致索引失效的场景。

id=1查询

EXPLAIN SELECT * FROM t_user WHERE id = 1

结果如下,我们可以看到使用了主键索引。

image-20220423224712219

id=’1’查询

我们把1加上引号,变成字符串’1’,在查看执行计划

EXPLAIN SELECT * FROM t_user WHERE id = '1'

结果如下,可以看到还是使用了主键索引。

image-20220423224939068

我们就想到了原来这是MySQL的类型隐式转换,so easy。

接下来第二个问题,SELECT * FROM t_user WHERE sex = 1 会使用到索引么?

如果你直接套用刚才的类型隐式转换,可能就认为索引生效,实际并不是如此,下面我们来验证下。

测试二:表字段char类型

sex=’1’查询

EXPLAIN SELECT * FROM t_user WHERE sex = '1';

结果如下,索引生效。

image-20220424092720222

sex=1查询

EXPLAIN SELECT * FROM t_user WHERE sex = 1;

结果如下,索引不生效。

image-20220424092939629

测试三:表字段varchar类型

username=’1’查询

先看查询username=’1’的执行计划。

EXPLAIN SELECT * FROM t_user WHERE username = '1'

结果如下,索引生效。

image-20220423232443567

username=1查询

查询username=1的执行计划。

 EXPLAIN SELECT * FROM t_user WHERE username = 1

结果如下,索引生效。

image-20220423232656955

这里如果我们在插入一条数据,看看情况会变么?

INSERT INTO `test`.`t_user`(`id`, `username`, `sex`) VALUES (4, '1a', '3');

结果如下,索引失效了。

image-20220424093927117

possible_keys是可能用到的索引,因为我们给该字段建立了索引,所以这里显示了索引字段,但是实际的索引字段key是空值,说明没有使用索引,type=ALL,表示进行了全表扫描,扫描rows=4。

结果分析

以上的结果大大出乎了我们的意料,这下对隐式类型转化更困惑了,什么时候可以转换,什么时候不可以转换?

网上许多博客都会写到隐式转换,但是并不详细,仔细深究又觉得漏洞百出,那我们就从MySQL官方网站去查阅资料,点击MySQL类型转换在线文档

当运算符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。一些转换是隐式发生的。例如,MySQL 会根据需要自动将字符串转换为数字,反之亦然。

可以使用CAST()函数将数字显式转换为字符串。转换隐式发生在 CONCAT()函数中,因为它需要字符串参数。

CAST函数

MySQL CAST()函数的语法如下:

CAST(expression AS TYPE);

CAST()函数将任何类型的值转换为具有指定类型的值。目标类型可以是以下类型之一:BINARYCHARDATEDATETIMETIMEDECIMALSIGNEDUNSIGNED

CAST()函数通常用于返回具有指定类型的值,以便在WHEREJOINHAVING子句中进行比较。

示例如下:

SELECT CAST('1' AS SIGNED);	//返回整数1

SELECT CAST(1 AS CHAR);	//返回char

SELECT CONCAT(1);	//返回字符串

这样看来,测试一是int和varchar做了类型转换,其结果符合上面的说明。

测试2和测试3的varchar、char和int两种类型就不能做类型转换么?

官方文档说明如下:

对于字符串列与数字的比较,MySQL 不能使用列上的索引来快速查找值。如果 str_col是索引字符串列,则在以下语句中执行查找时不能使用索引:

SELECT * FROM tbl_name WHERE str_col=1;

这样做的原因是有许多不同的字符串可以转换为 value 1,例如 '1',' 1''1a'

我擦,这。。。不可能啊,根据实践是检验真理的唯一标准,我们来测试下,结果如下图

image-20220424111631506

我们再显示使用类型转换操作符验证下

SELECT CAST('1a' AS SIGNED)	//返回1

SELECT CAST('  1' AS SIGNED)	//返回1

测试结果表明,许多不同的字符串可以转换为 value 1,这就是为什么索引字符串列和数字比较,索引失效的原因。

总结

MySQL类型不同的数据进行比较时,会发生隐式类型转换,我们平时常用的就是varchar和int类型的转换,时间戳和常量的转换,其他更多场景请查阅官方文档。

额外注意的是,对于字符串列与数字的比较,MySQL 不能使用列上的索引来快速查找值。

文档信息

搜索

    Table of Contents