深入理解MySQL慢查询优化(2) -- SQL的执行流程

news2024/9/21 2:40:09

要优化一条SQL语句,要先理解SQL操作的执行流程

1. 不同SQL操作的执行流程

1.1 order by

order by用于排序,如果用于排序的列上没有索引,就需要把整张表加载进内存进行排序,非常耗时。如果有索引,因为B+树存储的数据本就是有序的,所以MySQL可以通过索引直接顺序读取即可,非常高效

1.2 join

前面以及了解过join是通过两层for循环实现。

如果内存表没有索引,那么对于外层表的每一行数据都要进行一次全表扫描,如果有索引,内层表可以使用索引快速定位到匹配的记录。

1.3 where

没有索引只能对表进行扫描,逐一判断是否满足条件,有索引则可以快速定位到满足条件的数据

1.4 group by

分组操作一般有两种实现方案:hash和排序,MySQL使用的是排序:

如果没有索引:

  1. 新建临时表
  2. 扫描数据表,并按插入排序的方式插入临时表,这样就保证了group by列的值相同的会排列在一起
  3. 在临时表上做处理(使用聚合函数,max/min/count/sum/avg)

如果用于分组的列上有索引,那么前两部就直接省略了 

1.5 distinct

在没有索引的情况下,MySQL 主要有以下几种方式来实现 distinct(去重):

  1. 排序 + 去重:当查询数据量不大或内存足够时,MySQL 可能会选择先对数据进行排序,然后去除重复的行。这种方式通常在数据量适中且排序操作成本低时比较有效。
  2. 哈希去重:在处理大量数据时,如果内存足够,MySQL 可能会选择哈希去重。数据会加载到内存中的哈希表里,然后进行去重。这种方式在没有显式的排序要求时能有效处理大数据量的去重操作。
  3. 临时表:当数据量非常大,或者内存限制导致无法使用哈希表时,MySQL 可能会将结果集写入临时表,然后在临时表中进行去重。这种方式用于处理需要较大内存或存储空间的查询。

有索引则按照索引顺序,不重复的读取即可。

1.6 min/max

没有索引需要逐一对比所有的值,有索引则直接取

1.7 avg/sum/count 

没有索引需要扫描全部的数据计算结果,有索引则扫描索引文件(仅包含索引列和主键列)比主数据(全部列)会小一些,速度稍微快些,表的列越多,效果越明显

1.8 in / exists

select * from salaries where emp_no in (10005, 10006, 10007);
select * from salaries where emp_no in (select emp_no from employees);

in会先把in()中的结果全部查出保存在一个结果集中,再查主查询,对主查询的每一行记录在子查询结果集中查找,找到则把当前行加入结果集,直接进行下一行查询,不会再继续比较。

select * from salaries where exists 
(select 1 from employees where employees.emp_no = salaries.emp_no);

exists则先查主查询,对主查询的每一行数据查一次子查询,跟具返回结果决定是否加入结果集

1.9 not in / not exists

select * from salaries where emp_no not in (10005, 10006, 10007);
select * from salaries where emp_no not in (select emp_no from employees);

与使用in类似,not in会先把in()中的结果全部查出保存在一个结果集中,再查主查询,对主查询的每一行记录在子查询结果集中查找,找到则直接进行下一行查询,不会再继续比较,直到比较完not in中的元素,才会把当前行加入结果集.

select * from salaries where exists 
(select 1 from employees where employees.emp_no = salaries.emp_no);

not exists同样先查主查询,对主查询的每一行数据查一次子查询,跟具返回结果决定是否加入结果集

3. explain命令

explain本质上是一个工具,我们使用它是为了辅助理解给定SQL的底层执行策略 

以下面这条SQL为例,不使用explain,我们试着描述一下它的执行策略

-- 查询编号大于10005的员工信息,按工资升序排列
SELECT * 
FROM
        employees JOIN salaries 
        ON employees.emp_no = salaries.emp_no 
        AND salaries.from_date = '1992-08-04' 
WHERE
        employees.emp_no > 10005 
        AND salaries.salary > 70000 
ORDER BY
        salaries.salary;

-- employees: PRIMARY KEY(emp_no)
-- salaries: PRIMARY KEY(emp_no, from_date)
  1. 根据employees表的主键,直接把主键游标定位到emp_no > 10005的位置
  2. 开始读取employees表的记录,针对每条读到的记录(employees[i],根据employees[i].emp_no和from_date='1992-08-04'去查salaries表,这里刚好可以根据salaries表的主键索引(emp_no, from_date),唯一定位到一条记录salaries[j]
  3. 如果salaries[j].salary > 70000,则把JoinedRow(employees[i], salaries[j])加入join结果
  4. 拿到join结果集后,根据salaries.salary排序

Java伪代码:

// 获取表和索引
Table employeesTable = getEmployeesTable();
Table salariesTable = getSalariesTable();
Index empNoIndex = employeesTable.getPrimaryKeyIndex(); // 主键索引 (emp_no)
Index salaryIndex = salariesTable.getPrimaryKeyIndex(); // 主键索引 (emp_no, from_date)

// 结果集
List<JoinedRow> resultSet = new ArrayList<>();

// 从 employees 表的主键索引定位到 emp_no > 10005 的位置
Cursor employeesCursor = empNoIndex.getCursor();
employeesCursor.setPositionGreaterThan(10005);

// 遍历符合条件的员工记录
while (employeesCursor.hasNext()) {
    // 读取一条员工记录
    Employee employee = employeesCursor.next();
    
    // 使用 salaries 表的主键索引查找对应的薪资记录
    Cursor salariesCursor = salaryIndex.getCursor();
    salariesCursor.setPosition(employee.getEmpNo(), "1992-08-04");
    
    if (salariesCursor.hasNext()) {
        // 读取薪资记录
        Salary salary = salariesCursor.next();
        
        // 检查薪资是否大于阈值
        if (salary.getSalary() > 70000) {
            // 创建连接行并加入结果集
            JoinedRow joinedRow = new JoinedRow(employee, salary);
            resultSet.add(joinedRow);
        }
    }
}

// 根据薪资升序排序结果集
resultSet.sort(Comparator.comparingInt(JoinedRow::getSalary));

// 输出或返回结果集
return resultSet;

使用explain:

-- 查询编号大于10005的员工信息,按工资升序排列
SELECT * 
FROM
        employees JOIN salaries 
        ON employees.emp_no = salaries.emp_no 
        AND salaries.from_date = '1992-08-04' 
WHERE
        employees.emp_no > 10005 
        AND salaries.salary > 70000 
ORDER BY
        salaries.salary;

-- employees: PRIMARY KEY(emp_no)
-- salaries: PRIMARY KEY(emp_no, from_date)

输出结果有2行,分别对应emplyees表和salaries表,这个也可以从table这一列看出来

我们了解一下表格中的字段含义:

  • type:访问类型或连接类型,表示 MySQL 使用的访问方法。例如,ALL(全表扫描)、index(索引扫描)、range(范围扫描)、ref(按索引查找)
  • key:实际用于查询的索引
  • key_len:使用的索引的字段长度(字节数)。这里salaries表中同时使用了emp_no和from_date字段所以是7
  • ref:显示哪些列或常量与索引的列匹配。第二行中表示索引字段与employees数据库中的employees表中的emp_no字段匹配。
  • rows:预计扫描的行数
  • filtered:符合条件的数据率
  • extra:额外的信息,比如是否使用了文件排序、临时表等。

查询employees表时,用了主键索引查找where条件,并且预估通过where条件查询出149667条数据,因为没有其他where条件了,所以100%符合条件,filtered为100,extra中显示使用了where和临时表,以及排序。

查询salaries表时也用了主键索引,这里显示有两个索引,RPIMARY和idx_emp_no都可用,实际使用的是PRIMARY;ref显示用来和索引比较的值有两个,emp_no和一个const(常量值,1992-08-04)emp_no占4个字节,from_data占3个字节,这里都用到了,所以key_len是7;rows表示只会定位到1条数据,这条记录还需要满足salaries.salary > 70000,explain预估满足这个条件的概率为33.33%

有时MySQL的优化器会重写SQL,可以通过show warnings查看重写后的SQL,比如:

explain select * from employees where emp_no in (select emp_no from salaries);
show warnings;

/* select#1 */ select `employees`.`employees`.`emp_no` AS `emp_no`,`employees`.`employees`.`birth_date` AS `birth_date`,`employees`.`employees`.`first_name` AS `first_name`,`employees`.`employees`.`last_name` AS `last_name`,`employees`.`employees`.`gender` AS `gender`,`employees`.`employees`.`hire_date` AS `hire_date` 
from `employees`.`employees` semi join (`employees`.`salaries`) 
where (`employees`.`salaries`.`emp_no` = `employees`.`employees`.`emp_no`)

 这里简化一下重写后的sql:

select employees.* from employees semi join salaries on employees.emp_no = salaries.emp_no;

如果感觉执行计划很奇怪,有可能是MySQL优化器重写了SQL,可以执行show warnings,查看重写后的SQL

也可以使用新版本的执行计划:

explain format=tree
SELECT * FROM employees JOIN salaries 
    ON employees.emp_no = salaries.emp_no AND salaries.from_date = '1992-08-04' 
WHERE employees.emp_no > 10005 AND salaries.salary > 70000 
ORDER BY salaries.salary;
-- employees: PRIMARY KEY(emp_no)
-- salaries: PRIMARY KEY(emp_no, from_date)                                
                                
-> Sort: salaries.salary
    -> Stream results  (cost=82354 rows=49884)
        -> Nested loop inner join  (cost=82354 rows=49884)
            -> Filter: (employees.emp_no > 10005)  (cost=29971 rows=149667)
                -> Index range scan on employees using PRIMARY over (10005 < emp_no)  (cost=29971 rows=149667)
            -> Filter: (salaries.salary > 70000)  (cost=0.25 rows=0.333)
                -> Single-row index lookup on salaries using PRIMARY (emp_no=employees.emp_no, from_date=DATE'1992-08-04')  (cost=0.25 rows=1)

阅读顺序是由内到外:

  1. 通过索引查询10005 < emp_no,然后筛选出employees.emp_no > 10005
  2. 通过索引查emp_no=employees.emp_no, from_date=DATE'1992-08-04',然后筛选出salaries.salary > 70000
  3. 内连接
  4. 排序

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

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

相关文章

计算机毕业设计选题推荐-高校一卡通系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

史记——我与历史的缘妙

究天人之际&#xff0c;通古今之变&#xff0c;成一家之言。 注解&#xff1a;这句话出自司马迁《史记》之《报任安书》。意思是通过“史实”现象揭示本质,探究自然现象和人类社会之间的相依相对关系。通晓从古到今的社会的各种发展演变,进而寻找历代王朝兴衰成败之道理。通过…

【Unity编辑器扩展】SpriteAltas资源一键转换为TMP_SpriteAsset或Sprite图集

【Unity编辑器扩展】艺术字/自定义图片字体/TextMeshPro艺术字生成工具_unity 艺术字-CSDN博客 博文工具源码见GF_X自动化游戏开发框架&#xff1a;GitHub - sunsvip/GF_X: Unity GameFramework HybridCLR&#xff0c;Includes several automated editor extension tools, an …

vue 批量导出pdf 压缩包 zip

vue 批量导出pdf 压缩包 zip 使用插件 html2canvas jspdf jszip &#xff08;百度ai搜出来的是zip-js 这个没法安装&#xff09; file-saver 思路&#xff1a; 1.使用 html2canvasjspdf 将页面转图片转pdf&#xff08;这个怎么转的可以网上搜下很多&#xff09; 2.利用jszipfil…

10个精选ArcGIS图源分享第4辑

数据是GIS的血液。 我们在《10个精选ArcGIS图源分享第3辑》一文中为你分享了10个ArcGIS图源&#xff0c;现在又增加了10个新的图源作为第4辑分享给大家。 并提供了能直接在ArcMap和ArcGIS Pro打开的文件&#xff0c;如果你需要这些ArcGIS图源&#xff0c;请在文末查看该数据的…

iPhone出现4013错误的和解决方案分享

在苹果设备用户群体中&#xff0c;遇到iTunes错误4013是一个颇为棘手的问题。这个错误通常发生在尝试更新iOS系统、恢复iPhone或iPad时&#xff0c;导致操作无法顺利完成。本文将为你提供一系列实用的解决方案&#xff0c;帮助你摆脱iPhone 4013错误的困扰。 一、了解错误4013的…

AI大模型编写多线程并发框架(六十四):监听器优化·下

系列文章目录 文章目录 系列文章目录前言一、项目背景二、第十一轮对话-修正运行时数据三、修正任务计数器四、第十二轮对话-生成单元测试五、验证通过七、参考文章 前言 在这个充满技术创新的时代&#xff0c;AI大模型正成为开发者们的新宠。它们可以帮助我们完成从简单的问答…

面向智能体编程(Agent-Oriented Programming, AOP)

大家已经熟知面向对象编程、面向接口编程&#xff0c;AI大模型研发背景下&#xff0c;又产生了一个新概念&#xff1a;&#xff08;Agent-Oriented Programming, AOP&#xff09; 它是一种特殊的编程范式&#xff0c;它专注于开发能够模拟人类智能行为的智能体。智能体是能够在…

水经微图Web版功能简介

我们在《水经微图Web版341项功能清单》一文中&#xff0c;为你罗列了水经微图&#xff08;下称“微图”&#xff09;的详细功能清单。 现在基于该清单&#xff0c;再为你分享微图最主要的功能&#xff0c;从而让你对该平台有一个基本的了解。 微图Web版功能简介 微图Web版&a…

Vue3 官方推荐状态管理库Pinia

介绍 Pinia 是 Vue 官方团队推荐代替Vuex的一款轻量级状态管理库&#xff0c;允许跨组件/页面共享状态。 Pinia 旨在提供一种更简洁、更直观的方式来处理应用程序的状态。 Pinia 充分利用了 Vue 3 的 Composition API。 官网&#xff1a; Pinia符合直觉的 Vue.js 状态管理库 P…

我如何选择自己的AI细分方向和第一个入门项目

思维导图 下图展示了我的思考和分析过程 目录 思维导图大纲1. **确定兴趣和技能方向**2. **寻找合适的开源项目**3. **评估项目的活跃度**4. **开始贡献**5. **展示你的贡献**6. **推荐开源项目**总结 选择一个细分领域1. **了解各个领域的基本概念**2. **考虑你的兴趣和背景**…

备战秋招60天算法挑战,Day29

题目链接&#xff1a; https://leetcode.cn/problems/decode-ways/ 视频题解&#xff1a; https://www.bilibili.com/video/BV181YKeGE3E/ LeetCode 91. 解码方法 题目描述 一条包含字母 A-Z 的消息通过以下映射进行了 编码 &#xff1a; A -> "1" B -> &qu…

大力出奇迹背景下的Scaling Law能否带领我们走向AGI

Scaling Law&#xff08;尺度定律&#xff09; 在人工智能领域&#xff0c;尤其是在大模型的发展中扮演着至关重要的角色。它描述了模型性能如何随着模型规模&#xff08;如参数数量&#xff09;、数据量和计算资源的增加而提升。这一定律对于理解大模型的能力扩展和优化训练策…

CSS3文本属性详解

4.2 文本属性 想缩进段落&#xff0c;幂指数&#xff0c;标题字符增加间距&#xff0c;要用到文本属性。 最有用的CSS文本属性&#xff1a; text-indent:文本缩进letter-spacing:字符间距word-spacing:单词间距text-decoration:文本装饰&#xff0c;下划线text-align:文字对…

2024年小鹏MONA M03 P7 G3 G3i P5 G9 P7i G6 X9维修手册和电路图

汽修帮手资料库提供各大厂家车型维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对照表位置等&#xff0c;并长期保持高频率资料更新&#xff01; 覆盖车型&#xff1a; 2024年小…

langchain结合searXNG实现基于搜索RAG

目录 一、背景 二、环境说明和安装 1.1 环境说明 2.2 环境安装 2.2.1 searXNG安装 三、代码实现 代码 结果输出 直接请求模型输出 四、参考 一、背景 大语言模型的出现带来了新的技术革新&#xff0c;但是大模型由于训练语料的原因&#xff0c;它的知识和当前实时热点…

白酒酿造设备大揭秘:科技与传统的结合

在白酒的酿造世界里&#xff0c;设备与工艺同样重要。它们共同构建了白酒的不同风味和品质。今天&#xff0c;就让我们一起走进豪迈白酒&#xff08;HOMANLISM&#xff09;的酿造车间&#xff0c;探索那些科技与传统相结合的酿造设备&#xff0c;感受它们如何为白酒的酿造增添魅…

Seata环境搭建

1、Seata下载&#xff1a; 1.下载地址 2.下载的版本 2、Seata参数配置参考&#xff1a; 各种seata参数官网参考 3、Seata安装部署&#xff1a; 3.1.Seata新手部署指南: 3.2.在mysql8.0数据库里面建库建表 a.建数据库&#xff1a; create database seata; use seata;b.建…

开源项目管理工具 Plane 安装和使用教程

说到项目管理工具&#xff0c;很多人脑海中第一个蹦出来的可能就是 Jira 了。没错&#xff0c;Jira 确实很强大&#xff0c;但是...它也有点太强大了&#xff0c;既复杂又昂贵&#xff0c;而且目前也不再提供私有化部署版本了。 再说说飞书&#xff0c;作为国产之光&#xff0…