MySQL 用 limit 为什么会影响性能?

1.前言

首先说明一下MySQL的版本:

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.17    |
+-----------+
1 row in set (0.00 sec)

表结构:

mysql> desc test;
+--------+---------------------+------+-----+---------+----------------+
| Field  | Type                | Null | Key | Default | Extra          |
+--------+---------------------+------+-----+---------+----------------+
| id     | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| val    | int(10) unsigned    | NO   | MUL | 0       |                |
| source | int(10) unsigned    | NO   |     | 0       |                |
+--------+---------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

id为自增主键,val为非唯一索引。

灌入大量数据,共500万:

mysql> select count(*) from test;
+----------+
| count(*) |
+----------+
|  5242882 |
+----------+
1 row in set (4.25 sec)

我们知道,当limit offset rows中的offset很大时,会出现效率问题:

mysql> select * from test where val=4 limit 300000,5;
+---------+-----+--------+
| id      | val | source |
+---------+-----+--------+
| 3327622 |   4 |      4 |
| 3327632 |   4 |      4 |
| 3327642 |   4 |      4 |
| 3327652 |   4 |      4 |
| 3327662 |   4 |      4 |
+---------+-----+--------+
5 rows in set (15.98 sec)

为了达到同样的目的,我们通常改写如下句子:

[En]

In order to achieve the same goal, we usually rewrite the following sentence:

mysql> select * from test a inner join (select id from test where val=4 limit 300000,5) b on a.id=b.id;
+---------+-----+--------+---------+
| id      | val | source | id      |
+---------+-----+--------+---------+
| 3327622 |   4 |      4 | 3327622 |
| 3327632 |   4 |      4 | 3327632 |
| 3327642 |   4 |      4 | 3327642 |
| 3327652 |   4 |      4 | 3327652 |
| 3327662 |   4 |      4 | 3327662 |
+---------+-----+--------+---------+
5 rows in set (0.38 sec)

时间相差很明显。

为什么会出现上面的结果?我们看一下select * from test where val=4 limit 300000,5;的查询过程:

查询索引叶节点数据。

[En]

Query the index leaf node data.

根据叶节点上的主键值对索引上的查询所需的所有字段值进行聚集。

[En]

All the field values needed for the query on the index are clustered according to the primary key values on the leaf node.

类似于下面这张图:

MySQL 用 limit 为什么会影响性能?

像上面这样,需要查询300005次索引节点,查询300005次聚簇索引的数据,最后再将结果过滤掉前300000条,取出最后5条。MySQL耗费了大量随机I/O在查询聚簇索引的数据上,而有300000次随机I/O查询到的数据是不会出现在结果集当中的。

肯定会有人问:既然一开始是利用索引的,为什么不先沿着索引叶子节点查询到最后需要的5个节点,然后再去聚簇索引中查询实际数据。这样只需要5次随机I/O,类似于下面图片的过程:

MySQL 用 limit 为什么会影响性能?

其实我也想问这个问题。

证实

让我们在实践中证实上述推论:

[En]

Let’s confirm the above inference in practice:

为了证实 select * from test where val=4 limit 300000,5是扫描300005个索引节点和300005个聚簇索引上的数据节点,我们需要知道MySQL有没有办法统计在一个sql中通过索引节点查询数据节点的次数。我先试了Handler_read_*系列,很遗憾没有一个变量能满足条件。

我只能间接地证明:

[En]

I can only prove it indirectly:

InnoDB中有buffer pool。里面存有最近访问过的数据页,包括数据页和索引页。所以我们需要运行两个sql,来比较buffer pool中的数据页的数量。预测结果是运行 select * from test a inner join (select id from test where val=4 limit 300000,5) b>&#x4E4B;&#x540E;&#xFF0C;buffer pool中的数据页的数量远远少于 <code>select * from test where val=4 limit 300000,5;</code>对应的数量,因为前一个sql只访问5次数据页,而后一个sql访问300005次数据页。

select * from test where val=4 limit 300000,5

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
Empty set (0.04 sec)

可以看出,目前buffer pool中没有关于test表的数据页。

mysql> select * from test where val=4 limit 300000,5;
+---------+-----+--------+
| id      | val | source |
+---------+-----+--------+
| 3327622 |   4 |      4 |
| 3327632 |   4 |      4 |
| 3327642 |   4 |      4 |
| 3327652 |   4 |      4 |
| 3327662 |   4 |      4 |
+---------+-----+--------+
5 rows in set (26.19 sec)
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
+------------+----------+
| index_name | count(*) |
+------------+----------+
| PRIMARY    |     4098 |
| val        |      208 |
+------------+----------+
2 rows in set (0.04 sec)

可以看出,此时buffer pool中关于test表有4098个数据页,208个索引页。

select * from test a inner join (select id from test where val=4 limit 300000,5) b>&#x4E3A;&#x4E86;&#x9632;&#x6B62;&#x4E0A;&#x6B21;&#x8BD5;&#x9A8C;&#x7684;&#x5F71;&#x54CD;&#xFF0C;&#x6211;&#x4EEC;&#x9700;&#x8981;&#x6E05;&#x7A7A;buffer pool&#xFF0C;&#x91CD;&#x542F;mysql&#x3002;

mysqladmin shutdown
/usr/local/bin/mysqld_safe &
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
Empty set (0.03 sec)

运行sql:

mysql> select * from test a inner join (select id from test where val=4 limit 300000,5) b on a.id=b.id;
+---------+-----+--------+---------+
| id      | val | source | id      |
+---------+-----+--------+---------+
| 3327622 |   4 |      4 | 3327622 |
| 3327632 |   4 |      4 | 3327632 |
| 3327642 |   4 |      4 | 3327642 |
| 3327652 |   4 |      4 | 3327652 |
| 3327662 |   4 |      4 | 3327662 |
+---------+-----+--------+---------+
5 rows in set (0.09 sec)

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
+------------+----------+
| index_name | count(*) |
+------------+----------+
| PRIMARY    |        5 |
| val        |      390 |
+------------+----------+
2 rows in set (0.03 sec)

我们可以看明显的看出两者的差别:第一个sql加载了4098个数据页到buffer pool,而第二个sql只加载了5个数据页到buffer pool。符合我们的预测。也证实了为什么第一个sql会慢:读取大量的无用数据行(300000),最后却抛弃掉。

而且这会造成一个问题:加载了很多热点不是很高的数据页到buffer pool,会造成buffer pool的污染,占用buffer pool的空间。
遇到的问题

为了在每次重启时确保清空buffer pool,我们需要关闭innodb_buffer_pool_dump_at_shutdown和innodb_buffer_pool_load_at_startup,这两个选项能够控制数据库关闭时dump出buffer pool中的数据和在数据库开启时载入在磁盘上备份buffer pool的数据。

本博文转载自博文:

MySQL用limit为什么会影响性能

参考资料:

1.https://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/

2.https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-buffer-pool-tables.html

Original: https://www.cnblogs.com/yif0118/p/15318219.html
Author: 人无名,则可专心练剑
Title: MySQL 用 limit 为什么会影响性能?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/508116/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • JavaScript进阶内容——jQuery

    JavaScript进阶内容——jQuery 我们在前面的文章中已经掌握了JavaScript的全部内容,现在让我们了解一下JavaScript库 这篇文章主要是为了为大家大致讲解…

    数据库 2023年6月14日
    097
  • MySQL隐式转换的坑

    MySQL以以下规则描述比较操作如何进行转换: 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 实际使用中经研究发现字符串和数字比较时,优先把字符串转换为…

    数据库 2023年6月9日
    063
  • 建造者设计模式

    可以看到这个学生类的属性是非常多的,所以构造方法不是一般的长,如果我们现在直接通过new的方式去创建: 可以看到,我们光是填参数就麻烦,我们还得一个一个对应着去填,一不小心可能就把…

    数据库 2023年6月14日
    074
  • SpringBoot自动配置

    @SpringBootApplication SpringBoot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来…

    数据库 2023年6月16日
    090
  • StoneDB 亮相中国信通院OSCAR开源产业大会,石原子科技正式加入科技制造开源社区!

    2022 年 9 月 16 日,由中国信息通信研究院(以下简称”信通院”)主办的”2022 OSCAR 开源产业大会”活动于北京成功…

    数据库 2023年6月11日
    089
  • 翻译|使用 StatefulSet 运行数据库应用

    本文介绍 Kubernetes 支持数据库等有状态应用的常见解决方案:StatefulSet。 我们在构建机器学习向量管理时面临的一个重要问题是:如何持久化数据,避免数据丢失? […

    数据库 2023年5月24日
    092
  • Oracle 有排序的分页SQL写法

    第一种: 第二种: 第一种比第二种效率会高,这是因为:CBO 优化模式下,Oracle可以将外层的查询条件推到内层查询中,以提高内层查询的执行效率。对于第一个查询语句,第二层的查询…

    数据库 2023年6月16日
    083
  • SQL语句实战学习

    参考:https://zhuanlan.zhihu.com/p/38354000再次感谢作者的整理!! 1.数据已提前准备好了,已知有如下4张表:学生表:student 成绩表:s…

    数据库 2023年6月16日
    081
  • RPC学习

    设计协议 相对于 HTTP 的用处,RPC 更多的是负责应用间的通信,所以性能要求相对更高。但 HTTP 协议的数据包大小相对请求数据本身要大很多,又需要加入很多无用的内容,比如换…

    数据库 2023年6月16日
    0101
  • jdbc-实现用户登录业务(解决sql注入问题)

    package com.cqust; import java.sql.*;import java.util.HashMap;import java.util.Map;import …

    数据库 2023年5月24日
    062
  • 设置颜色与画笔

    设置画笔 默认情况下,Graphics绘图类 使用的画笔属性是粗细为I个像素的正方形,而Graphics2D类可以调用 setStroke()方法 设置画笔的属性,如改变线条的粗细…

    数据库 2023年6月16日
    088
  • Mysql之Binlog

    1、简述 binlog 二进制日志文件,这个文件记录了MySQL所有的DML操作。通过binlog日志我们可以做数据恢复,增量备份,主主复制和主从复制等等。 2、Docker中无法…

    数据库 2023年5月24日
    0123
  • Markdown学习

    Markdown学习 标题 三级标题 四级标题 字体 hello word hello word hello word hello word 引用 环境更加hi举报 分割线 图片 …

    数据库 2023年6月11日
    0100
  • 实现一个简单的Database3(译文)

    前文回顾 实现一个简单的Database1(译文) 实现一个简单的Database2(译文) 实现一个简单的Database3(译文) 译注:cstsck在github维护了一个简…

    数据库 2023年6月11日
    087
  • django中的auth模块与admin后台管理

    1. auth模块 在创建完django项目之后,执行数据库迁移之后,数据库里会增加很多新表,其中有一张名为auth_user的表,当访问django自带的路由admin的时候,需…

    数据库 2023年6月14日
    085
  • Docker Bridge 网络原理

    Docker 的网络子系统是可插拔驱动式的,默认情况下存在或支持多种网络接口,如 bridge、host、overlay、macvlan 以及 none 类型的网络接口。 brid…

    数据库 2023年6月6日
    087
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球