Mysql 大表limit查询优化原理实战
文章目录
- 1 大表查询无条件优化&原理(入门)
- 2 大表查询带 条件 优化&原理(进阶)
- 2.1 where 后面的查询字段只有一个时,要求该字段是索引字段
- 2.2 where 后面的查询字段有多个时,尽量让查询字段为索引字段且字段值基数大
- 3 大表查询带 排序 优化&原理(进阶)
- 3.1 没有where 条件,那么推荐使用 order by create_time desc。
- 3.2 where 条件查询字段中,如果存在索引字段 ,且当前查询不需要回表,那么推荐使用 order by id desc
- 3.查询字段加了索引,一定会用上吗(最左匹配原则-模糊搜索案例~ 会颠覆你的三观)
1 大表查询无条件优化&原理(入门)
优化前( 查询耗时 114.1s)
select * from link_exec_task limit 80000, 10 # 查询耗时 114.1s
优化后( 查询耗时 0.121s)
select * from link_exec_task a
INNER JOIN (select id from link_exec_task limit 80000, 10) b on a.id = b.id #0.121s
原理(索引覆盖+非聚簇索引):
观察以下sql 查询计划
查询优化原理::
主键ID形成的索引是聚簇索引(B+树),叶子节点存的是记录(数据);而普通索引字段形成的索引是非聚簇索引,非聚簇索引的叶子节点里面存的是记录的ID。
查找第8w条记录,聚簇索引和非聚簇索引 查找对比:
聚簇索引方式:由于聚簇索引-数据页里面的数据项存的是完整的数据记录,故而一个数据页里面的数据项占用空间大,即一个数据页只能存少数的数据项,即一次磁盘IO查询出来的数据页只包含少数的数据项。因此当limit 偏移量很大时,就得通过多次的磁盘IO来查找对应的偏移量的记录,很慢!
非聚簇索引方式:由于非聚簇索引-数据页里面的数据项存的是记录的ID,故而一个数据页里面的数据项占用空间很小,即一个数据页可以存更多的数据项,即一次磁盘IO查询出来的数据页只包含很多个数据项。因此当limit 偏移量很大时,只select id的情况下,只需通过少次数的磁盘IO就能快速找到对应偏移量的记录ID,很快~。
故而上面就算我查询哪怕没有用到 status字段进行查询,Innodb执行引擎也会借助status索引字段使用的非聚簇索引,来快使找到第8w条记录的ID。
问1:为什么借助的是status索引字段,而不使用其他非聚簇索引字段?
答:我的理解是 会挑一个索引字段空间占用最小的字段,在该表的索引字段中,就status字段的空间占用最小。因为这里借助非聚簇索引的目的是找出数据页里面的id值,而不关心目录页里面的索引字段值,由于目录页存的内容是索引字段值和数据页的地址,故而索引字段越小,那么一个目录页能存放更多的目录项,这些目录页又可以指向数据页的地址,故而可以通过更少次数的IO来找到更多的数据页。
问2:子查询里面可能select id吗?我还能select 其他吗?
答:正常情况下不行,例如你select * ,那么此时你的查询耗时会和优化前查询耗时一样,因为此时你的查询不是覆盖索引查询,由于非聚簇索引里面没有你要select的内容,故而innodb直接就不借助非聚簇索引找第8w条记录的ID;而是直接从聚簇索引里面找出第8w条记录,故而速度会变得很慢。
当然,如果你select 的字段均包含在某个非聚簇索引树里面,那么此时还是会使用覆盖索引,还是走非聚簇索引,查询速度也会快。
问3:上文的业务逻辑是查询第8W条数据之后的10条记录,假设使用聚簇索引进行扫描,那么只需扫描数据页下的8w条数据项;那么innodb借助了status非聚簇索引进行查询第8w条记录之后的10个ID,是否也只需要扫描8w条数据项?
答:一般不止。因为在聚簇索引中,数据页里面的数据项(完整数据记录)的ID是升序的。但是在非聚簇索引中,索引结构是根据索引字段值进行排序,所以数据页里面的数据项的ID是无序的。所以我的理解是,innodb需要把非聚簇索引所有数据页里面所有的数据项的ID都给查询出来,然后再根据ID进行排序,最终返回ID第8w大的10条ID记录。
2 大表查询带 条件 优化&原理(进阶)
经过上文的学习,我们找到大表主要可以利用 非聚簇索引+覆盖索引 的方式进行目标记录ID的快速查找。
select * from link_exec_task a
INNER JOIN (select id from link_exec_task limit 80000, 10) b on a.id = b.id
这一节主要讲 “select id from link_exec_task limit 80000, 10” 这部分 加条件和排序 的优化。因此下面的示例也是针对该部分的sql进行改造。
2.1 where 后面的查询字段只有一个时,要求该字段是索引字段
以下文SQL为例:
假设status字段不是索引字段:那么innodb就无法找到 包含status字段的非聚簇索引树,故而只能利用聚簇索引树进行数据过滤,而聚簇索引的一次磁盘IO只能扫描到少数量的数据项(上一节的知识),故而该SQL查询非常耗时!
假设status字段是索引字段:那么innodb就可以找到 status字段对应的非聚簇索引树,根据该非聚簇索引快速找到符合查询条件的数据页,由于非聚簇索引的数据页里面的数据项只有记录ID(上一节的知识),即一个数据页可以包含大量的数据项。故而只需要少量的数据页就能表示符合条件的数据项,故而通过少次数的磁盘IO就能把对应的数据页给查找出来,故而该SQL查询非常快!
select id from link_exec_task where status=3 limit 80000, 10
2.2 where 后面的查询字段有多个时,尽量让查询字段为索引字段且字段值基数大
注:字段值基数大是指 字段值重复率低,越低越好。例如ID的基数大,因为ID不能重复。基数越大,越有利于查找树的快速查找。
(1)以下文SQL为例,假设status字段是索引字段,而scene_type字段不是索引字段。status字段值基数大、小 时的查询耗时:
经过status过滤后,此时会根据符合status条件的这些ID进行回表,然后根据ID对应的所有记录,然后再根据 scene_type = 1 进行过滤,筛选和合适的ID。如果status字段基数很大,那么回表次数会很少,此时总体查询耗时短;反之,回表次数会很多,从而造成查询耗时急剧加长。
select id from link_exec_task where status=3 and edge_id = '04001' limit 80000, 10
(2)以下文SQL为例,假设status字段是索引字段,而edge_id 字段也是索引字段。
此时innodb会进行优化分析,有可能将这两个索引进行合并,即索引合并index_merge,也有可能只使用其中一个索引。
如果使用了索引合并index_merge:那么查询速度会很快,因为此时innodb会使用status和edge_id 这两个非聚簇索引树,分别根据查询条件进行过滤,过滤出两组ID集合后,再对ID集合取交集(因为条件是and方式),此过程无需回表,就可以找出符合条件的ID集合。
如果只使用其中一个索引:那么查询耗时就取决于作为索引的那个字段值基数大不大,如果基数大,那么耗时短,否则耗时就很长,具体原因见上文(1)。
select id from link_exec_task where status=3 and edge_id = '04001' limit 80000, 10
问:什么时候会使用索引合并,什么时候只会使用其中一个索引。
答:和你字段范围、字段的存储内容、条件查询内容有关。理论上,表数据量大 + limit offert偏移量大 情况下,索引合并 会比 “只使用其中一个索引” 的查询速度快很多。但有时候innodb就是会选择“只使用一个索引”,导致优化后查询速度反而变慢,这玩意儿感觉也有点捉摸不透,让人蛋疼。
相同查询条件,查询内容不一样,实际使用的索引可能也不一样。
条件里面有多个索引,只实际使用一个聚簇索引可能导致的查询急剧下降:
见下图:
3 大表查询带 排序 优化&原理(进阶)
假设当前表中,有id主键字段,也有creat_time索引字段,前端需要展示最新记录,那么此时就必须使用id或者create_time 进行倒序排序,具体使用哪个字段呢?选择合适排序字段能让你的查询速度天差地别。
3.1 没有where 条件,那么推荐使用 order by create_time desc。
结合下图的explain结果:
见下图无where条件时,使用id和create_time进行倒序排序,时间相差甚大,这是为什么?
如果没有where条件且使用id进行倒序排序时:由于id没有专门的非聚簇索引树,所以innodb使用的是聚簇索引查找,从聚簇索引里面的最后一个数据页的数据项从后晚前找,直到找到第8w条数据项之后的10条记录id。上文已经说过,聚簇索引的数据页里面的数据项是完整的数据记录,即一个数据项占用空间很大,一个数据页只能包含少量的数据项,故而需要查找大量的数据页,即通过大量的磁盘IO 才能把这8w条数据项所在数据页给扫描出来,速度很慢。
如果没有where条件且使用ceate_time进行倒序排序时:由于create_time有专门的非聚簇索引树,此时innodb使用的是非聚簇索引查找,从ceate_time非聚簇索引里面的最后一个数据页的数据项从后晚前找,直到找到第8w条数据项之后的10条记录id。上文已经说过,非聚簇索引的数据页里面的数据项是记录ID,即一个数据项占用空间很小,一个数据页可以包含大量的数据项,故而只需查找少量的数据页,即通过少量的磁盘IO 就能把这8w条数据项所在数据页给扫描出来,速度很慢。
以下SQL查询耗时:27.166s
select id from link_exec_task ORDER BY id desc limit 100000, 10 # 查询耗时:27.166s
以下SQL查询耗时:0.143s
select id from link_exec_task ORDER BY id desc limit 100000, 10 # 查询耗时:0.143s
3.2 where 条件查询字段中,如果存在索引字段 ,且当前查询不需要回表,那么推荐使用 order by id desc
结合下图的explain结果:
看explain分析结果,再结合上文那些说明,其实你也应该懂了。
答:where 条件查询字段中,存在status索引查询字段 ,根据条件status=0 innodb使用status非聚簇索引树过滤掉数据后,拿到符合条件的记录ID集合,此时分以下两种情况:
假设当前是根据id进行排序排序: 将过滤后ID集合进行快速倒序排序,然后找出第10w条记录后面的10条记录ID,不需要回表,故而速度很快。
假设当前是根据create_time进行排序排序: 由于当前是create_time字段进行倒序排序,而且上一步status=0过滤后只有记录的ID集合,innodb执行引擎没办法对这些ID集合进行create_time倒序排序,故而只能拿着过滤后的ID集合,到聚簇索引树进行查找记录,即回表,根据ID对应的create_time值后,再根据ID所对应的Create_time值,对ID集合进行排序。由于我这张表中,status字段基数值不大,也就是status=0过滤后的ID集合还是很大,就会导致后面出现频繁回表,从而造成查询速度急剧下降。
以下SQL执行耗时:29.082s
select id from link_exec_task where status = 0 ORDER BY create_time desc limit 100000, 10
以下SQL执行耗时:0.057s
select id from link_exec_task where status = 0 ORDER BY id desc limit 100000, 10
3.查询字段加了索引,一定会用上吗(最左匹配原则-模糊搜索案例~ 会颠覆你的三观)
答案是不一定,innodb会根据 你当前 是否有使用limit分页、分页的偏移量大小、查询字段存储值基数大小、条件查询内容、排序字段 等 来决定当前使不使用 该查询字段索引。‘’
下面以 分页的偏移量大小、以及是否有使用limit分页 影响innodb查询方式进行演示:
下这里我的name字段配置了普通索引,理论来说,使用最左匹配原则的模糊搜索就能使用到索引,但事实真的是这样吗?见下图。
下面两个图中,我对name使用最左匹配原则的模糊搜索,但实际上却没有使用到name索引,也就是没有走非聚簇索引。而是走了主键索引,从而导致我的查询速度极速下降。 这innodb的优化还不如不优化!!!!
把limit偏移量调小:
查询耗时:22.153s
select id from link_exec_task where name like '定时%' order by id desc limit 0, 10 # 查询耗时 22.153s
把limit偏移量调大:
我把上面的sql limit偏移量调到5000,结果查询速度反而快了几百倍,见下图:
查询耗时:0.095s
select id from link_exec_task where name like '定时%' order by id desc limit 5000, 10 # 查询耗时 0.095s
上面说了,使用limit 偏移量小慢,偏移量大反而块。如果我直接把分页limit 给去掉,是怎么样子的呢?
直接把limit偏移量去掉:
查询耗时:0.075s
select id from link_exec_task where name like '定时%' order by id desc # 查询耗时 0.075s
总结:至此,我们明白了有时候我们使用 索引字段进行搜索,但实际不一定会使用到该索引,影响使用哪种索引的因素很多,具体使用哪种索引还是由innodb决定。要注意的是,innodb优化 有时候不一定会加快查询,反而导致查询速度变慢几十、甚至几百倍。例如像上文的 最左匹配原则的模糊搜索 理论上走普通字段的非聚簇索引更快,但是它有时候却使用了主键索引;有例如有时候 查询使用到了多索引字段查询,但实际没有使用索引合并,而是只使用了其中一个索引,造成查询速度变慢。
相关文章:

Mysql 大表limit查询优化原理实战
文章目录 1 大表查询无条件优化&原理(入门)2 大表查询带 条件 优化&原理(进阶)2.1 where 后面的查询字段只有一个时,要求该字段是索引字段2.2 where 后面的查询字段有多个时,尽量让查询字段为索引字段且字段值基数大 3 大表查询带 排序 优化&…...

在vscode中开发运行uni-app项目
确保电脑已经安装配置好了node、vue等相关环境依赖 进行项目的创建 vue create -p dcloudio/uni-preset-vue 项目名 vue create -p dcloudio/uni-preset-vue uni-app 选择模版 这里选择【默认模版】 项目创建成功后在vscode中打开 第一次打开项目 pages.json 文件会报错&a…...

【JavaEE初阶 — 多线程】Thread的常见构造方法&属性
目录 Thread类的属性 1.Thread 的常见构造方法 2.Thread 的几个常见属性 2.1 前台线程与后台线程 2.2 setDaemon() 2.3 isAlive() Thread类的属性 Thread 类是JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread 对象与之关联&am…...

ctfshow(316)--XSS漏洞--反射性XSS
Web316 进入界面: 审计 显示是关于反射性XSS的题目。 思路 首先想到利用XSS平台解题,看其他师傅的wp提示flag是在cookie中。 当前页面的cookie是flagyou%20are%20not%20admin%20no%20flag。 但是这里我使用XSS平台,显示的cookie还是这样…...

ubuntu22.04安装conda
在 Ubuntu 22.04 上安装 Conda 可以通过以下步骤进行: 下载 Miniconda(轻量级版本的 Conda): 打开终端并运行以下命令以下载 Miniconda 安装脚本: wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-…...

D58【python 接口自动化学习】- python基础之异常
day58 异常捕获 学习日期:20241104 学习目标:异常 -- 74 自定义异常捕获:如何定义业务异常? 学习笔记: 自定义异常的用途 自定义异常的方法 # 抛出一个异常 # raise ValueError(value is error) # ValueError: val…...

Java项目实战II基于Spring Boot的便利店信息管理系统(开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在快节奏的…...

Java-日期计算工具类DateCalculator
DateCalculator是用于日期计算的工具类。这个工具类将包括日期的加减、比较、周期计算、日期 范围生成等功能。 import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Period; import java.time.temporal.ChronoUnit;…...

单片机串口接收状态机STM32
单片机串口接收状态机stm32 前言 项目的芯片stm32转国产,国产芯片的串口DMA接收功能测试不通过,所以要由原本很容易配置的串口空闲中断触发DMA接收数据的方式转为串口逐字节接收的状态机接收数据 两种方式各有优劣,不过我的芯片已经主频跑…...

ipv6的 fc00(FC00::/7) 和 fec0(FEC0::/10)
ipv6的 fc00(FC00::/7)(唯一本地地址) 替代了 fec0(FEC0::/10) (站点本地地址,已弃用) ipv6的 fc00(FC00::/7) 替代了 fec0(FEC0::/10) , 在IPv6地址中,FC00::/7(通常简写为FC00)和…...

Chat GPT英文学术写作指令
目录 Chat GPT英文学术写作指令 Chat GPT英文学术写作指令 1."为我捉供一些建议和技巧,以提高我的学术写作质最和风格" (Provide me with some suggestions andtips to improve the quality andstyleofmyacademic writing.) 2."帮我提写一个清晰而有逻辑的…...

超详细Pycharm安装汉化教程,Python环境配置和使用指南,Python零基础入门看这个就够了!
包含编程资料、学习路线图、源代码、软件安装包等!【[点击这里]】! PyCharm 是由 JetBrains 打造的一款 Python IDE (集成开发环境,Integrated Development Environment),带有一整套可以帮助用户在使用 Py…...

react-native:解决使用webView后部分场景在安卓10崩溃闪退问题
app闪退问题原因: 安卓7以上版本(7和7以下版本不会出现闪退):在屏幕不可视区域加载webView或者webView不在可视区域内切换页面时app崩溃闪退(在屏幕可视区域加载webView或者webView在可视区域内切换页面不会闪退&…...

大数据工具 flume 的安装配置与使用 (详细版)
参考网址:Flume 1.9用户手册中文版 — 可能是目前翻译最完整的版本了 1,上传安装包 安装包链接:文件下载-奶牛快传 Download |CowTransfer 口令:x8bhcg 1,切换盘符到安装目录 cd /opt/moudles 解压文件…...

智慧城市智慧城市项目方案-大数据平台建设技术方案(原件Word)
第1章 总体说明 1.1 建设背景 1.2 建设目标 1.3 项目建设主要内容 1.4 设计原则 第2章 对项目的理解 2.1 现状分析 2.2 业务需求分析 2.3 功能需求分析 第3章 大数据平台建设方案 3.1 大数据平台总体设计 3.2 大数据平台功能设计 3.3 平台应用 第4章 政策标准保障…...

C语言比较两个字符串是否相同
在不使用string.h中的内置函数的情况下 #include <stdio.h> #include <string.h> void main(){char arr1[]"hello world";char arr2[]"hello world";int i,a0;if(strlen(arr1)!strlen(arr2)){print("不相等");return 0;}for(i0;arr…...

丹摩征文活动|FLUX.1图像生成模型:AI工程师的创新实践
文章目录 一、对"FLUX.1"系列模型版本分析 二、AI工程师与FLUX.1系列模型 三、ComfyUI在线部署四、FLUX.1在线部署五、添加工作流呈现效果图展示六、总结 黑森林实验室(Black Forest Labs)推出的FLUX.1图像生成模型,凭借120亿参数的…...

ZABBIX API获取监控服务器OS层信息
Zabbix 是一款强大的开源监控解决方案,能够通过其 API 接口自动化管理和获取监控数据。在这篇文章中,详细讲解如何通过 Zabbix API 批量获取服务器的系统名称、IP 地址及操作系统版本信息,并将数据保存到 CSV 文件中。本文适合对 Python 编程和 Zabbix 监控系统有一定基础的…...

SpringBoot基础系列学习(五):JdbcTemplate 访问数据库
文章目录 一丶介绍二丶引入依赖三丶配置配置文件四丶创建表五丶java代码 一丶介绍 Spring Boot作为Spring的集大成者,自然会将JdbcTemplate集成进去。Spring Boot针对JDBC的使用提供了对应的Starter包:spring-boot-starter-jdbc,它其实就是在…...

JavaEE-多线程初阶(3)
目录 1.线程的状态 1.1 NEW、RUNNABLE、TERMINATED 1.2 TIMED_WAITING 1.3 WAITING 1.4 BLOCKED 2.多线程带来的风险-线程安全(重点) 2.1 观察线程不安全的现象 2.2 分析产生该现象的原因 2.3 产生线程安全问题的原因 2.3.1 抢占式执行&#x…...

从入门到精通:如何在Vue项目中有效运用el-image-viewer
Element UI之el-image-viewer组件详解 引言 在现代 Web 应用中,高质量的用户体验是不可或缺的一环。Element UI 作为一款基于Vue.js 2.0 的桌面端组件库,以其丰富的组件集、良好的文档和支持赢得了广大开发者的好评。本文将深入探讨el-image-viewer组件,这是一个用于在网页…...

uniapp组件实现省市区三级联动选择
1.导入插件 先将uni-data-picker组件导入我们的HBuilder项目中,在DCloud插件市场搜索uni-data-picker 点击下载插件并导入到我们的项目中 2.组件调用 curLocation :获取到的当前位置(省市区) <uni-data-picker v-slot:defa…...

【C++】异常处理机制(对运行时错误的处理)
🌈 个人主页:谁在夜里看海. 🔥 个人专栏:《C系列》《Linux系列》 ⛰️ 天高地阔,欲往观之。 目录 引言 1.编译器可以处理的错误 2.编译器不能处理的错误 3.传统的错误处理机制 assert终止程序 返回错误码 一、…...

C++ boost steady_timer使用介绍
文章目录 1. 引入必要的头文件2. 基本用法2.1 同步定时器解释:2.2 异步定时器解释:3. 异步定时器与回调函数4. 设置定时器的超时时间4.1 使用秒、毫秒、微秒4.2 修改定时器的到期时间5. 多次使用定时器6. 循环执行任务7. 错误处理总结:C++ Boost 库提供了 boost::asio::stea…...

JVM 由多个模块组成,每个模块负责特定的功能
Java虚拟机(JVM, Java Virtual Machine)是一个抽象的计算机,它提供了一个运行环境,使得Java字节码可以在不同的平台上执行。JVM 由多个模块组成,每个模块负责特定的功能。以下是 JVM 的主要模块及其功能: …...

ORACLE批量插入更新如何拆分大事务?
拆分大事务 一、批量插入更新二、拆分事务之前文章MYSQL批量插入更新如何拆分大事务?说明了Mysql如何拆分,本篇文章探讨Oracle或OceanBase批量插入更新拆分大事务的问题 一、批量插入更新 oracle批量插入更新可使用merge语法eg: merge test ausing test_tmp bon (a.id = b.id…...

kafka+zookeeper的搭建
kafka从2.8版本开始,就可以不用配置zookeeper了,但是也可以继续配置。我目前使用的kafka版本是kafka_2.12-3.0.0.tgz,其中前面的2.12表示是使用该版本的scala语言进行编写的,而后面的3.00才是kafka当前的版本。 通过百度网盘分享…...

Spark中的宽窄依赖
一、什么是依赖关系 这里通过一张图来解释: result_rdd是由tuple_rdd使用reduceByKey算子得到的, 而tuple_rdd是由word_rdd使用map算子得到的,word_rdd又是由input_rdd使用flatMap算子得到的。它们之间的关系就称为依赖关系! 二…...

安装和运行开发微信小程序
下载HBuilder uniapp官网 uni-app官网 微信开发者工具 安装 微信小程序 微信小程序 官网 微信小程序 配置 运行 注意:运行前需要开启服务端口 如果运行看不到效果,设置下基础库选别的版本 配置...

地图框架之mapbox——(五)
今天主要学习mapbox中如何使用画笔! 一、导入画笔依赖 <script src"https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.2.2/mapbox-gl-draw.js"></script> <link rel"stylesheet" href"https://api.mapbox…...