MySQL Limit执行过程分析探索

 更新时间:2022年12月12日 15:06:16   作者:爱吃南瓜糕的北络  
limit是MySql的内置函数,一般用于查询表中记录的条数,作用是用于限制查询条数,下面这篇文章主要给大家介绍了关于SQL中limit函数语法与用法的相关资料,详细讲了MySQL Limit执行过程

故事还得从下面的图说起:

what? 两条sql执行结果的id列居然不一致。。。。。。

一、LIMIT 处理过程

为了故事的顺利发展,我们得先创建一张表:

CREATE TABLE `t_null_index` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `key1` char(1) DEFAULT NULL,
  `common_field` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_key1` (`key1`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3

表 t_null_index 包含3个列,id列是主键,key1列是二级索引列。表中包含9999条数据。

mysql> select * from t_null_index order by key1 limit 1;
+-------+------+----------------------------------+
| id    | key1 | common_field                     |
+-------+------+----------------------------------+
| 10019 | a    | a9ecd8f845cd4e6791e99af406e075c1 |
+-------+------+----------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from t_null_index order by key1 limit 1;
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------+
| id | select_type | table        | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra |
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------+
|  1 | SIMPLE      | t_null_index | NULL       | index | NULL          | idx_key1 | 4       | NULL |    1 |   100.00 | NULL  |
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

当我们执行上面的这条sql,是使用了 idx_key1 二级索引,这个好理解,因为在二级索引idx_key1中,key1列是有序的。而查询是要取按照key1列排序的第1条记录,那MySQL只需要从idx_key1中获取到第一条二级索引记录,然后直接回表得到完整聚簇索引的记录返回客户端即可。

但是如果我们把上边语句的 limit 1 换成 limit 5000, 1,效果会如何?

mysql> select * from t_null_index order by key1 limit 5000, 1;
+-------+------+----------------------------------+
| id    | key1 | common_field                     |
+-------+------+----------------------------------+
| 10125 | e    | e90499ca17b44727ab44a08c1cf609e8 |
+-------+------+----------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from t_null_index order by key1 limit 5000, 1;
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table        | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | SIMPLE      | t_null_index | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9847 |   100.00 | Using filesort |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.01 sec)

当 limit 1 换成 limit 5000, 1 后,我们发现没有使用 idx_key1 二级索引,反而使用了全表扫描,并且进行 Using filesort。

开始我很不理解,limit 5000, 1 也可以使用二级索引 idx_key1啊,我们可以先扫描到第5001条二级索引记录,对5001条二级索引记录通过主键id回表取得完成聚簇索引记录不就好了吗?这样的代价也比全表扫描+filesort牛批啊。

Limit具体是怎么搞?

我们知道,MySQL 内部其实是分为 server层 和 存储引擎层,具体 server层和存储引擎层具体的交互这里就不说了。

对于limit的操作,MySQL是在server层准备向客户端发送记录的时候才会去处理limit子句中的内容。

select * from t_null_index order by key1 limit 5000, 1;

如果使用 idx_key1 索引执行上述查询,那么MySQL会这样处理:

(1)server层向InnoDB要第1条记录,InnoDB从idx_key1中获取到第1条二级索引记录,然后进行回表操作得到完整的聚簇索引记录,然后返回给server层。server层准备将其发送给客户端,此时发现还有个limit 5000, 1的要求,意味着符合条件的记录中的第5001条才可以返回给客户端,则不能将记录返回给客户端,同时会先记录下当前是第1条。

(2)server层再向InnoDB要下一条记录,InnoDB再根据二级索引记录的next_record属性找到下一条二级索引记录,再次进行回表得到完整的聚簇索引记录返回给server层。server层再将其发送给客户端的时候发现当前记录仍然不是5001条,所以就放弃了将记录发送给客户端,同时将记录数+1。

(3)。。。重复上述操作

(4)直到server层发现InnoDB返回的聚簇索引记录是5001条的时候,server层才会将InnoDB返回的完整聚簇索引记录发送给客户端。

从上述过程中我们可以看出,由于MySQL中是server层实际向客户端发送记录前才会判断limit子句是否符合要求,所以如果使用二级索引执行上述查询的话,意味着需要进行5001次回表操作。server层在执行执行计划分析的时候会觉得执行这么多次回表的成本太大了,还不如直接 全表扫描+filesort 快呢,所以就选择了 全表扫描+filesort 执行查询。

二、开始的图

说着说着,差点忘记了故事的前奏的图了😂

奇怪了?为什么都是 limit 5000,1,而两条sql执行结果的id列居然不一致,我们来看一下两条sql的执行计划:

mysql> explain select id from t_null_index limit 5000, 1;
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table        | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_null_index | NULL       | index | NULL          | idx_key1 | 4       | NULL | 9847 |   100.00 | Using index |
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t_null_index limit 5000, 1;
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table        | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | t_null_index | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9847 |   100.00 | NULL  |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

通过执行计划,我们可以看出 select id from t_null_index limit 5000, 1;这条sql执行过程采用了idx_key1,我们上面说到 limit 5000, 1 这个条件意味着会进行5001次回表操作,为什么这里又走了 idx_key1 索引呢?

其实,由于 select id 查询的查询列表只有一个 id 列,而 idx_key1 索引的叶子节点包含了 索引列key1+主键id的信息,故MySQL可以通过仅扫描二级索引idx_key1,然后无需回表操作直接就可以获取到想要的id列并且返回server层,server层再判断是否满足第5001条记录,如果不满足,再向InnoDB要下一条记录,直到满足为止。这样就省去了5001条记录的回表操作,从而大大提升了查询效率。

那到底为啥两条sql执行结果的id列值不一样?

我们来画一画 idx_key1索引的示意图,如图所示:

通过图上,我们可以看出 idx_key1 索引B+树的叶子节点,根据key1值由左向右升序排列,当key1列相同的节点,则按照id升序由左向右排序。

mysql> explain select id from t_null_index limit 5000, 1;
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table        | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_null_index | NULL       | index | NULL          | idx_key1 | 4       | NULL | 9847 |   100.00 | Using index |
+----+-------------+--------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

对于上述SQL,由于扫描二级索引 idx_key1,其实结果集是按照 key1 和 id 这两个键进行排序的,可以通过 select * from t_null_index order by key1, id limit 5000, 1; 来验证结果的id列是否和上面图中的SQL结果一致。而对于select * from t_null_indexlimit 5000, 1; 该SQL由于走全表扫描并且默认按照主键id升序排序,两条SQL执行的排序规则不一致,所以就会导致两条结果的id列值不一致。

通过上图,我们可以看出,扫描 idx_key1 索引列的SQL 和 显示 order by key1,id 的SQL的执行结果id列值是相同的。

那如果显示对 select * from t_null_index order by key1 limit 5000, 1; 结果会如何?

通过执行结果,我们可以看出扫描 idx_key1 索引列的SQL 和 显示 order by key1 的SQL的执行结果id列值还是不相同的。

根据前面我们的分析,我们知道 select id from t_null_index limit 5000, 1;会通过扫描二级索引 idx_key1 来获得结果集,并且结果集是按照 key1 和 id 这两个键进行排序的。而对于 select * from t_null_index order by key1 limit 5000, 1; 这条SQL执行会直接全表扫描后再在引擎层根据key1进行文件堆排序。这种排序的结果集存在根据key1升序的情况下,相同的key1,id列可能是乱序,所以就会出现图中两个值不相等的情况。

附:select * from t_null_index order by key1 limit 5000, 1; 执行计划

mysql> explain select * from t_null_index order by key1 limit 5000, 1;
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table        | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | SIMPLE      | t_null_index | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9847 |   100.00 | Using filesort |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

附:select * from t_null_index order by key1 limit 4990, 20; 出现乱序的情况

到此这篇关于MySQL Limit执行过程分析探索的文章就介绍到这了,更多相关MySQL Limit内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 利用Prometheus与Grafana对Mysql服务器的性能监控详解

    利用Prometheus与Grafana对Mysql服务器的性能监控详解

    Prometheus是源于 Google Borgmon的一个开源监控系统,用 Golang开发。被很多人称为下一代监控系统。Grafana是一个开源的图表可视化系统,简单说图表配置比较方便、生成的图表比较漂亮。下面就介绍了利用Prometheus与Grafana对Mysql服务器性能监控的方法。
    2017-03-03
  • mysql严格模式Strict Mode详细说明

    mysql严格模式Strict Mode详细说明

    使用mysql严格模式可以使数据更加安全严格,缺点是减少了对空数据入库的兼容性,下面这篇文章主要给大家介绍了关于mysql严格模式Strict Mode详细说明的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • MySQL导入导出助手类库MysqlHelper安装使用

    MySQL导入导出助手类库MysqlHelper安装使用

    这篇文章主要为大家介绍了MySQL导入导出助手类库MysqlHelper安装使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • 一篇文章带你了解MySQL之undo日志

    一篇文章带你了解MySQL之undo日志

    Undo日志也叫做回滚日志,是MySQL数据库当中一种重要的日志,用于记录更新操作之前的数据状态,这篇文章主要给大家介绍了关于如何通过一篇文章带你了解MySQL之undo日志的相关资料,需要的朋友可以参考下
    2023-06-06
  • centos7中如何利用crontab进行mysql定时备份

    centos7中如何利用crontab进行mysql定时备份

    crontab是一个命令,常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令,下面这篇文章主要给大家介绍了关于centos7中如何利用crontab进行mysql定时备份的相关资料,需要的朋友可以参考下
    2022-02-02
  • MySQL如何比较两个表数据的差异

    MySQL如何比较两个表数据的差异

    这篇文章主要介绍了MySQL比较两个表数据的差异,这些方式可以根据具体需求和数据结构选择合适的方法来比较两个表的数据差异,本文给大家介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • MySQL复制之GTID复制的具体使用

    MySQL复制之GTID复制的具体使用

    从MySQL 5.6.5开始新增了一种基于GTID的复制方式,本文主要介绍了MySQL复制之GTID复制的具体使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • MySQL中DATE_FORMATE函数使用时的注意点

    MySQL中DATE_FORMATE函数使用时的注意点

    这篇文章主要介绍了MySQL中DATE_FORMATE函数使用时的注意点,主要是针对其内置的字符集使用时需要转换而进行说明,需要的朋友可以参考下
    2015-05-05
  • MySQL查询优化:用子查询代替非主键连接查询实例介绍

    MySQL查询优化:用子查询代替非主键连接查询实例介绍

    对多的两张表,一般是一张表的外键关联到另一个表的主键,接下来为大家介绍下用子查询代替非主键连接查询,感兴趣的朋友可以参考下哈,希望对你有所帮助
    2013-04-04
  • Linux下Mysql5.6 二进制安装过程

    Linux下Mysql5.6 二进制安装过程

    这篇文章主要介绍了Linux下Mysql5.6 二进制安装过程,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-06-06

最新评论