MySQL Limit执行过程分析探索

目录
  • 一、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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL数据库之union,limit和子查询详解

    目录 1.where中的子查询 2.from子句后的子查询 3.union 4.limit查询 5.分页 1.where中的子查询 示例数据参见此文章 案例:查询比最低工资高的员工姓名和薪资 子查询,先查询子查询括号里的,再向上级进行查询 mysql> select ename,sal from emp where sal -> > -> (select min(sal) from emp); +--------+---------+ | ename | sal | +------

  • MySQL 8.0 对 limit 的优化技巧

    目录 一.前言 二.MySQL 8.0 对limit 的改进 三.实践出真知 3.1 默认开启参数 3.2 关闭该参数 四.总结 一.前言 提到 limit 优化,大多数 MySQL DBA 都不会陌生,能想到各种应对策略,比如延迟关联,书签式查询等等,之前我也写过一篇优化的文章:https://www.jb51.net/article/234357.htm ,有兴趣的朋友可以复习一下. 二.MySQL 8.0 对limit 的改进 对于 limit N 带有 group by ,order b

  • MySQL之select、distinct、limit的使用

    目录 1.简介 2.select 2.1 查询单个列 2.2 查询多个列 2.3 查询所有列 3.distinct 4.limit 1.简介 这篇博客将会非常基础,如果有MySQL经验的可以跳过,写这篇博客的原因是给初学者看的.下面将会讲解如何使用select查看指定表的单个列.多个列以及全部列. 首先准备一张表,表结构如下所示: SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- T

  • MySQL 原理与优化之Limit 查询优化

    假设有表tb_sku,其表结构如下: 表中大约有200w条记录,执行如下的sql 语句大约 4.36s 返回数据 select count(*) from tb_sku; 接着我们使用 对其进行分页查找: select * from tb_sku limit 0,10; limit 语句 其中0 代表起始位置,10 为每页返回的数据数量. 如上图所示,很快就返回了查询结果. 接着我们再使用SQL 语句 select * from tb_sku limit 10,10; 语句从记录位置10的位置开

  • MySql nion与Limit查询介绍

    目录 1. union合并查询结果集 2. limit分页查询 1. union合并查询结果集 查询工作岗位是 MANAGER 和 SALESMAN 的员工? mysql> select ename, job from emp where job = 'MANAGER' or job = 'SALESMAN'; mysql> select ename, job from emp where job in('MANAGER', 'SALESMAN'); mysql> select enam

  • 详细介绍mysql中limit与offset的用法

    目录 mysql limit与offset用法 附:Mysql limit offset用法举例 总结 有的时候我们在学习或者工作中会使用到SQL语句,那么介绍一下limit和offset的使用方法. mysql limit与offset用法 mysql里分页一般用limit来实现,例如: 1.select* from user limit 3 表示直接取前三条数据 2.select * from user limit 1,3; 表示取1后面的第2,3,4三条条数据 3.select * fro

  • MySQL Limit执行过程分析探索

    目录 一.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, PRIMA

  • Mysql Limit 分页查询优化详解

    select * from table LIMIT 5,10; #返回第6-15行数据 select * from table LIMIT 5; #返回前5行 select * from table LIMIT 0,5; #返回前5行 我们来写分页 物理分页 select * from table LIMIT (当前页-1)*每页显示条数,每页显示条数; MySQL之Limit简单优化.md 同样是取90000条后100条记录,传统方式还是改造方式? 传统方式是先取了前90001条记录,取其中最

  • MySQL中执行计划explain命令示例详解

    前言 explain命令是查看查询优化器如何决定执行查询的主要方法. 这个功能有局限性,并不总会说出真相,但它的输出是可以获取的最好信息,值得花时间去了解,因为可以学习到查询是如何执行的. 调用EXPLAIN 在select之前添加explain,mysql会在查询上设置一个标记,当执行查询计划时,这个标记会使其返回关于执行计划中每一步的信息,而不是执行它. 它会返回一行或多行信息,显示出执行计划中的每一部分和执行次序. 这是一个简单的explain效果: 在查询中每个表在输出只有一行,如果查询

  • MySQL limit分页大偏移量慢的原因及优化方案

    在 MySQL 中通常我们使用 limit 来完成页面上的分页功能,但是当数据量达到一个很大的值之后,越往后翻页,接口的响应速度就越慢. 本文主要讨论 limit 分页大偏移量慢的原因及优化方案,为了模拟这种情况,下面首先介绍表结构和执行的 SQL. 场景模拟 建表语句 user 表的结构比较简单,id.sex 和 name,为了让 SQL 的执行时间变化更加明显,这里有9个姓名列. CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREME

  • MySQL 语句执行顺序举例解析

    目录 1.SQL数据举例 my_user 表数据 my_order数据 测试数据生成 2.SQL的执行顺序 1.SQL数据举例 举例:有10个用户,输出在订单表中下单数最多的5个人的名字. my_user 表数据 my_order数据 my_order,uid对应my_user表的id: 测试数据生成 写一个存储过程,随机插入10000条数据: CREATE DEFINER=`root`@`localhost` PROCEDURE `test_loop`( ) BEGIN DECLARE i I

  • 前端传参数进行Mybatis调用mysql存储过程执行返回值详解

    目录 查询数据库中的存储过程: 方法一: select `name` from mysql.proc where db = 'your_db_name' and `type`; = 'PROCEDURE' 方法二:  show procedure status; 你要先在数据库中建一个表,然后创建存储过程 我建的表a_tmp,存储过程名称bill_a_forbusiness 执行语句:  CALL bill_a_forbusiness(44,44,52,47,44,46,52,52,349171

  • MySQL EXPLAIN执行计划解析

    目录 前言 1 调用EXPLAIN 2 EXPLAIN中的列 2.1 id 2.2 select_type 2.3 table 2.4 type 2.5 possible_keys 2.6 key 2.7 key_len 2.8 ref 2.9 rows 2.10 Extra 前言 调用EXPLAIN可以获取关于查询执行计划的信息,以及如何解释输出.EXPLAIN命令是查看查询优化器如何决定执行查询的主要方法,但该动能也有局限性,它的选择并不总是最优的,展示的也并不一定是真相. 1 调用EXPL

  • mysql limit 分页的用法及注意要点

    mysql limit 分页的用法及注意事项: 在我们使用查询语句的时候,经常要返回前几条或者中间某几行数据,这个时候怎么办呢?不用担心,mysql已经为我们提供了这样一个功能. SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数.LIMIT 接受一个或两个数字参数.参数必须 是一个整数常量.如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参

  • mysql limit分页优化详细介绍

    mysql limit分页优化 同样是取10条数据 select * from yanxue8_visit limit 10000,10 和 select * from yanxue8_visit limit 0,10 就不是一个数量级别的. 网上也很多关于limit的五条优化准则,都是翻译自MySQL手册,虽然正确但不实用.今天发现一篇文章写了些关于limit优化的,很不错. 文中不是直接使用limit,而是首先获取到offset的id然后直接使用limit size来获取数据.根据他的数据,

  • mysql批量执行sql文件的方法

    1.待执行的sql文件为1.sql.2.sql.3.sql.4.sql等 2.写一个batch.sql文件: source 1.sql; source 2.sql; source 3.sql; source 4.sql; 3.在mysql下执行source /batch文件所在目录/batch.sql; 以上就是小编为大家带来的mysql批量执行sql文件的方法全部内容了,希望大家多多支持我们~

随机推荐