MySQL 用 limit 为什么会影响性能


Posted in MySQL onSeptember 15, 2021

首先说明一下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)

为了达到相同的目的,我们一般会改写成如下语句:

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;的查询过程:

查询到索引叶子节点数据。
根据叶子节点上的主键值去聚簇索引上查询需要的全部字段值。

类似于下面这张图:

MySQL 用 limit 为什么会影响性能

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

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

MySQL 用 limit 为什么会影响性能

证实:

下面我们实际操作一下来证实上述的推论:

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

我只能通过间接的方式来证实:

InnoDB中有buffer pool。里面存有最近访问过的数据页,包括数据页和索引页。所以我们需要运行两个sql,来比较buffer pool中的数据页的数量。预测结果是运行select * from test a inner join (select id from test where val=4 limit 300000,5) b>之后,buffer pool中的数据页的数量远远少于select * from test where val=4 limit 300000,5;对应的数量,因为前一个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>为了防止上次试验的影响,我们需要清空buffer pool,重启mysql。

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_shutdowninnodb_buffer_pool_load_at_startup,这两个选项能够控制数据库关闭时dump出buffer pool中的数据和在数据库开启时载入在磁盘上备份buffer pool的数据。

到此这篇关于MySQL 用 limit 为什么会影响性能的文章就介绍到这了,更多相关MySQL 使用 limit的性能影响 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL创建索引需要了解的
Apr 08 MySQL
MySQL慢查询的坑
Apr 28 MySQL
MySQL索引知识的一些小妙招总结
May 10 MySQL
Mysql文件存储图文详解
Jun 01 MySQL
如何自己动手写SQL执行引擎
Jun 02 MySQL
MySQL定时备份数据库(全库备份)的实现
Sep 25 MySQL
Mysql关于数据库是否应该使用外键约束详解说明
Oct 24 MySQL
Mysql数据库表中为什么有索引却没有提高查询速度
Feb 24 MySQL
MySQL创建管理LIST分区
Apr 13 MySQL
以MySQL5.7为例了解一下执行计划
Apr 13 MySQL
为什么MySQL8新特性会修改自增主键属性
Apr 18 MySQL
MySQL去除密码登录告警的方法
Apr 20 MySQL
一次MySQL启动导致的事故实战记录
Sep 15 #MySQL
MySQL中几种插入和批量语句实例详解
Sep 14 #MySQL
MySQL 如何限制一张表的记录数
Sep 14 #MySQL
MySQL into_Mysql中replace与replace into用法案例详解
Sep 14 #MySQL
MYSQL 的10大经典优化案例场景实战
Sep 14 #MySQL
MySQL中连接查询和子查询的问题
mysql配置SSL证书登录的实现
You might like
PHP获取windows登录用户名的方法
2014/06/24 PHP
Yii2实现自定义独立验证器的方法
2017/05/05 PHP
php过滤htmlspecialchars() 函数实现把预定义的字符转换为 HTML 实体用法分析
2019/06/25 PHP
基于jquery的bankInput银行卡账号格式化
2012/08/22 Javascript
jQuery中对节点进行操作的相关介绍
2013/04/16 Javascript
angularjs中的e2e测试实例
2014/12/06 Javascript
EasyUI实现第二层弹出框的方法
2015/03/01 Javascript
Bootstarp 基础教程之表单部分实例代码
2017/02/03 Javascript
JavaScript中创建对象的7种模式详解
2017/02/21 Javascript
详解ESLint在Vue中的使用小结
2018/10/15 Javascript
JavaScript前端页面搜索功能案例【基于jQuery】
2019/07/10 jQuery
微信小程序3种位置API的使用方法详解
2019/08/05 Javascript
微信小程序bindtap事件与冒泡阻止详解
2019/08/08 Javascript
详解JavaScript中的Object.is()与"==="运算符总结
2020/06/17 Javascript
详解Python设计模式编程中观察者模式与策略模式的运用
2016/03/02 Python
使用python3实现操作串口详解
2019/01/01 Python
Python3实现的简单三级菜单功能示例
2019/03/12 Python
Python+OpenCV+pyQt5录制双目摄像头视频的实例
2019/06/28 Python
python PyAutoGUI 模拟鼠标键盘操作和截屏功能
2019/08/04 Python
python-opencv获取二值图像轮廓及中心点坐标的代码
2019/08/27 Python
python实现人机猜拳小游戏
2020/02/03 Python
详解Python yaml模块
2020/09/23 Python
python-地图可视化组件folium的操作
2020/12/14 Python
CSS中的字体大小设置属性总结
2016/05/24 HTML / CSS
HTML5是什么 HTML5是什么意思 HTML5简介
2012/10/26 HTML / CSS
亚历山大·王官网:Alexander Wang
2017/06/23 全球购物
香港彩色隐形眼镜在线商店:Stunninglens(全球免费送货)
2019/05/10 全球购物
新入职员工的自我介绍演讲稿
2014/01/02 职场文书
《盘古开天地》教学反思
2014/02/28 职场文书
意向协议书范本
2014/04/23 职场文书
计算机网络专业求职信
2014/06/05 职场文书
运动会班级口号
2014/06/09 职场文书
个人党性分析材料
2014/12/19 职场文书
故意伤害罪辩护词
2015/05/21 职场文书
小学六年级班主任工作经验交流材料
2015/11/02 职场文书
低门槛开发iOS、Android、小程序应用的前端框架详解
2021/10/16 Javascript