0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

MySQL用limit为什么会影响性能

Linux爱好者 来源:简书 作者:Muscleape 2022-06-20 16:31 次阅读

有一张财务流水表,未分库分表,目前的数据量为9555695,分页查询使用到了limit,优化之前的查询耗时16 s 938 ms(execution: 16 s 831 ms, fetching: 107 ms),按照下文的方式调整SQL后,耗时347 ms(execution: 163 ms, fetching: 184 ms);

操作:查询条件放到子查询中,子查询只查主键ID,然后使用子查询中确定的主键关联查询其他的属性字段;

原理:减少回表操作,利用延迟关联或者子查询优化超多分页场景。

--优化前SQL
SELECT各种字段
FROM`table_name`
WHERE各种条件
LIMIT0,10;
--优化后SQL
SELECT各种字段
FROM`table_name`main_tale
RIGHTJOIN
(
SELECT子查询只查主键
FROM`table_name`
WHERE各种条件
LIMIT0,10;
)temp_tableONtemp_table.主键=main_table.主键

找到的原理分析:MySQL 用 limit 为什么会影响性能?

前言

首先说明一下MySQL的版本:

mysql>selectversion();
+-----------+
|version()|
+-----------+
|5.7.17|
+-----------+
1rowinset(0.00sec)

表结构:

mysql>desctest;
+--------+---------------------+------+-----+---------+----------------+
|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||
+--------+---------------------+------+-----+---------+----------------+
3rowsinset(0.00sec)

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

灌入大量数据,共500万:

mysql>selectcount(*)fromtest;
+----------+
|count(*)|
+----------+
|5242882|
+----------+
1rowinset(4.25sec)

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

mysql>select*fromtestwhereval=4limit300000,5;
+---------+-----+--------+
|id|val|source|
+---------+-----+--------+
|3327622|4|4|
|3327632|4|4|
|3327642|4|4|
|3327652|4|4|
|3327662|4|4|
+---------+-----+--------+
5rowsinset(15.98sec)

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

mysql>select*fromtestainnerjoin(selectidfromtestwhereval=4limit300000,5)bona.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|
+---------+-----+--------+---------+
5rowsinset(0.38sec)

时间相差很明显。

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

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

类似于下面这张图:fdbcabee-efc7-11ec-ba43-dac502259ad0.jpg

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

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

fdd667fa-efc7-11ec-ba43-dac502259ad0.jpg

其实我也想问这个问题。

证实

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

为了证实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);之后,buffer pool中的数据页的数量远远少于select * from test where val=4 limit 300000,5;对应的数量,因为前一个sql只访问5次数据页,而后一个sql访问300005次数据页。

select*fromtestwhereval=4limit300000,5
mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;Emptyset(0.04sec)

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

mysql>select*fromtestwhereval=4limit300000,5;
+---------+-----+--------+
|id|val|source|
+---------+-----+--------+|
3327622|4|4|
|3327632|4|4|
|3327642|4|4|
|3327652|4|4|
|3327662|4|4|
+---------+-----+--------+
5rowsinset(26.19sec)

mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;
+------------+----------+
|index_name|count(*)|
+------------+----------+
|PRIMARY|4098|
|val|208|
+------------+----------+2rowsinset(0.04sec)

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

select * from test a inner join (select id from test where val=4 limit 300000,5) ;为了防止上次试验的影响,我们需要清空buffer pool,重启mysql。

mysqladminshutdown
/usr/local/bin/mysqld_safe&
mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;

Emptyset(0.03sec)

运行sql:

mysql>select*fromtestainnerjoin(selectidfromtestwhereval=4limit300000,5)bona.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|
+---------+-----+--------+---------+
5rowsinset(0.09sec)

mysql>selectindex_name,count(*)frominformation_schema.INNODB_BUFFER_PAGEwhereINDEX_NAMEin('val','primary')andTABLE_NAMElike'%test%'groupbyindex_name;
+------------+----------+
|index_name|count(*)|
+------------+----------+
|PRIMARY|5|
|val|390|
+------------+----------+
2rowsinset(0.03sec)

我们可以看明显的看出两者的差别:第一个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的数据。

原文标题:一次 SQL 查询优化原理分析:900W+ 数据,从 17s 到 300ms

文章出处:【微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6792

    浏览量

    88724
  • SQL
    SQL
    +关注

    关注

    1

    文章

    753

    浏览量

    44018
  • MySQL
    +关注

    关注

    1

    文章

    795

    浏览量

    26385

原文标题:一次 SQL 查询优化原理分析:900W+ 数据,从 17s 到 300ms

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    MySQL的整体逻辑架构

    支持多种存储引擎是众所周知的MySQL特性,也是MySQL架构的关键优势之一。如果能够理解MySQL Server与存储引擎之间是怎样通过API交互的,将大大有利于理解MySQL的核心
    的头像 发表于 04-30 11:14 408次阅读
    <b class='flag-5'>MySQL</b>的整体逻辑架构

    Redis与MySQL协同升级企业缓存

    传统的MySQL数据库在处理大规模应用时已经到了瓶颈,RedisEnterprise怎样助力突破这一瓶颈?RedisEnterprise与MYSQL共同用作企业级缓存或副本数据库,产生什么样的火花
    的头像 发表于 02-19 13:18 329次阅读
    Redis与<b class='flag-5'>MySQL</b>协同升级企业缓存

    mysql密码忘了怎么重置

    mysql密码忘了怎么重置  MySQL是一种开源的关系型数据库管理系统,密码用于保护数据库的安全性和保密性。如果你忘记了MySQL的密码,可以通过以下几种方法进行重置。 方法一:使用MySQ
    的头像 发表于 12-27 16:51 5884次阅读

    MySQL的执行过程 SQL语句性能优化常用策略

    回顾 MySQL 的执行过程,帮助介绍如何进行 sql 优化。
    的头像 发表于 12-12 10:26 597次阅读
    <b class='flag-5'>MySQL</b>的执行过程 SQL语句<b class='flag-5'>性能</b>优化常用策略

    mysql数据库基础命令

    使用以下命令: mysql -u -p 其中,username是您的MySQL用户名。执行此命令后,系统提示您输入密码。成功输入密码后,您将登录到MySQL命令行界面。 创建数据库
    的头像 发表于 12-06 10:56 526次阅读

    oracle数据库limit怎么

    在Oracle数据库中,可以使用ROWNUM来实现类似LIMIT的功能。ROWNUM是Oracle数据库提供的一个伪列,它在查询结果集中为每一行分配一个唯一的数字。 要使用ROWNUM进行分页查询
    的头像 发表于 12-06 10:05 2046次阅读

    oracle的limit语句

    Oracle数据库中并没有像MySQL或其他数据库那样直接使用"LIMIT"关键字来限制查询结果的行数。在Oracle中,可以使用不同的方法来实现类似的功能。下面将详细介绍这些方法。 1.
    的头像 发表于 12-06 10:03 2821次阅读

    php的mysql无法启动

    ,以便帮助读者快速解决相关问题。 一、安装环境配置检查 1.1 PHP版本检查 在使用PHP连接MySQL之前,首先要确保PHP版本的兼容性。查看所使用的PHP版本是否与MySQL版本兼容,如果不兼容,可能导致
    的头像 发表于 12-04 15:59 1338次阅读

    docker部署mysql的坏处

    的问题和挑战。在这篇文章中,我们将探讨 Docker 部署 MySQL 的坏处。 首先,Docker 部署 MySQL 可能会面临性能问题。尽管 Docker 提供了轻量级的容器化解决方案,但容器化的应用程序在
    的头像 发表于 11-23 09:29 1322次阅读

    MySQL性能优化方法

    MySQL 性能优化是一项关键的任务,可以提高数据库的运行速度和效率。以下是一些优化方法,包括具体代码和详细优化方案。
    的头像 发表于 11-22 09:59 520次阅读

    mysql和sql server区别

    MySQL和SQL Server是两种常见的关系型数据库管理系统(RDBMS),用于存储和管理数据库。虽然它们都支持SQL语言,但在其他方面存在一些显著的区别。以下是MySQL和SQL Server
    的头像 发表于 11-21 11:07 1443次阅读

    MySQL忘记root密码解决方案

    的密码,可能导致无法正常管理MySQL数据库。 这篇文章将提供详尽、详实、细致的解决方案,帮助解决MySQL忘记root密码的问题。 解决方案分为以下几个步骤: 停止MySQL服务:
    的头像 发表于 11-21 11:04 599次阅读

    MySQL导出的步骤

    MySQL是一种常用的关系型数据库管理系统,用于存储和管理大量的结构化数据。在实际应用中,我们经常需要将MySQL数据库中的数据导出到其他地方,如备份数据、数据迁移、数据分析等。下面是使用MySQL
    的头像 发表于 11-21 10:58 729次阅读

    mysql是一个什么类型的数据库

    MySQL是一种关系型数据库管理系统(RDBMS),用于存储和管理大量结构化数据。它被广泛用于各种应用程序和网站的后端,包括电子商务平台、社交媒体网站、金融系统等等。MySQL的特点是性能高、可靠性
    的头像 发表于 11-16 14:43 1632次阅读

    如何提高Mysql数据库的访问瓶颈

    在学习Mysql的时候,我们都有这个常识:对于DB的操作,其实本质上是对于磁盘的操作,如果对于DB的访问次数过多,其实就是涉及了大量的磁盘IO,这就会导致MYsql出现性能上的瓶颈。 项目背景
    的头像 发表于 11-08 16:22 988次阅读
    如何提高<b class='flag-5'>Mysql</b>数据库的访问瓶颈