本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/102

关于MySQL的loose index scan有几点疑问,欢迎看到这篇文章的人一起探讨。

测试表结构:

CREATE TABLE `test` (
`id` int(11) NOT NULL default '0',
`v1` int(10) unsigned NOT NULL default '0',
`v2` int(10) unsigned NOT NULL default '0',
`v3` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `v1_v2_v3` (`v1`,`v2`,`v3`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 select * from test;
+----+----+-----+----+
| id | v1 | v2 | v3 |
+----+----+-----+----+
| 1 | 1 | 0 | 1 |
| 2 | 3 | 1 | 2 |
| 10 | 4 | 10 | 10 |
| 0 | 4 | 100 | 0 |
| 3 | 4 | 100 | 3 |
| 5 | 5 | 9 | 5 |
| 8 | 7 | 3 | 8 |
| 7 | 7 | 4 | 7 |
| 30 | 8 | 15 | 30 |
+----+----+-----+----+ select version();
+-------------+
| version() |
+-------------+
| 5.0.51b-log |
+-------------+

由此我们可以大致画出索引的结构:

下面说下我纠结的实验过程:

mysql> explain select max(v3) from test where v1>3 group by v1,v2;
+----+-------------+-------+-------+---------------+----------+---------+------+------+---------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+------+------+---------------------------------------+
| 1 | SIMPLE | test | range | v1_v2_v3 | v1_v2_v3 | 8 | NULL | 1 | Using where; Using index for group-by |
+----+-------------+-------+-------+---------------+----------+---------+------+------+---------------------------------------+
1 row in set (0.00 sec)

一般来说,MySQL的索引扫描需要定义一个起点和终点,即使需要的数据只是这段索引中的几个,MySQL仍然需要扫描这段索引中的每一个条目,将它们返回给Sever层,Server层根据where条件将存储引擎返回的数据再进行一遍过滤。

但是根据官方文档9.2.1.16 GROUP BY Optimization的描述,这段sql会用到松散扫描索引,那么我有一点疑问:MySQL的loose index scan的工作原理究竟是怎样的呢?我有个猜想:

如上图,MySQL根据索引的前2列(v1,v2)来分组,此时先不读取v3列的值。MySQL扫描时发现(v1,v2)的分组值发生变化时,取上一个节点的v3值(因为是B-Tree,v3的值在相同的(v1,v2)中肯定是有序的,并且是从小到大)。这样的话MySQL就少扫描了一个v3的值。当(v1,v2)重复的越多,MySQL少扫描的v3列的次数越多。

如果MySQL全部读取的话,存储引擎需要将全部的数据返回给Server层,Server层还需要自己判断max(v3),莫不如在扫描索引的时候顺便读取max(v3)了。

当我马上就要说服自己的时候,突然发现Explain结果的rows值为1。难道说MySQL估算只扫描一行就能算出结果?这时,我通过如下命令来看MySQL是如何扫描索引的:

mysql> flush status;
Query OK, 0 rows affected (0.00 sec) mysql> select max(v3) from test where v1>3 group by v1,v2;
+---------+
| max(v3) |
+---------+
| 10 |
| 3 |
| 5 |
| 8 |
| 7 |
| 30 |
+---------+
6 rows in set (0.00 sec) mysql> show session status like 'Handler_%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 0 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 0 |
| Handler_read_key | 15 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 14 |
+----------------------------+-------+
15 rows in set (0.00 sec)

结果令我很惊奇,我们重点观察这两行数据:

| Handler_read_key           | 15    |
| Handler_read_next | 0 |

Handler_read_next为0(Handler_read_next的意思是按照索引叶子节点顺序读取下一个节点的次数。6.1.7 Server Status Variables)。说明MySQL根本没有按照我上面的意思顺序扫描v1>3的叶子节点,到此只有三种解释了:

  • 我猜想的MySQL松散扫描的方法不正确。
  • show session status like 'Handler_%'存在bug,没有计算出正确的值。
  • explain方法的rows列的估算方法存在bug,没有正确的估算出扫描的行数。

在bugs.mysql.com上我找到了有人存在跟我一样的疑惑:Manual does not provide enough details on how loose index scan really works

罢了,我换了个MySQL版本:

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.14 |
+-----------+
1 row in set (0.00 sec)

执行同样的查询:

mysql> flush status;
Query OK, 0 rows affected (0.00 sec) mysql> explain select max(v3) from test where v1>3 group by v1,v2\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: test
partitions: NULL
type: range
possible_keys: v1_v2_v3
key: v1_v2_v3
key_len: 4
ref: NULL
rows: 7
filtered: 100.00
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec) mysql> select max(v3) from test where v1>3 group by v1,v2;
+---------+
| max(v3) |
+---------+
| 10 |
| 3 |
| 5 |
| 8 |
| 7 |
| 30 |
+---------+
6 rows in set (0.00 sec) mysql> show session status like 'Handler_%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 2 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 7 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
18 rows in set (0.00 sec)

我惊喜的发现,换了版本之后,explain输出的rows值正常了,为7。show session status like 'Handler_%';输出的值看起来也正常了:

| Handler_read_key           | 1     |
| Handler_read_last | 0 |
| Handler_read_next | 7 |

正当我以为是MySQL5.7修复了统计的bug时,我突然发现explain的Extra是这样的:

        Extra: Using where; Using index

等等,这是什么鬼,这说明MySQL在运行这条Sql时根本没有使用松散扫描索引,怪不得统计输出结果是正常的。

我在stackoverflow上关于loose index scan的提问:How MySQL implements the loose index scan

参考资料:

关于mysql的loose index scan的几点疑问的更多相关文章

  1. MySQL的loose index scan

    众所周知,InnoDB采用IOT(index organization table)即所谓的索引组织表,而叶子节点也就存放了所有的数据,这就意味着,数据总是按照某种顺序存储的.所以问题来了,如果是这样 ...

  2. MySQL索引扩展(Index Extensions)学习总结

    MySQL InnoDB的二级索引(Secondary Index)会自动补齐主键,将主键列追加到二级索引列后面.详细一点来说,InnoDB的二级索引(Secondary Index)除了存储索引列k ...

  3. MySQL索引的Index method中btree和hash的优缺点

    MySQL索引的Index method中btree和hash的区别 在MySQL中,大多数索引(如 PRIMARY KEY,UNIQUE,INDEX和FULLTEXT)都是在BTREE中存储,但使用 ...

  4. Index Seek和Index Scan的区别

    Index Seek是Sql Server执行查询语句时利用建立的索引进行查找,索引是B树结构,Sql Server先查找索引树的根节点,一级一级向下查找,在查找到相应叶子节点后,取出叶子节点的数据. ...

  5. 数据库的Index Scan V.S. Rscan

    一直在做performance,但直到今天才完成了这个第一天应该完成的图,到底Index scan和Rscan的分界点在哪里?   如下图所示,很简单的一个查询,只是查询int,分别强制走索引和表扫描 ...

  6. skip index scan

    官网对skip index scan的解释: Index skip scans improve index scans by nonprefix columns since it is often f ...

  7. index seek与index scan

    原文地址:http://blog.csdn.net/pumaadamsjack/article/details/6597357 低效Index Scan(索引扫描):就全扫描索引(包括根页,中间页和叶 ...

  8. 浅析MySQL中的Index Condition Pushdown (ICP 索引条件下推)和Multi-Range Read(MRR 索引多范围查找)查询优化

    本文出处:http://www.cnblogs.com/wy123/p/7374078.html(保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误 ...

  9. MySQL ICP(Index Condition Pushdown)特性

    一.SQL的where条件提取规则 在ICP(Index Condition Pushdown,索引条件下推)特性之前,必须先搞明白根据何登成大神总结出一套放置于所有SQL语句而皆准的where查询条 ...

随机推荐

  1. SQL Server 结构分解

    关系引擎和存储引擎是SQL Server 的两大组件,其中关系引擎也叫查询处理器,它包括查询优化器.命令解析器.查询执行器.存储引擎管理所有的数据及涉及的IO,它包括事务管理器和数据访问方法和缓冲区管 ...

  2. 自学HTML5难 我们应该怎么做

    互联网发展到今天,越来越多的技术岗位人才出现了稀缺的状态,就拿当前的HTML5来讲,基本成为了每家互联网公司不可缺少的人才.如果抓住这个机会,把HTML5搞好,那么前途不可限量,而且这门行业是越老越吃 ...

  3. (12.05)Java小知识!

     今天与大家分享关于抽象类的知识点. 抽象类: 抽象类应用场景:在某种情况下,某个父类只是知道子类应该包含怎样的方法,但无法准确的知道这些子类如何实现这些方法. 从多一个具有相同特征的类中抽象出一个抽 ...

  4. 天梯赛 L2-019. 悄悄关注 map

    L2-019. 悄悄关注 时间限制 150 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 新浪微博上有个"悄悄关注",一个用 ...

  5. nginx: [emerg] the "ssl" parameter requires ngx_http_ssl_module in /usr/local/nginx/conf/nginx.conf:37

    一:开始Nginx的SSL模块 1.1 Nginx如果未开启SSL模块,配置Https时提示错误 1 nginx: [emerg] the "ssl" parameter requ ...

  6. IIS网站本机可以访问但局域网其他机器无法访问 解决方法

    在IIS部署网站的时候,发现只有本机可以访问,通过localhost和IP地址都可以,但是当局域网其他机器访问我的网站的时候,无响应. 我的是WIN10的系统 解决方法:   网站所对应的端口必须对外 ...

  7. google guava cache缓存基本使用讲解

    代码地址:https://github.com/vikde/demo-guava-cache 一.简介 guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存 ...

  8. <算法>进制转换超详细

    16转10 用竖式计算: 16进制数的第0位的权值为16的0次方,第1位的权值为16的1次方,第2位的权值为16的2次方 第0位: 5 * 16^0 = 5 第1位: F * 16^1 = 240 第 ...

  9. Activiti源代码分析

    ExecutionEntity内部含有parent,是一个运行树或运行路径.应该是一个流程实例的运行过程,一个实例相应一个ExecutionEntity,通过getActivity得到的是当前正在运行 ...

  10. 《深入理解java虚拟机》笔记——简析java类文件结构

    一直不太搞得明确jvm究竟是如何进行类载入的,在看资料的过程中迷迷糊糊.在理解类载入之前,首先看看java的类文件结构究竟是如何的,都包含了哪些内容. 最直接的參考当然是官方文档:The Java® ...