[mysql]索引优化-2

news2024/11/18 17:45:59

目录

  • 一、分页查询优化
    • 1.根据自增且连续的主键排序的分页查询
    • 2.根据非主键字段排序的分页查询
  • 二、Join关联查询优化
    • 1.嵌套循环连接 Nested-Loop Join(NLJ) 算法
    • 2.基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法
  • 三、count(*)查询优化
    • 1.查询mysql自己维护的总行数
    • 2.show table status
    • 3.将总数维护到Redis里
    • 4.增加数据库计数表

一、分页查询优化

在日常开发中分页是必不可少的,都知道简单的分页,有的是使用分页插件实现,有的是自己写分页sql等等。

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

常见分页sql

SELECT * FROM employees LIMIT 10000,10;
SELECT * FROM employees LIMIT 10 OFFSET 10000;

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

上边这里sql语句是等价的,我们应该有或多或少有了解过深分页,或者听说过深分页,其实就是这种形式分页导致的,越往后性能越低,越容易产生深分页。
mysql这种的分页原理是从表 employees 中取出从 10001 行开始的 10 行记录。看似只查询了 10 条记录,实际这条 SQL 是先读取 10010
条记录,然后抛弃前 10000 条记录,然后读到后面 10 条想要的数据。因此要查询一张大表比较靠后的数据,执行效率是非常低的。

解决方式:该上述SQL 表示查询从第 10001 开始的五行数据,没添加单独 order by,表示通过主键排序。我们再看表 employees ,因
为主键是自增并且连续的,所以可以改写成按照主键去查询从第 10001 开始的10行数据

SELECT * FROM employees WHERE id>10000 LIMIT 10;

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

可以看到结果是一样的,这种情况也是最简单的情况,没有各种条件的限制,比如Oder by这些。完全按照id排序分页。前提是主键自增,查询结果也是按照主键排序。
具体进行分页的话,从一页开始,id大于0开始取10条,第二页id大于第一页最后一个id值,以此类推。

2.根据非主键字段排序的分页查询

根据某一列或某几列字段排序查询进行分页的情况。
例如:按照name字段进行排序查询实现分页。
表的索引信息如下
在这里插入图片描述

SELECT * FROM employees ORDER BY NAME LIMIT 10000,10;

执行计划
在这里插入图片描述
虽然满足最左前缀原则但是并没有走索引,原因可能是mysql任务走索引之后还需要回表查询其余字段,不如全表扫描成本低。
解决思路:首先关于这种情况的优化思路在上一章中,首先是考虑能不能走覆盖索引,因为现在查询是select *。其次是这个using filesort,尝试让他走走索引。这个问题关键是让排序时返回的字段尽可能少,保证在buffer中能排更多行数据,所以可以让排序和分页操作先查出主键。

SELECT * FROM employees e INNER JOIN 
(SELECT id FROM employees ORDER BY NAME LIMIT 10000,10) ed 
ON e.id = ed.id 

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

优化后的结果,排序走了二级索引,并且时间上要比前者要快。大家可以自行比较一下。


二、Join关联查询优化

创建t1和t2表,并且给a字段添加索引。

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` (
`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;

插入数据

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();
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();

1.嵌套循环连接 Nested-Loop Join(NLJ) 算法

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

sql举例(INNER JOIN)

EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.a= t2.a;

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

  • 驱动表是 t2,被驱动表是 t1。先执行的就是驱动表(执行计划结果的id如果一样则按从上到下顺序执行sql);优化器一般会优先选择小表做驱动表。所以使用 inner join 时,排在前面的表并不一定就是驱动表。

  • 当使用left join时,左表是驱动表,右表是被驱动表,当使用right join时,右表时驱动表,左表是被驱动表,当使用join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表。

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

sql执行大致流程为:

  1. 从表 t2 中读取一行数据(如果t2表有查询过滤条件的,会从过滤结果里取出一行数据);
  2. 从第 1 步的数据中,取出关联字段 a,到表 t1 中查找;
  3. 取出表 t1 中满足条件的行,跟 t2 中获取到的结果合并,作为结果返回给客户端;
  4. 重复上面 3 步。
    整个过程会读取 t2 表的所有数据(共扫描100行),然后遍历这每行数据中字段 a 的值,根据 t2 表中 a 的值索引扫描 t1 表中的对应行(扫描100次 t1 表的索引,1次扫描可以认为最终只扫描 t1 表一行完整数据,也就是总共 t1 表也扫描了100行)。因此整个过程共扫描了 200 行。
    如果被驱动表的关联字段没索引,使用NLJ算法性能会比较低,mysql会选择Block Nested-Loop Join算法。

sql举例(LEFT JOIN)

EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON t1.a= t2.a;

在这里插入图片描述

EXPLAIN SELECT * FROM t2 LEFT JOIN t1 ON t1.a= t2.a;

在这里插入图片描述

2.基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法

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

EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.b= t2.b;

在这里插入图片描述

sql的大致执行流程:

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

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

被驱动表的关联字段没索引为什么要选择使用 BNL 算法而不使用 Nested-Loop Join 呢?

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

总结:

  1. 关联字段加索引,让mysql做join操作时尽量选择NLJ算法。
  2. 小表驱动大表,写多表连接sql时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间。

straight_join解释:straight_join功能同join类似,但能让左边的表来驱动右边的表,能改表优化器对于联表查询的执行顺序。
straight_join只适用于inner join,并不适用于left join,right join。(因为left join,right join已经代表指定了表的执行顺序)

尽可能让优化器去判断,因为大部分情况下mysql优化器是比人要聪明的。使用straight_join一定要慎重,因为部分情况下人为指定的执行顺序并不一定会比优化引擎要靠谱。

对于小表定义的明确
在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。

3.in和exsits优化
原则:小表驱动大表,即小的数据集驱动大的数据集

  1. 当B表的数据集小于A表的数据集时,in优于exists
select * from A where id in (select id from B)
  1. 当A表的数据集小于B表的数据集时,exists优于in

将主查询A的数据,放到子查询B中做条件验证,根据验证结果(true或false)来决定主查询的数据是否保留

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

1、EXISTS (subquery)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以用SELECT 1替换,官方说法是实际执行时会忽略SELECT清单,因此没有区别
2、EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比
3、EXISTS子查询往往也可以用JOIN来代替,何种最优需要具体问题具体分析


三、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的执行计划一样,说明这四个sql执行效率应该差不多
字段有索引:count( * )≈count(1)>count(字段)>count(主键 id) (效率从高到低)//字段有索引,count(字段)统计走二级索引,二级索引存储数据比主键索引少,所以count(字段)>count(主键 id)
字段无索引:count( * )≈count(1)>count(主键 id)>count(字段) //字段没有索引count(字段)统计走不了索引,count(主键 id)还可以走主键索引,所以count(主键 id)>count(字段)

1.查询mysql自己维护的总行数

对于myisam存储引擎的表做不带where条件的count查询性能是很高的,因为myisam存储引擎的表的总行数会被mysql存储在磁盘上,查询不需要计算。

EXPLAIN SELECT COUNT(*) FROM myisam_table

在这里插入图片描述
而对于innodb存储引擎的表mysql不会存储表的总记录行数(因为有MVCC机制,在后面的博客有讲到),查询count需要实时计算。

2.show table status

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

SHOW TABLE STATUS LIKE 'employees'

在这里插入图片描述

3.将总数维护到Redis里

插入或删除表数据行的时候同时维护redis里的表总行数key的计数值(用incr或decr命令),但是这种方式可能不准,很难保证表操作和redis操作的事务一致性。

4.增加数据库计数表

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

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

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

相关文章

PySide/PYQT如何用Qt Designer和代码来设置文字属性,如何设置文字颜色?

文章目录 📖 介绍 📖🏡 环境 🏡📒 实现方法 📒📝 Qt Designer设置📝 代码📖 介绍 📖 本人介绍如何使用Qt Designer/代码来设置字体属性(包含字体颜色) 🏡 环境 🏡 本文使用Pyside6来进行演示📒 实现方法 📒 📝 Qt Designer设置 首先打开Qt De…

Langchain-Chatchat环境安装

目录 一、简介 二、环境安装 三、使用Langchain-Chatchat 3.1、下载模型 3.2、设置配置文件 3.3、执行 一、简介 基于 ChatGLM 等大语言模型与 Langchain 等应用框架实现&#xff0c;开源、可离线部署的检索增强生成(RAG)大模型知识库项目。 &#x1f916;️ 一种利用 l…

通用结构化剪枝DepGraph

文章目录 0. 前言一. 第一部分: Torch-Pruning1.1 传统的剪枝流程 - ResNet-18结构化剪枝1.2 Torch-Pruning剪枝 - ResNet-18结构化剪枝1.3 Torch-Pruning剪枝 - 遍历所有分组1.4 Torch-Pruning剪枝 - 剪枝器 High-level Pruners1.5 Torch-Pruning剪枝 - 拓展到更复杂的神经网…

中国电子学会2023年09月份青少年软件编程Python等级考试试卷六级真题(含答案)

2023-09 Python六级真题 分数&#xff1a;100 题数&#xff1a;38 测试时长&#xff1a;60min 一、单选题(共25题&#xff0c;共50分) 1. 以下选项中&#xff0c;不是tkinter变量类型的是&#xff1f;&#xff08;D &#xff09;(2分) A.IntVar() B.StringVar() C.Do…

高效简洁的文档翻译网站

一款简单而强大的文档翻译网站 一款文字/文件翻译的网站,支持多个领域的翻译&#xff0c;支持常见的语言翻译(韩/日/法/英/俄/德…),最大百分比的保持原文排版(及个别除外基本100%还原)。 新用户注册就有100页的免费额度&#xff0c;每月系统还会随机赠送翻译额度&#xff0c;…

ssh开启,centOS7

1、先确定虚拟机是否装了openssh-server&#xff0c;执行 yum list installed |grep openssh-server 查看是否安装 [rootlocalhost ~]# yum list installed |grep openssh-server Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast openssh-serve…

C语言--每日五道选择题--Day9

第一题 1、如下程序的运行结果是&#xff08; &#xff09; char c[5]{a, b, \0, c, \0}; printf("%s", c); A: a b B: ab\0c\0 C: ab c D: ab 答案及解析 D 首先这是一个字符数组&#xff0c;我们要知道无论是字符串还是字符数组&#xff0c;它们遇到ASCII值为0就…

第6 章 布局管理及多窗口技术

6.1 控件布局技术 所谓GUI界面&#xff0c;归根结底&#xff0c;就是一堆可视化控件的叠加。创建一个窗口&#xff0c;把按钮放上面&#xff0c;把图标放上面&#xff0c;这样就成了一个界面。在放置时&#xff0c;控件的位置尤为重要。我们必须指定控件放在哪里&#xff0c;以…

vue3项目常用功能分享

Vue3常用功能分享 本文主要分享一下在使用vue3开发项目时的一些常用功能 一、自动注册全局组件 自动注册components目录下所有vue组件并以组件的文件名为组件的名称 // components/index.tsimport { type App, defineAsyncComponent } from vue const components Object.e…

Leetcode—剑指OfferII LCR 019.验证回文串II【简单】

2023每日刷题&#xff08;二十七&#xff09; Leetcode—剑指OfferII LCR 019.验证回文串II 实现代码 class Solution { public:bool judgeFunc(string s, int left, int right) {while(left < right) {if(s[left] ! s[right]) {return false;}left;right--;}return true;…

【数据结构】拓扑序列求法

概念不多说了&#xff0c;有疑问的搜一下&#xff0c;这里直接放求法&#xff1a; 找到入度为0的节点输出并删除该节点&#xff0c;并删除与该点链接的边重复第一步 例子 输出a&#xff0c;删除a输出b&#xff0c;删除b输出c&#xff0c;删除c 最终结果为abcdef 注意 拓扑排…

WorkPlus IM即时通讯软件:私有化部署、安全加密、信创适配

WorkPlus IM即时通讯软件是一款具备私有化部署、安全加密和信创适配功能的高效沟通工具。下面我们将详细介绍WorkPlus IM的功能和特点。 WorkPlus支持私有化部署&#xff0c;这意味着企业可以完全掌控数据的存储与传输。企业可以选择将该软件部署在自己的本地服务器上&#xff…

文件上传 [ACTF2020 新生赛]Upload1

打开题目&#xff0c;发现是一道文件上传题目 随便上传个一句话木马上去 发现网站前端有白名单限制&#xff0c;只能上传含有jpg&#xff0c;png&#xff0c;gif的后缀文件 最开始我想到的做法是先上传htaccess文件&#xff0c;bp修改文件头&#xff0c;上传成功后然后再上传以…

Linux文件缓冲区

文章目录 1. 缓冲区现象2. 用户级和系统级缓冲区3. 缓冲区刷新4. 为什么要有缓冲区5. 文件打印的全缓冲6. 模拟实现C语言文件标准库 本章gitee代码仓库&#xff1a;重定向、模拟C语言文件标准库 1. 缓冲区现象 我们这里分别调用了4个差不多的函数&#xff0c;但是结果是有一定差…

Sprint Boot 学习路线 5

Spring MVC Spring MVC是Spring框架的一部分&#xff0c;是一个Web应用程序框架。它旨在使用Model-View-Controller&#xff08;MVC&#xff09;设计模式轻松构建Web应用程序。 在Spring MVC中&#xff0c;应用程序被分为三个主要组件&#xff1a;Model、View和Controller。M…

深入理解强化学习——多臂赌博机:知识总结

分类目录&#xff1a;《深入理解强化学习》总目录 我们在《深入理解强化学习——多臂赌博机》系列文章中介绍了几种平衡试探和开发的简单方法。 ϵ − \epsilon- ϵ−贪心方法在一小段时间内进行随机的动作选择&#xff0c;而UCB方法虽然采用确定的动作选择&#xff0c;却可以通…

同一个Unity项目打开两个Unity Editor实例

特殊情况下&#xff0c;同一个项目需要同时打开两个编辑器做测试&#xff0c;如多人在线游戏&#xff0c;或者有通信功能的时候就有这样的需求。同时也为了方便调试和观察日志。并且修改的是同一份代码。 命令介绍&#xff1a; 实现思路&#xff1a; 使用 mklink 命令 分别创建…

Flowable 外部表单

内置表单需要在每个节点中去配置&#xff0c;当如果多个节点使用同一套表单属性就要配置多次比较麻烦&#xff0c;修改的时候也要修改多次&#xff0c;外部表单可以定义一次&#xff0c;然后其它节点都去引用同一个表单属性。 外部表单需要定义一个.form后缀的文件。 外部表单…

快速查看Linux系统占用多的文件夹

背景 租用了一台云服务器&#xff0c;存储很快就满了&#xff0c;想看下哪部分占用多&#xff0c;然后进行清理 工具 使用ncdu工具 sudo apt install ncdu效果