本文同时发表在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. input常见类型

       值   描述  text   默认.定义单行输入字段,用户可在其中输入文本.默认是 20 个字符  button   定义可点击的按钮(大多与 JavaScript 使用来启动脚本)  chec ...

  2. Python学习笔记整理总结【Memcache & Redis】

    一.Memcached1.简介Memcached 是一个高性能的分布式内存对象缓存系统,一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度.提高可扩展性.用来存储 ...

  3. Makefile Android.mk 引发的思索

    在我们编写 Android 平台 cocos2d-x 游戏的时候,我们除了编写 Classes 之内的源代码文件之外,我们还需要维护其编译文件 Android.mk,如我们在 Classes 添加新的 ...

  4. laravel 表单验证 Exists 规则的基本使用方法

    public function rules(){ return [ 'm_pushing_frequency_level_id' => 'integer|required|exists:m_pu ...

  5. spring boot 错误,求大神帮解决

    Exception in thread "main" java.lang.IllegalStateException: Failed to read Class-Path attr ...

  6. 状态压缩dp第一题

    标签: ACM 题目: Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; ...

  7. ASP.NET Core 一步步搭建个人网站(持续更新中~~~)

    摘要 ASP.NET Core2.0发布有一阵子了,这是.NET 开源跨平台的一个重大里程碑, 也意味着比1.0版本要更加成熟.目前.net core具有开源.跨平台.灵活部署.模块化架构等等特性,吸 ...

  8. Mybatis(三)返回值

    Mybatis返回值 MyBatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap,resultType是直接表示返回类型的,而resultMap则 ...

  9. 1、C#基础 - C# 语言简介

    C# 语言和 .NET Framework 介绍 C# 是类型安全的面向对象的精妙语言,可帮助开发者生成在 .NET Framework 上运行的各种安全可靠的应用程序. C# 可用于创建 Windo ...

  10. Android ListView与RecycleView的对比使用

    ListView,就如其名,是用来显示列表的一种View,而RecycleView,是其的加强版,今天带来的是这两个几乎具有相同的功能的对比使用 先从ListView说起吧 ListView: 1.在 ...