关于mysql的loose index scan的几点疑问
本文同时发表在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的几点疑问的更多相关文章
- MySQL的loose index scan
众所周知,InnoDB采用IOT(index organization table)即所谓的索引组织表,而叶子节点也就存放了所有的数据,这就意味着,数据总是按照某种顺序存储的.所以问题来了,如果是这样 ...
- MySQL索引扩展(Index Extensions)学习总结
MySQL InnoDB的二级索引(Secondary Index)会自动补齐主键,将主键列追加到二级索引列后面.详细一点来说,InnoDB的二级索引(Secondary Index)除了存储索引列k ...
- MySQL索引的Index method中btree和hash的优缺点
MySQL索引的Index method中btree和hash的区别 在MySQL中,大多数索引(如 PRIMARY KEY,UNIQUE,INDEX和FULLTEXT)都是在BTREE中存储,但使用 ...
- Index Seek和Index Scan的区别
Index Seek是Sql Server执行查询语句时利用建立的索引进行查找,索引是B树结构,Sql Server先查找索引树的根节点,一级一级向下查找,在查找到相应叶子节点后,取出叶子节点的数据. ...
- 数据库的Index Scan V.S. Rscan
一直在做performance,但直到今天才完成了这个第一天应该完成的图,到底Index scan和Rscan的分界点在哪里? 如下图所示,很简单的一个查询,只是查询int,分别强制走索引和表扫描 ...
- skip index scan
官网对skip index scan的解释: Index skip scans improve index scans by nonprefix columns since it is often f ...
- index seek与index scan
原文地址:http://blog.csdn.net/pumaadamsjack/article/details/6597357 低效Index Scan(索引扫描):就全扫描索引(包括根页,中间页和叶 ...
- 浅析MySQL中的Index Condition Pushdown (ICP 索引条件下推)和Multi-Range Read(MRR 索引多范围查找)查询优化
本文出处:http://www.cnblogs.com/wy123/p/7374078.html(保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误 ...
- MySQL ICP(Index Condition Pushdown)特性
一.SQL的where条件提取规则 在ICP(Index Condition Pushdown,索引条件下推)特性之前,必须先搞明白根据何登成大神总结出一套放置于所有SQL语句而皆准的where查询条 ...
随机推荐
- SQL Server 结构分解
关系引擎和存储引擎是SQL Server 的两大组件,其中关系引擎也叫查询处理器,它包括查询优化器.命令解析器.查询执行器.存储引擎管理所有的数据及涉及的IO,它包括事务管理器和数据访问方法和缓冲区管 ...
- 自学HTML5难 我们应该怎么做
互联网发展到今天,越来越多的技术岗位人才出现了稀缺的状态,就拿当前的HTML5来讲,基本成为了每家互联网公司不可缺少的人才.如果抓住这个机会,把HTML5搞好,那么前途不可限量,而且这门行业是越老越吃 ...
- (12.05)Java小知识!
今天与大家分享关于抽象类的知识点. 抽象类: 抽象类应用场景:在某种情况下,某个父类只是知道子类应该包含怎样的方法,但无法准确的知道这些子类如何实现这些方法. 从多一个具有相同特征的类中抽象出一个抽 ...
- 天梯赛 L2-019. 悄悄关注 map
L2-019. 悄悄关注 时间限制 150 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 新浪微博上有个"悄悄关注",一个用 ...
- 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 ...
- IIS网站本机可以访问但局域网其他机器无法访问 解决方法
在IIS部署网站的时候,发现只有本机可以访问,通过localhost和IP地址都可以,但是当局域网其他机器访问我的网站的时候,无响应. 我的是WIN10的系统 解决方法: 网站所对应的端口必须对外 ...
- google guava cache缓存基本使用讲解
代码地址:https://github.com/vikde/demo-guava-cache 一.简介 guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存 ...
- <算法>进制转换超详细
16转10 用竖式计算: 16进制数的第0位的权值为16的0次方,第1位的权值为16的1次方,第2位的权值为16的2次方 第0位: 5 * 16^0 = 5 第1位: F * 16^1 = 240 第 ...
- Activiti源代码分析
ExecutionEntity内部含有parent,是一个运行树或运行路径.应该是一个流程实例的运行过程,一个实例相应一个ExecutionEntity,通过getActivity得到的是当前正在运行 ...
- 《深入理解java虚拟机》笔记——简析java类文件结构
一直不太搞得明确jvm究竟是如何进行类载入的,在看资料的过程中迷迷糊糊.在理解类载入之前,首先看看java的类文件结构究竟是如何的,都包含了哪些内容. 最直接的參考当然是官方文档:The Java® ...