数据库

MySQL 的 in 查询到底走不走索引?

位置:首页 > 数据库 > mysql教程,2019-01-04 09:17
一条 SQL 查询有问题。于是下面就有几个回答说 MySQL 查询 in 是不走索引的!哇,我惊呆了。这种话都出来了!我想证明我是错的,于是我到网上搜索了非常多

一条 SQL 查询有问题。于是下面就有几个回答说 MySQL 查询 in 是不走索引的!

哇,我惊呆了。这种话都出来了!我想证明我是错的,于是我到网上搜索了非常多的文章,什么 MySQL 优化实战,MySQL 军规 36 条,30条SQL优化军规,SQL语句优化原则,mysql语句优化建议,数据库查询优化方法总结等等有非常多的文章在描述,MySQL 中 in 查询不走索引。

这些文章当中有些是 2018 年才发出来的,完全属于误导读者。今天我们一起来看看 MySQL 的 in 查询到底走不走索引?

首先说一下,我的测试环境:

MySQL 版本:SELECT VERSION(); — 5.6.28-cdb2016-log
存储引擎:innodb
一些基本配置,我就不贴了,对于我们这个问题影响的不大。

使用 desc xttblog; 名称查看一下表结构。我就不贴图了,直接给大家贴上建表语句。

1
2
3
4
5
6
7
8
CREATETABLE`xttblog` (
  `id` bigint(20) NOTNULLAUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(255) NOTNULL,
  `title` varchar(255) DEFAULTNULL,
  `page` bigint(20) DEFAULTNULL,
  `status` tinyint(3) unsigned NOTNULLCOMMENT '状态:0 正常,1 冻结,2 删除',
  PRIMARYKEY(`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULTCHARSET=utf8mb4

然后循环插入 100 万条测试数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DELIMITER ;;
    CREATEPROCEDUREtest_insert()
    BEGIN
        declarei int;
        seti = 1 ;
        WHILE (i < 1000000) DO
            INSERTINTOxttblog(`name`,title,`page`,`status`)
                VALUES(CONCAT('xttblog_', i),
                    CONCAT('xttblog_', i),
                i, (SELECTFLOOR(RAND() * 2)));
            seti = i + 1;
        ENDWHILE;
        commit;
END;;
CALL test_insert();

首先我们看一下无索引的情况,xttblog 表中的 name 值已经非常的散了。查询时间 0.55 秒左右。

1
2
SELECT* FROMxttblog WHERE`name` IN
('xttblog_1','xttblog_100','xttblog_1000','xttblog_10000');

我们在查一下 xttblog 表中的 status in 的情况,耗时 17 秒左右。

1
SELECT* FROMxttblog WHERE`status` IN(0,2);

通过 EXPLAIN 对上面两条查询语句进行分析,它们都没有走索引。name 的值因为更分散,查询速度更快。

再来看看有索引的情况下,in 的执行效率。

1
SELECT* FROMxttblog WHEREid IN(1,100,1000,10000);

我们的主键 id 是一个索引,上面语句的查询耗时 0.02 秒左右。

为了比较,我们再给 xttblog 表的 name 字段加上索引。

1
CREATEINDEXindex_xttblog_name ONxttblog(`name`(255));

然后执行下面的查询:

1
2
SELECT* FROMxttblog WHERE`name`
IN('xttblog_1','xttblog_100','xttblog_1000','xttblog_10000');

当 name 字段添加了索引后,in 查询耗时 0.02 毫秒左右。

MySQL  in 查询(图0)

EXPLAIN 分析发现它们的 type 都是 range,表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =,<>,>,>=,<,<=,IS NULL,<=>,BETWEEN,IN() 操作中。而且 possible_keys 的值也表明了分别使用主键索引和 index_xttblog_name 索引。并且在 in 索引的情况下,rows 的值,大家都可以看到是 4。以上就可以证明了,在 MySQL 5.6 版本中,in 在有索引的情况下,会走索引进行查询。

虽然 Extra 的值是 Using where,它表示优化器需要通过索引回表查询数据。使用了 WHERE 从句来限制哪些行将与下一张表匹配或者是返回给用户。Extra 列出现 Using where 表示 MySQL 服务器将存储引擎返回服务层以后再应用 WHERE 条件过滤。

现在来看,在回答题目中的问题。MySQL 的 in 查询在 5.5 以上的版本中存储引擎都是 innodb 的,正常情况下会走索引的!至于 MyISAM 的情况大家自己去研究,或者我以后抽时间了来写!

如果是 5.5 之前的版本确实不会走索引的,在 5.5 之后的版本,MySQL 做了优化。MySQL 在 2010 年发布 5.5 版本中,优化器对 in 操作符可以自动完成优化,针对建立了索引的列可以使用索引,没有索引的列还是会走全表扫描。

比如,5.5 之前的版本(以下都是 5.5 以前的版本)。select * from a where id in (select id from b); 这条 sql 语句它的执行计划其实并不是先查询出 b 表的所有 id,然后再与 a 表的 id 进行比较。mysql 会把 in 子查询转换成 exists 相关子查询,所以它实际等同于这条 sql 语句:select * from a where exists(select * from b where b.id=a.id);

而 exists 相关子查询的执行原理是:循环取出 a 表的每一条记录与 b 表进行比较,比较的条件是 a.id=b.id。看 a 表的每条记录的 id 是否在 b 表存在,如果存在就行返回 a 表的这条记录。

关于 exists 的问题,我们以后抽时间再说。

互联网上的信息太过广泛,但这不应该成为我们掉以轻心的借口,抱着存疑求是的精神进行甄别,任何信息只有在我们实践验证后方可全信,用在生产开发上的知识,不容有失!

尽信书,不如无书!

TAGS:MySQL in 查询

上编:MySQL SSL主从复制配置方法

下编:没有了

猜你喜欢

NewHot