MySQL索引优化实战二

news2025/1/18 16:54:25

分页查询优化

很多时候我们业务中实现分页功能时可能会用如下SQL来实现:

select * from employees LIMIT 10000,10

表示从表中中区从10001行开始的10行记录,看似只查了10条记录,但是这条SQL是先读取10010条记录,然后抛弃前10000条记录,然后读到后面10条想要的数据,因此要查询一张大表比较靠后的数据,执行效率是很低的。

1、根据自增且连续的主键排序的分页查询
select * from employees LIMIT 90000,5

在这里插入图片描述
在这里插入图片描述

select * from employees where id>90000 limit 5

在这里插入图片描述
在这里插入图片描述
改写后的SQL走了索引,且看执行时间,改写后的时间更快。
但是这种方式在很多场景中不适用,因为表中某些数据被删后造成主键空缺,导致结果不一致。
上面改写必须满足两个条件:

  • 主键自增且连续
  • 结果是按照主键排序的
2、根据非主键字段排序的分页查询
select * from employees order by name  LIMIT 90000,5

在这里插入图片描述

explain select * from employees order by name  LIMIT 90000,5

在这里插入图片描述

没有使用索引,是因为:扫描整个扫一年病查找到没索引的行比扫描全表的成本更高。

如何优化呢?
关键是让排序返回的字段尽可能的少,所以可以让排序和分页操作先查出主键,然后根据主键查找到对应的记录,如下SQL:

select * from employees e INNER JOIN (SELECT id FROM employees ORDER BY name limit 9000,5) ed on e.id = ed.id

在这里插入图片描述
执行计划:

explain select * from employees e INNER JOIN (SELECT id FROM employees ORDER BY name limit 9000,5) ed on e.id = ed.id

原SQL使用的是filesort排序,优化后的SQL使用的是索引排序
在这里插入图片描述

Join关联查询优化

‐‐ 示例表:
 CREATE TABLE `t1` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `a` int(11) DEFAULT NULL,
 `b` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
 KEY `idx_a` (`a`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 create table t2 like t1;

 ‐‐ 插入一些示例数据
 ‐‐ 往t1表插入1万行记录
 drop procedure if exists insert_t1;
 delimiter ;;
 create procedure insert_t1()
 begin
 declare i int;
 set i=1;
 while(i<=10000)do
 insert into t1(a,b) values(i,i);
 set i=i+1;
 end while;
 end;;
 delimiter ;

call insert_t1();

 ‐‐ 往t2表插入100行记录
 drop procedure if exists insert_t2;
 delimiter ;;
 create procedure insert_t2()
 begin
 declare i int;
 set i=1;
 while(i<=100)do
 insert into t2(a,b) values(i,i);
set i=i+1;
 end while;
 end;;
 delimiter ;
 call insert_t2();

MySQL表关联常见有两种算法

  • Nested-Loop Join 算法
  • Block Nested-Loop Join算法

嵌套循环连接算法

一次一行地循环地从第一张表(成为驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动表)中取出满足的行,然后取出两张表的结果合集。

 explain select * from t1 inner join t2 on t1.a=t2.a

在这里插入图片描述

  • 驱动表是t2,被驱动表是t1。先执行的是驱动表。优化器一般会优先选择小表做驱动表,所以使用inner Join 时,排在前面的不一定是驱动表。
  • 当使用left Join时,左表是驱动表。当使用right Join时,右表是驱动表;当使用join时,MySQL会选择数量较小的表作为驱动表。
  • 使用了NLJ算法。一般join语句中,如果执行计划extra中未出现 Using join buffer则表示使用join算法是NLJ。

上面SQL执行流程如下:
1、从表t2中读取一行数据(如果t2表有过滤条件的,会从过滤条件中取出一行数据)
2、从第一步骤的数据中,取出关联字段a到表t1中查找;
3、取出表t1满足条件的行,跟t2中获取的结果合并,作为结果返回给客户端;
4、重复上面3步。

整个过程会读取t2表的所有数据(扫描100行),遍历这每行数据中字段a的值,根据t2表中a的值索引扫描t1表中对应行。(扫描100次t1表的索引,1次扫描可以认为最终只扫描t1表一行完整数据,也就是总共t1表也扫描了100行)。

如果被驱动表的关联字段没索引使用NLJ算法性能会比较低,MySQL会选择Block Nested-Loop Join算法

基于块的嵌套循环连接算法

把驱动表的数据读到join buffer中,然后扫描被驱动表,把被驱动表每一行取出来跟join buffer 中的数据做对比。

explain select * from t1 inner join t2 on t1.b=t2.b

在这里插入图片描述
Extra中的Using join buffer (Block Nested Loop)说明该关联查询用的是BNL算法。

上面SQL的大致流程如下:
1、把t2的所有数据放入到join buffer中
2、把t1中每一行取出来跟join buffer中的数据做对比
3、返回满足join条件的数据

整个过程对两个表都做了一次全表扫描,因此扫描的总行数为10000(t1的数据总量)+100(表t2的数据总量)=10100.并且join buffer中的数据是无序的,因此对表t1中的每一行都要做100次判断,所以内存中的判断次数是100*10000=100万次
如果t2表很大,join buffer放不下怎么办呢?
join buffer的大小是由参数join_buffer_size设定的,默认值为256k,如果放不下表t2的所有数据的话,就分段放
比如t2表中有10000行记录,join_buffer一次只能放800行数据,那么执行过程就是先往join_buffer中放800条记录,然后从t1表中取数据跟join_buffer中数据对比得到部分结果,然后清空join_buffer,再放入t2表剩余200条记录,再次从t1表中取数据跟join buffer中数据对比,所以就多扫了一次表。

被驱动表的关联字段没有索引为什么要算则使用BNL算法而不使用NLJ算法呢?

如果上面第二条SQL使用NLJ算法,那么扫描行数为100*10000=100万次,这个是磁盘扫描。显然,BNL磁盘扫描次数少很多,相比与磁盘扫描,BNL内存扫描会快很多。
因此MySQL对于被驱动表的关联字段没有索引的关联查询,一般都会使用BNL算法,如果有索引一般选择NLJ算法,有索引的情况下,NLJ算法比BNL算法性能更高。

对关联SQL的优化
  • 关联字段加索引: 让MySQL做join操作时尽量选择NLJ算法
  • ==小表驱动大表:==如果明确知道那张是小表可以用straight_join写法固定连接驱动方式,省去MySQL优化器自己判断的时间。
小表的定义

按照各自的条件过滤,过滤后哪个数据量小 哪个是小表

in和exsits 优化

原则:=小表驱动大表,即小的数据集驱动大的数据集

in:

当B表的数据集小于A的数据集时,in优于exists

select * from A where id in (select id from B )
等价于:
forselect id from B){
	select * from A where A.id = B.id
}
exists:

当A表的数据集小于B的数据集时 exists 优于in
当主查询A的数据,放到子查询B中做条件验证,根据验证结果(true或false)来决定主查询的数据是否保留

select * from A where exists(select 1 from B where B.id = A.id)

先查询A中的数据,然后判断是否符合与B的条件

count(*)查询优化
explain select count(1) from employees;
explain select count(id) from employees;
explain select count(name) from employees;
explain select count(*) from employees;

以上四条SQL只有根据某个字段count不会统计字段为null值的数据行
字段有索引时查询效率:
count(星)≈ count(1)>count(字段)>count(主键id)
字段有索引,count(字段)统计走二级索引,二级索引存储数据比主键索引少,所以count(字段)>count(id)
字段无索引时查询效率:
count(*)≈ count(1)>count(主键id)>count(字段)
字段无索引,count(字段)统计走不了索引,count(id)还可以走主键索引,所以count(主键)>count(id)

  • count(1)跟count(字段)执行过程类似,不过count(1)不需要取出字段统计,就用常量1做统计,

  • count(字段)还需要取出字段,索引理论上count(1)比count(字段会快一点)

  • count(*)是例外,MySQL并不会把全部字段取出来,而是专门做了优化,不取值,按行累加,效率很高,所以不需要用count(列名)或count(常量)来代替count(星)

  • 为什么对于count(id),MySQL最终选择辅助索引而不是主键聚集索引?
    因为二级索引相对于主键存储数据更少,检索性能应该更高,mysql内部做了点优化(应该是5.7)

常见优化方法

1、查询MySQL自己维护的总行数

myisam存储引擎做不带where条件的count查询性能是很高的,因为myisam存储引擎的表的总行数总会被MySQL存储在磁盘上,查询不需要计算。
对于innodb存储引擎的表MySQL不会存储表的总记录,因为有mvcc机制,查询count需要实时计算。

2、show table status

如果只需要知道表总行数的估计值可以用下面SQL,性能很高。

show table status like 'employes'

在这里插入图片描述

3、将总数维护到redis

但是很难保证表操作和redis的事务一致性

4、增加数据库计数表

插入或删除表数据行的时候同时维护计数表,让他们在同一个事务里操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1280600.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【个人笔记】-python-强化学习-类-在内存中的值

{int} 数值 {int} 200 {float} 数值 {float} 0.9 {narray:维度} 数值 {narray:(1,)} [2.] {bool} True {bool} False {类名} 对象1 {类名} 对象2

LLM 开发模式 RAG,MRKL,Re-Act,Plan-Execute 模式对比

本心、输入输出、结果 文章目录 LLM 开发模式 RAG&#xff0c;MRKL&#xff0c;Re-Act&#xff0c;Plan-Execute 模式对比前言RAG、MRKL、Re-Act和Plan-Execute模式的一些对比花有重开日&#xff0c;人无再少年实践是检验真理的唯一标准 LLM 开发模式 RAG&#xff0c;MRKL&…

MySQL进阶_EXPLAIN重点字段解析

文章目录 第一节.准备1.1 版本信息1.2 准备 第二节.type2.1 system2.2 const2.3 eq_ref2.4 ref2.5 ref_or_null2.6 index_merge2.7 unique_subquery2.8 range2.9 index2.10 all 第三节. Extra3.1 No tables used3.2 No tables used3.3 Using where3.4 No matching min/max row3…

基于springboot实现的垃圾分类管理系统

一、系统架构 前端&#xff1a;html | layer | jquery | css 后端&#xff1a;springboot | mybatis 环境&#xff1a;jdk1.8 | mysql | maven 二、 代码及数据库 三、功能介绍 01. 登录页 02. 系统设置-用户管理 03. 系统设置-页面管理 04. 系统设置-角色管…

Python练习题(二)

&#x1f4d1;前言 本文主要是【Python】——Python练习题的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&am…

三极管在数字电路中的应用

一、认识三极管 三极管拥有3个引脚&#xff0c;分别对应3个级&#xff1a;基极(Base)、发射极&#xff08;Emitter&#xff09;、集电极(Collector)&#xff0c;如下图所示&#xff1b;下图横向左侧的是基极&#xff0c;带箭头的那个引脚就是发射极&#xff0c;另一个就是集电…

ElementPlus中 使用ElLoading.service, spinner: ‘el-icon-loading‘不生效

let downloadLoadingInstance ElLoading.service({ text: "正在下载数据&#xff0c;请稍候",spinner: el-icon-loading, background: "rgba(0, 0, 0, 0.7)", })使用以上代码时&#xff0c;加载的圆圈出不来&#xff0c;使用f12查看&#xff0c;即使能出…

代理模式介绍(静态代理、jdk动态代理、cglib代理)

一、静态代理 &#xff08;一&#xff09;定义 1、定义 为其他对象提供一种代理以控制对这个对象的访问&#xff1b; 2、涉及到的角色 &#xff08;1&#xff09;抽象主题角色&#xff1a;真实主题和代理主题的共同接口&#xff0c;便于在使用真实主题的地方都可以使用代理…

blue beacon rssi 指纹室内定位数据集

数据集是开展实验的基础&#xff0c;搜集并分享。如果你有关于室内定位的问题&#xff0c;请联系博主。 namedatesetpapercommentBLEBeacon: A Real-Subject Trial Dataset from Mobile Bluetooth Low Energy Beaconshttps://github.com/dimisik/BLEBeacon-Datasethttps://arxi…

React18 入门与进阶

React18 入门与进阶 前言一、核心概念与类组件使用1、虚拟DOM与新的渲染写法2、JSX 与 JSX 的使用3、类组件和函数组件4、类组件与类组件通信5、props详解与注意事项6、类组件中事件的使用7、类组件响应式数据实现与原理8、PureComponent 与 shouldComponentUpdate9、immutable…

Kubernetes(K8s)_15_CNI

Kubernetes&#xff08;K8s&#xff09;_15_CNI CNI网络模型UnderlayMAC VLANIP VLANDirect Route OverlayVXLAN CNI插件FlannelCalico CNI配置内置实现 CNI CNI(Container Network Interface): 实现容器网络连接的规范 Kubernetes将网络通信可分为: Pod内容器、Pod、Pod与Se…

YOLOv7+姿态估计Pose+tensort部署加速

YOLOv7-Pose YOLOv7是一种高效的目标检测算法&#xff0c;用于实时物体检测。姿态估计Pose是一种用于识别和跟踪人体关键点的技术。TensorRT是一个针对深度学习推理任务进行加速的高性能推理引擎。 将YOLOv7和姿态估计Pose与TensorRT结合可以实现快速而准确的目标检测和姿态估…

Element UI 实战:跨页保存表格选中状态与判断状态可选性的高效方案

引言 在前文中&#xff0c;我们曾深入探讨了在修改数据后跨页时提醒用户可能丢失数据的问题。虽然这种方式对于一些场景是足够的&#xff0c;但当涉及选择框时&#xff0c;我们需要更为智能和高效的解决方案。在本文中&#xff0c;我们将分享一种基于 Element UI 的实际案例&am…

opencv学习二:加载显示图片

文章目录 加载显示图片&#xff08;一&#xff09;函数1.imread()读取图片&#xff08;1&#xff09;matplotlib和opencv中imread函数的区别 加载显示图片 &#xff08;一&#xff09;函数 1.imread()读取图片 Mat imread(const string& filename, int flags1 );第一个参…

ASP.NET-BS结构的城市酒店入住信息管理系统的设计

2 理论基础 2.1 数据库技术 数据库技术应用中&#xff0c;经常用到的基本概念有&#xff1a;数据库&#xff08;DB&#xff09;、数据库管理系统&#xff08;DBMS&#xff09;、数据库系统&#xff08;DBS&#xff09;、数据库技术及数据模型。 数据库技术是研究数据库的结构、…

【Linux服务器Java环境搭建】04 JDK安装(JAVA环境安装)

【Linux服务器Java环境搭建】01购买云服务器以及在服务器中安装Linux系统 【Linux服务器Java环境搭建】02 通过xftp和xshell远程连接云服务器 【Linux服务器Java环境搭建】03 Git工具安装 【Linux服务器Java环境搭建】04 JDK安装&#xff08;JAVA环境安装&#xff09; 【Linux服…

【Linux服务器Java环境搭建】02 通过xftp和xshell远程连接云服务器

【Linux服务器Java环境搭建】01购买云服务器以及在服务器中安装Linux系统 【Linux服务器Java环境搭建】02 通过xftp和xshell远程连接云服务器 【Linux服务器Java环境搭建】03 Git工具安装 【Linux服务器Java环境搭建】04 JDK安装&#xff08;JAVA环境安装&#xff09; 【Linux服…

Linux4.7、环境变量

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 基本概念 见见环境变量 指令原理 常见环境变量及其测试 环境变量相关指令 环境变量组织方式 通过代码获取环境变量 通过系统变量获取环境变量以及设置环境变量 环境变量的全局属性 基本概念 首先&#xff0c;…

深入理解同源限制:网络安全的守护者(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Hdoop学习笔记(HDP)-Part.10 创建集群

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …