Mysql 隐式类型转换 可能不走索引?
在实际开发和运维过程中有没有发现,在对一张数据量很大的表执行查询的时候,明明 where
条件后面的字段有索引的啊,可是查询耗时却相当长,这是为什么呢?
先说结论:Mysql在 varchar
类型字段的索引中如果发生了隐式类型转换,则索引将失效。
创建user表,具有name
和 age
两个属性,还有一个 id
的主键字段:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_name` (`name`),
KEY `user_age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
我们分别为 name
和 age
这两个字段建立了索引,下面我们就来对这两个索引进行测试和验证。
我们尝试一下用 age
字段进行正常类型的查询,看看执行计划是什么样的:
mysql> explain select * from user where age = 1;
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | user | NULL | ref | user_age | user_age | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
我们可以看到执行计划的 type
列显示为 ref
并且 possible_keys
列为 user_age
表明可能会用到索引,而 key
列的值为 user_age
则表明最终将决定采用 user_age
索引。
age
字段本身是数字类型,那么对数字类型进行隐式类型转换的查询会用到索引吗?我们继续测试,将 where
条件中的数字 1
改为字符串 '1'
:
mysql> explain select * from user where age = '1';
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | user | NULL | ref | user_age | user_age | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
我们看到Mysql的执行计划完全相同,依然会用到 user_age
这个索引。所以Mysql对待int类型字段的隐式类型转换的时候,不会导致索引失效,那么varcher类型字段的隐式类型转换会导致索引失效吗?我们继续测试。
首先验证下使用正常的类型会走索引:
mysql> explain select * from user where name = '1';
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | user | NULL | ref | user_name | user_name | 768 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
可以看到where条件后使用的 varcher
类型的 name
字段,并且 =
后面的数据类型采用的是跟 name
字段定义的类型一致的 varcher
类型,以上结果中的 key
字段为 user_name
表明走了索引。好,那我们将字符串类型的 '1'
改为数字类型的 1
看看效果呢:
mysql> explain select * from user where name = 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | user | NULL | ALL | user_name | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)
咦,我们发现 type
字段变为了 NULL
,possible_keys
字段的值依然是 user_name
,但 key
字段的值变为了 NULL
,说明Mysql还是想用索引的,但最终决定不使用索引。
为什么会这样呢?
Mysql针对类型不一致的值进行比较的时候,会尽最大努力的执行比较,以上案例 name
字段是 varchar
类型,但语句 where name = 1;
中的 1
是数字类型,此时Mysql将 name
字段的值转换为浮点数进行比较,但索引中存储的都是字符串,所以需要将每一条记录值都进行转换,也就需要全表扫描了。