最近一个后台开发的朋友去面试,面试官问他,“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
结果如下,我们可以看到使用了主键索引。
id=’1’查询
我们把1加上引号,变成字符串’1’,在查看执行计划
EXPLAIN SELECT * FROM t_user WHERE id = '1'
结果如下,可以看到还是使用了主键索引。
我们就想到了原来这是MySQL的类型隐式转换,so easy。
接下来第二个问题,SELECT * FROM t_user WHERE sex = 1 会使用到索引么?
如果你直接套用刚才的类型隐式转换,可能就认为索引生效,实际并不是如此,下面我们来验证下。
测试二:表字段char类型
sex=’1’查询
EXPLAIN SELECT * FROM t_user WHERE sex = '1';
结果如下,索引生效。
sex=1查询
EXPLAIN SELECT * FROM t_user WHERE sex = 1;
结果如下,索引不生效。
测试三:表字段varchar类型
username=’1’查询
先看查询username=’1’的执行计划。
EXPLAIN SELECT * FROM t_user WHERE username = '1'
结果如下,索引生效。
username=1查询
查询username=1的执行计划。
EXPLAIN SELECT * FROM t_user WHERE username = 1
结果如下,索引生效。
这里如果我们在插入一条数据,看看情况会变么?
INSERT INTO `test`.`t_user`(`id`, `username`, `sex`) VALUES (4, '1a', '3');
结果如下,索引失效了。
possible_keys是可能用到的索引,因为我们给该字段建立了索引,所以这里显示了索引字段,但是实际的索引字段key是空值,说明没有使用索引,type=ALL,表示进行了全表扫描,扫描rows=4。
结果分析
以上的结果大大出乎了我们的意料,这下对隐式类型转化更困惑了,什么时候可以转换,什么时候不可以转换?
网上许多博客都会写到隐式转换,但是并不详细,仔细深究又觉得漏洞百出,那我们就从MySQL官方网站去查阅资料,点击MySQL类型转换在线文档。
当运算符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。一些转换是隐式发生的。例如,MySQL 会根据需要自动将字符串转换为数字,反之亦然。
可以使用CAST()函数将数字显式转换为字符串。转换隐式发生在
CONCAT()
函数中,因为它需要字符串参数。
CAST函数
MySQL CAST()
函数的语法如下:
CAST(expression AS TYPE);
CAST()
函数将任何类型的值转换为具有指定类型的值。目标类型可以是以下类型之一:BINARY
,CHAR
,DATE
,DATETIME
,TIME
,DECIMAL
,SIGNED
,UNSIGNED
。
CAST()
函数通常用于返回具有指定类型的值,以便在WHERE,JOIN和HAVING子句中进行比较。
示例如下:
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'
。
我擦,这。。。不可能啊,根据实践是检验真理的唯一标准,我们来测试下,结果如下图
我们再显示使用类型转换操作符验证下
SELECT CAST('1a' AS SIGNED) //返回1
SELECT CAST(' 1' AS SIGNED) //返回1
测试结果表明,许多不同的字符串可以转换为 value 1
,这就是为什么索引字符串列和数字比较,索引失效的原因。
总结
MySQL类型不同的数据进行比较时,会发生隐式类型转换,我们平时常用的就是varchar和int类型的转换,时间戳和常量的转换,其他更多场景请查阅官方文档。
额外注意的是,对于字符串列与数字的比较,MySQL 不能使用列上的索引来快速查找值。