MySQL 索引优化(二)

书接上文,继续使用上次创建的 student 表,表里已经被我们装填了大量数据,接下来做分页查询的索引优化。

分页查询优化

普通的分页查询语句:

SELECT * FROM student LIMIT 10000,10;

这条语句是从 student 表中取出 10001 行开始的10条数据,不过看起来只查询了十条,但实际上是将 10010 条记录读取,只显示后面十条数据,抛弃前面的 10000 条记录,所以执行效率很低。

  1. 根据自增且连续的主键排序的分页查询

上一条语句就是这样的例子,表通过主键 id 进行排序,看一下执行计划:

    id  select_type  table    partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra   
------  -----------  -------  ----------  ------  -------------  ------  -------  ------  ------  --------  --------1  SIMPLE       student  (NULL)      ALL     (NULL)         (NULL)  (NULL)   (NULL)   97664    100.00  (NULL)                                                                                                                

type 类型 ALL,全表扫描,查询效率很低。对于这种情况,可以改写一下,id 连续且自增,直接按照 id 进行筛选,查询 10000 后五条的数据:

EXPLAIN SELECT * FROM student WHERE id > 10000 LIMIT 10;
    id  select_type  table    partitions  type    possible_keys  key      key_len  ref       rows  filtered  Extra        
------  -----------  -------  ----------  ------  -------------  -------  -------  ------  ------  --------  -------------1  SIMPLE       student  (NULL)      range   PRIMARY        PRIMARY  4        (NULL)   48832    100.00  Using where  

显然,使用了索引,扫描的行数少了很多,执行效率也提高了很多。但这样的改写对于数据有很高的要求,比如数据不能物理删除,物理删除导致主键非连续,从而使得结果不一致,使用逻辑删除的情况可以采用上面的优化方法。
这种方式必须满足这两个条件:

  • 主键连续且自增
  • 结果集按照主键排序
  1. 根据非主键字段排序的分页查询
SELECT * FROM student ORDER BY NAME LIMIT 10000, 10;

结果集

    id  name         age  school                 start_time  
------  --------  ------  ------------  ---------------------19002  老18999     18999  老大小学            2024-03-29 16:03:2122  老19           19  老大小学            2024-03-29 16:01:23193  老190         190  老大小学            2024-03-29 16:01:241903  老1900       1900  老大小学            2024-03-29 16:01:3419003  老19000     19000  老大小学            2024-03-29 16:03:2119004  老19001     19001  老大小学            2024-03-29 16:03:2119005  老19002     19002  老大小学            2024-03-29 16:03:2119006  老19003     19003  老大小学            2024-03-29 16:03:2119007  老19004     19004  老大小学            2024-03-29 16:03:2119008  老19005     19005  老大小学            2024-03-29 16:03:21

查询计划

    id  select_type  table    partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra           
------  -----------  -------  ----------  ------  -------------  ------  -------  ------  ------  --------  ----------------1  SIMPLE       student  (NULL)      ALL     (NULL)         (NULL)  (NULL)   (NULL)   97664    100.00  Using filesort  

Usiing filesort,ALL 全表扫描,使用文件排序,并没有使用 name 字段的索引,扫描整个索引并查找到没索引的行的成本可能比全表扫描更高,所以优化器放弃使用索引。优化的关键是让排序时返回的字段尽可能少,可以用下面的优化:

SELECT id FROM student ORDER BY NAME LIMIT 10000, 10) t WHERE student.id = t.id;

查询结果:

    id  name         age  school                 start_time      id  
------  --------  ------  ------------  -------------------  --------19002  老18999     18999  老大小学          2024-03-29 16:03:21     1900222  老19           19  老大小学          2024-03-29 16:01:23        22193  老190         190  老大小学          2024-03-29 16:01:24       1931903  老1900       1900  老大小学          2024-03-29 16:01:34      190319003  老19000     19000  老大小学          2024-03-29 16:03:21     1900319004  老19001     19001  老大小学          2024-03-29 16:03:21     1900419005  老19002     19002  老大小学          2024-03-29 16:03:21     1900519006  老19003     19003  老大小学          2024-03-29 16:03:21     1900619007  老19004     19004  老大小学          2024-03-29 16:03:21     1900719008  老19005     19005  老大小学          2024-03-29 16:03:21     19008

执行计划

    id  select_type  table       partitions  type    possible_keys  key                  key_len  ref       rows  filtered  Extra        
------  -----------  ----------  ----------  ------  -------------  -------------------  -------  ------  ------  --------  -------------1  PRIMARY      <derived2>  (NULL)      ALL     (NULL)         (NULL)               (NULL)   (NULL)   10010    100.00  (NULL)       1  PRIMARY      student     (NULL)      eq_ref  PRIMARY        PRIMARY              4        t.id         1    100.00  (NULL)       2  DERIVED      student     (NULL)      index   (NULL)         idx_name_age_school  140      (NULL)   10010    100.00  Using index  

可以看到查询结果是一致的,执行计划中 extra 列也从 Using filesort 变成 Using index,可以对比查询时间,显然看到查询效率变高了。

Join 关联查询优化

创建表

CREATE TABLE `temp1` (`id` INT(11) NOT NULL AUTO_INCREMENT,`first` INT(11) DEFAULT NULL,`second` INT(11) DEFAULT NULL,PRIMARY KEY(`id`),KEY `idx_first` (`first`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;DROP PROCEDURE IF EXISTS insert_temp1;
DELIMITER ;;
CREATE PROCEDURE insert_temp1()
BEGINDECLARE i INT;SET i=1;WHILE(i<=100)DOINSERT INTO temp1(`first`,`second`) VALUES(i, i);SET i=i+1;END WHILE;
END;;
DELIMITER ;
CALL insert_temp1();

先来看一个查询语句

SELECT * FROM student INNER JOIN temp1 ON student.id=temp1.id;

执行计划

    id  select_type  table    partitions  type    possible_keys  key      key_len  ref                        rows  filtered  Extra   
------  -----------  -------  ----------  ------  -------------  -------  -------  -----------------------  ------  --------  --------1  SIMPLE       temp1    (NULL)      ALL     PRIMARY        (NULL)   (NULL)   (NULL)                      100    100.00  (NULL)  1  SIMPLE       student  (NULL)      eq_ref  PRIMARY        PRIMARY  4        multiplefather.temp1.id       1    100.00  (NULL)                                                                                                                                  

MySQL 表关联常见有两种算法

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

Nested-Loop Join 算法

嵌套循环连接算法(NLJ)一次一次循环地从第一张表(驱动表)中读取行,这行数据取出关联字段,再根据关联字段在另一张表(被驱动表)里取到满足条件的数据,然后取出两张表的结果合集。

从执行计划中可以看出,驱动表是 temp1。id 相同,按顺序执行,先执行的是驱动表,优化器一般优先选择小标做驱动表,而不是按照 Inner Join 使用时两张表的顺序

当使用 Left Join 时,左表是驱动表,右表是被驱动表,当使用 Right Join 时,右表是驱动表,左表是被驱动表,当使用 Join 时,MySQL 会选择小表作为驱动表,大表作为被驱动表。

使用了 NLJ 算法,一般 Join 语句中,如果执行计划 Extra 中未出现 Using Join Buffer 表示使用的 Join 算法是 NLJ。

综合这些信息,再看上面语句的执行流程

  1. 从小表 temp1 中取出一条记录
  2. 从该语句中找到关联字段,再去 student 表查询
  3. 取出 student 表中满足条件的行,跟 temp1 表中的结果进行合并,返回并重复执行这三步。

整个过程中,会读取驱动表 temp1 的所有数据,遍历出关联字段 id 的值,再根据 id 的值索引扫描 student 表中的对应行,因为 temp1 表中有 100 条数据,扫描 100 次 student 表的索引,1次扫描可以理解为最终只扫描 student 表一行完整数据,也就是总共 student 表也扫描了 100 行。因此整个过程扫描了 200 行。但如果被驱动表关联字段没有索引,使用 NLJ 算法性能较低下面对比两种算法

Block Nested-Loop Join(BNL) 算法

基于块的嵌套循环连接算法会先把驱动表的数据都读取到 join_buffer 中,然后扫描被驱动表,把被驱动表的每一行数据取出跟 join_buffer 中的数据做对比。

执行下面的查询计划

EXPLAIN SELECT * FROM temp1 INNER JOIN student ON temp1.second=student.age;

执行计划

    id  select_type  table    partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra                                       
------  -----------  -------  ----------  ------  -------------  ------  -------  ------  ------  --------  --------------------------------------------1  SIMPLE       temp1    (NULL)      ALL     (NULL)         (NULL)  (NULL)   (NULL)     100    100.00  (NULL)                                      1  SIMPLE       student  (NULL)      ALL     (NULL)         (NULL)  (NULL)   (NULL)   97664     10.00  Using where; Using join buffer (hash join)  

没想到吧,不是 Using join buffer(Block Nested Loop),是因为 MySQL 在 8.0.20 版本以后,就已经把 BNL 移除了,使用 hash join 代替。我用的 MySQL 版本是 8.0.28,复现不了 BNL,但思路还是要总结一下的。

BNL 算法语句执行流程是:

  1. 把 temp1 表所有数据都放入到 join_buffer 中
  2. 把表 student 中的每一行取出来,跟 join_buffer 中的数据做对比
  3. 返回满足 join 条件的数据

整个过程对表 temp1 和 student 表都做了一次全表扫描,因此扫描的总行数为 100000(表student 的数据总量)+ 100(表 temp1 的数据总量)=100100,join_buffer 中的数据都是无序的,因此对表 student 中的每行数据都要做 100 次判断,所以内存中判断总数是 100000*100=1000 万次。如果 temp1 表的数据很大,在 join_buffer 中放不下,就会将数据分段,分段放进 join_buffer,再进行判断。

对比 BNL 和 NLJ 两种算法:
如果使用 NLJ 算法,扫描的行数是 100 * 100000=1000 万次,但这个是磁盘扫描。而 BNL 算法,是在内存中进行判断,相比磁盘扫描,肯定快得多。
如果被驱动表的关联字段,没有索引,那就选择 BNL 算法,有索引就选择 NLJ 算法。

而 hash join,基本思想是根据驱动表在内存中建立一个 hash table,然后用大表来探测这个 hash table。这样这需要遍历一遍内表,就可以完成 join 操作,输出匹配的记录。NLJ 的复杂度是驱动表记录数*被驱动表的记录数,hash join 只需要遍历一次内表就可以完成查询,效率有所提升。

in 和 exists 优化

主要原则就是小表驱动大表,把前面的表称为 A 表,后面的表称为 B 表。当 B 表的数据小于 A 表的数据时,in 优于 exists。

select * from A where id in (select id from B);

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

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

总结

这篇文章主要对分页查询、join 连接查询以及 in 和 exists 的索引查询优化,也比较浅显,还要更努力的学习,希望各位都能从中有所收获。

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

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

相关文章

面向对象进阶5:类和泛型

内部类 在一个类的内部定义的类,内部类可以访问外部类的成员变量和成员方法,包括私有成员,内部类可以分为成员内部类,局部内部类,静态内部类和匿名内部类 public class InnerClass {class InnerC{} }1,成员内部类 定义在类中方法外的类,可以访问外部类的所有成员变量和方法,…

人脸识别seetaface6 windows + cmake + vs编译,踩坑指南

遇到问题冷静分析&#xff0c;没有解决不了的问题&#xff0c;只是需要时间。与君共勉 环境准备 要在windows 上编译c 源码&#xff0c;需要准备如下软件。省去了详细的安装过程。 visual studio 2022 (社区免费版链接)mingw64 下载路径 (安装后&#xff0c;记得添加系统路径…

elementui 实现一个固定位置的Pagination(分页)组件

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 二、elementui 左侧导航菜单栏与main区域联动 三、elementui 中设置图片的高度并支持PC和手机自适应 四、 elementui 实现一个固定位置的Pagination&#xff08;分页&#xff09;组件 文章目录 系列文章目录…

今客CRM客户管理系统 v17.3

简介&#xff1a; 今客CRM客户管理系统主要是为了帮助企业解决在日常工作中遇到的客户管理等难题而开发&#xff0c;通过今客CRM客户管理系统可以对企业事务中的不同功能进行操作&#xff0c;用户通过自定义字段类型可以达到适合不同企业的需求。在今客客户关系管理系统中管理…

【子集回溯】【树枝+树层去重】Leetcode 491. 非递减子序列

【组合回溯】【树枝树层去重】Leetcode 491. 非递减子序列 解法1 ---------------&#x1f388;&#x1f388;题目链接&#x1f388;&#x1f388;------------------- 解法1 【树层去重】&#xff1a;如果在同层元素重复(这里无法排序&#xff0c;所以无法使用之前的flag&a…

Making Anti-Palindromes

题目链接 Codeforces Round 867 (Div. 3) E. Making Anti-Palindromes 挺好的一道鸽巢原理题。 思路&#xff1a; 贪心地来想&#xff0c;我们没必要动本来就不同的一对&#xff0c;而对相同的对&#xff0c;我们可以让它们互相之间进行交换&#xff0c;这样一次交换就可以…

HarmonyOS 应用开发之启动远程PageAbility(仅对系统应用开放)

启动远程PageAbility同样通过featureAbility中的startAbility接口实现。 除引入’ohos.ability.featureAbility’外&#xff0c;还需引入’ohos.distributedHardware.deviceManager’&#xff0c;通过DeviceManager&#xff08;该组件提供帐号无关的分布式设备的认证组网能力&…

鸿蒙南向开发实战:【智能窗帘】

样例简介 智能窗帘设备不仅接收数字管家应用下发的指令来控制窗帘开启的时间&#xff0c;而且还可以加入到数字管家的日程管理中。通过日程可以设定窗帘开关的时间段&#xff0c;使其在特定的时间段内&#xff0c;窗帘自动打开或者关闭&#xff1b;通过日程管家还可以实现窗帘…

蓝桥杯备考

目录 P8823 [传智杯 #3 初赛] 期末考试成绩 题目描述 输入格式 输出格式 输入输出样例 说明/提示 代码 P8828 [传智杯 #3 练习赛] 直角三角形 题目描述 输入格式 输出格式 输入输出样例 代码 P8833 [传智杯 #3 决赛] 课程 题目背景 题目描述 输入格式 输出格式…

redis---位图Bitmap和位域 Bitfield

位图是字符串类型的拓展&#xff0c;可以使用一个string类型来模拟一个Bit数组。数组的下标就是偏移量&#xff0c;值只有0和1&#xff0c;也支持一些位运算&#xff0c;比如与或非&#xff0c;异或等等&#xff0c;它们的应用场景非常广泛比如可以用来记录用户的签到情况&…

dict类型如何保存为json数据,给一个python示例代码

dict类型如何保存为json数据&#xff0c;给一个python示例代码 import json# 假设我们有一个字典对象 data_dict {"name": "Alice","age": 30,"city": "Wonderland" }# 将字典转换为JSON格式的字符串 json_string json.d…

代码随想录一刷总结(待更新)

文章目录 代码随想录一刷总结数组篇二分查找双指针用法 链表篇哈希表篇字符串篇栈与队列篇二叉树篇回溯篇贪心篇动态规划篇 代码随想录一刷总结 数组篇 二分查找 最重要的是循环不变量原理 区间定义 left right 时有没有意义&#xff0c;取决于开区间还是闭区间 使用场景…

Qt中继承QCheckBox的类结合QTableWidget实现多选并且每个多选的id都不一样

1.相关描述 继承QCheckBox的类MyCheckBox&#xff0c;利用QTableWidget的setCellWidget方式添加MyCheckBox类的对象 2.相关页面 3.相关代码 mycheckbox.h #ifndef MYCHECKBOX_H #define MYCHECKBOX_H#include <QCheckBox> #include <QObject>class MyCheckBox : pu…

力扣刷题Days31-2.两数相关(js)

1&#xff0c;题目 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;…

Vue3:Pinia简介及环境搭建

一、简介 Pinia是Vue3中的状态管理工具&#xff0c;类似与Vue2中的Vuex框架的作用 二、环境搭建 1、安装 npm install pinia2、配置 main.ts import {createApp} from vue import App from ./App.vue // 第一步&#xff1a;引入pinia import {createPinia} from piniacons…

Nginx - directory index of “/usr/share/nginx/html/“ is forbidden

问题描述 安装完 Nginx 之后访问本机 IP&#xff0c;结果直接报错&#xff0c;然后去查看 Nginx 错误日志&#xff0c;看到如下错误信息&#xff0c;意思是 html 下面没有 directory index of "/usr/share/nginx/html/" is forbidden 解决方案 Q1&#xff1a;如果在…

zabbix源码安装

目录 一.安装php和nginx客户端环境 二.修改php配置 三.修改nginx配置文件 四.下载并编译zabbix 五.创建zabbix需要的用户及组 六.安装编译需要的依赖 七.配置zabbix文件 八.数据库配置 九.配置zabbix 十.web界面部署 十一.遇到无法创建配置文件 十二.登录zabbix 前…

C# OAuth单点登录的实现

原理 单点登录&#xff08;Single Sign-On&#xff0c;简称SSO&#xff09;是一种身份验证技术&#xff0c;它允许用户使用一组凭据&#xff08;如用户名和密码&#xff09;登录多个相关但独立的系统&#xff0c;而无需在每个系统中都进行登录操作。下面是一个简单的SSO实现示…

[Python学习篇] Python简介

介绍 Python&#xff08;意为大蟒蛇&#xff09;由荷兰国家数学与计算机科学研究中心的吉多范罗苏姆于1990年代初设计&#xff0c;作为一门叫做ABC语言的替代品。 Python提供了高效的高级数据结构&#xff0c;还能简单有效地面向对象编程。Python语法和动态类型&#xff0c;以及…

2014最新AI学法减分交管12123小程序源码最新玩法

2014最新AI学法减分交管12123小程序源码最新玩法利用ChatGPT实现拍照搜题 利用ChatGPT实现拍照搜题 学法减分这个项目是几年之前的项目&#xff0c;老朋友都知道&#xff0c;以前我用Python实现了向量检索&#xff0c;也就是当时和大家说到的AI题库&#xff0c;那时候国内还没…