关于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查询条 ...
随机推荐
- input常见类型
值 描述 text 默认.定义单行输入字段,用户可在其中输入文本.默认是 20 个字符 button 定义可点击的按钮(大多与 JavaScript 使用来启动脚本) chec ...
- Python学习笔记整理总结【Memcache & Redis】
一.Memcached1.简介Memcached 是一个高性能的分布式内存对象缓存系统,一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度.提高可扩展性.用来存储 ...
- Makefile Android.mk 引发的思索
在我们编写 Android 平台 cocos2d-x 游戏的时候,我们除了编写 Classes 之内的源代码文件之外,我们还需要维护其编译文件 Android.mk,如我们在 Classes 添加新的 ...
- laravel 表单验证 Exists 规则的基本使用方法
public function rules(){ return [ 'm_pushing_frequency_level_id' => 'integer|required|exists:m_pu ...
- spring boot 错误,求大神帮解决
Exception in thread "main" java.lang.IllegalStateException: Failed to read Class-Path attr ...
- 状态压缩dp第一题
标签: ACM 题目: Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; ...
- ASP.NET Core 一步步搭建个人网站(持续更新中~~~)
摘要 ASP.NET Core2.0发布有一阵子了,这是.NET 开源跨平台的一个重大里程碑, 也意味着比1.0版本要更加成熟.目前.net core具有开源.跨平台.灵活部署.模块化架构等等特性,吸 ...
- Mybatis(三)返回值
Mybatis返回值 MyBatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap,resultType是直接表示返回类型的,而resultMap则 ...
- 1、C#基础 - C# 语言简介
C# 语言和 .NET Framework 介绍 C# 是类型安全的面向对象的精妙语言,可帮助开发者生成在 .NET Framework 上运行的各种安全可靠的应用程序. C# 可用于创建 Windo ...
- Android ListView与RecycleView的对比使用
ListView,就如其名,是用来显示列表的一种View,而RecycleView,是其的加强版,今天带来的是这两个几乎具有相同的功能的对比使用 先从ListView说起吧 ListView: 1.在 ...