Mysql 隐式类型转换 可能不走索引?

Mysql 隐式类型转换 可能不走索引?

在实际开发和运维过程中有没有发现,在对一张数据量很大的表执行查询的时候,明明 where 条件后面的字段有索引的啊,可是查询耗时却相当长,这是为什么呢?

先说结论:Mysql在 varchar 类型字段的索引中如果发生了隐式类型转换,则索引将失效。

创建user表,具有nameage 两个属性,还有一个 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;

我们分别为 nameage 这两个字段建立了索引,下面我们就来对这两个索引进行测试和验证。

我们尝试一下用 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 字段变为了 NULLpossible_keys 字段的值依然是 user_name ,但 key 字段的值变为了 NULL ,说明Mysql还是想用索引的,但最终决定不使用索引。

为什么会这样呢?

Mysql针对类型不一致的值进行比较的时候,会尽最大努力的执行比较,以上案例 name 字段是 varchar 类型,但语句 where name = 1; 中的 1 是数字类型,此时Mysql将 name 字段的值转换为浮点数进行比较,但索引中存储的都是字符串,所以需要将每一条记录值都进行转换,也就需要全表扫描了。

猜你喜欢

转载自blog.csdn.net/booynal/article/details/125931371