【MySQL】记一次 SQL 优化

1 背景

我们的数据库中配置了一套慢 SQL 的监控(这里存在 SQL 本身不慢, 但是触发某些场景, 比如 filesort 等也会被采集), 会不定时的输出一批需要排查的 SQL, 下面挑了几条比较有意思的进行分享。

2 table_1

表结构:

CEATE TABLE `table_1` (column_1,column_2,column_3,column_4,time_column_5KEY `idx_001`(`column_3`),KEY `idx_002`(`column_2`, `column_3`, `time_column_5`)
)

SQL:

select column_1 from table_1 where column_2 and column_3 and time_column_5 > ? order by time_column_5 desc

执行计划:

select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_1rangeidx_001,idx_002idx_00213315Using index condition

通过分析执行过程, 可以发现这条 SQL 本身已经是 range 同时基本走到符合条件的索引 idx_002 了。
因为这条 SQL 只需要在查询出一个 column_1 字段, 如果还想再进一步优化的话, 可以直接将这个字段添加到 idx_002 的索引, 直接让这条 SQL 在索引树中处理, 不回表查询。

修改 idx_002 的索引如下 column_2, column_3, column_1, time_column_4

select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_1rangeidx_001, idx_002idx_00213315Using where, Backward index scan, Using index

可以发现 Extra 中, 整条 SQL 为 Using index, 只使用到了索引树, 同时利用到了 MySQL8 的新特性 backward index scan (MySQL 8 对字段倒序排序做的一种优化, 同样是直接在索引树上操作, 不回表处理)。

3 table_2

表结构:

CEATE TABLE `table_2` (column_1,column_2,column_3,column_4,time_column_5KEY `idx_001`(`column_3`),KEY `idx_002`(`column_2`, `column_3`, `time_column_5`)KEY `idx_003`(`column_4`, `time_column_5`)
)

SQL:

select column_1 from table_2 where (column_2 in (?+) or column_4 in (?+)) and time_column_5 > ? and time_column_5 <= ?

执行计划:

select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_2index_mergeidx_001, idx_002, idx_003idx_002128128179070.11Using sort_union(idx_002, idx_003); Using where

index_merge: 分别通过对两个独立的 index 进行过滤之后,将过滤之后的结果聚合在一起,然后在返回结果集
sort_union: 简单理解: 使用到了 or, 回表捞到需要的数据,合并后再排序

column_2 和 column_4 都有各种适合的索引, 尝试通过 union all 将 or 替换掉

select column_1 from table_2 where column_2 in (?+) and time_column_5 > ? and time_column_5 <= ?
union all
select column_1 from table_2 where column_4 in (?+) and time_column_5 > ? and time_column_5 <= ?
select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_2rangeidx_002idx_00213363231Using index condition; Using where
SIMPLEtable_2rangeidx_003idx_0013351Using index condition; Using where

观察 rows 预测扫描的行数少了, 同时 Extra 中切换到了 使用索引 + 回表查询

4 table_3

表结构:

CEATE TABLE `table_2` (column_1,column_2,char_column_3,time_column_4KEY `idx_001`(`char_column_3`, `time_column_4`)
)

SQL:

select column_1, char_column_3, time_column_4 from table_3 where char_column_3 in (?+) order by time_column_4 desc lmit ?, ?

执行计划:

select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_3rangeidx_001idx_00112845Using index condition; Using where; Using filesort

这条 SQL 主要是因为使用到了 filesort。
通过索引 idx_001 可以看出 查询的条件 char_column_3 和 time_column_4 都是在索引里面的, 理而导致 Using filesort 的原因是因为 char_column_3 的条件是 in

尝试将 char_column_3 的条件修改为 =, 执行计划如下

select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_3refidx_001idx_001128const45Using index condition; Using where

in 导致索引中的 time_column_4 失效原因:
索引是有序的, 通过索引找到的数据, 理论上也是有序的。
比如表中当前有数据 (1, 1), (2, 2), (3, 3), (1,5) (1,3)。
通过 char_column_3 in 查询出来的数据为 (1, 1), (1,3), (1,5), (2, 2), (3, 3), 可以发现是按照 char_column_3 排序好了。
但是现在我们需要的是按照 time_column_4 进行排序, 那么就在用 char_column_3 查询出来的数据后再进行多一次排序, 就导致了 filesort 的出现。

尝试去掉 filesort, 建立 idx_02(time_column_4, char_column_3) 的组合索引, 同时强制走这个索引 (通过尝试, 发现 MySQL 的优化器分析走旧索引比较好)

select column_1, char_column_3, time_column_4 from table_3 force index(idx_02) where char_column_3 in (?+) order by time_column_4 desc lmit ?, ?
select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_3refidx_001idx_001128const150.33Using where

可以发现 filesort 去掉了, 但是对应的 rows 查询条数上涨了。
结论: 当前 SQL 暂时这样, 不修改。

5 table_4

表结构:

CEATE TABLE `table_4` (column_1,column_2,column_3
)

SQL:

select * from table_4 where column_2 = ? and column_3 = ?

执行计划:

select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_4ALL128const10260.2Using where

走到了 ALL, 整张表数据量也在 900+ 左右, 可能初期设想时不会有那么多数据, 所以没加索引。
现在走动了 ALL, 第一时间想到的是给查询的 2 个字段加上索引, 那么直接加上吗?

先分析一下 column_2 和 column_3 各自的分布。

select count(1), column_2 from table_4 group by column_2
count(1)column_2
102
9013
74
15
126
17
18
29
select count(1), column_3 from table_4 group by column_3
count(1)column_3
91
642
8613

可以发现 column_2 和 column_3 应该都是枚举值 (一开始可能考虑到都是枚举所以没加索引吧)。
回到代码中查看 SQL 的调用链, 发现调用的地方就 2 个, 查询的条件 column_2 主要在 (6,7,8), 而 column_3 则是 2。

虽然平常我们都知道枚举类型的字段不要加索引,因为区分度不高。
但是在某些情况下,还是建议建立索引的。举个例子: 有一张大表, 发送给客户的短信信息和状态, 表中有个字段存储的是当前这条短信是否发送给客户了,0: 未发送, 1: 已发送。
短信发送成功后, 会将状态修改为 1: 已发送。 基于这种情况, 这张大表中的未发送的数据量和远远小于已发送的数据量, 同时平时查询的时候也都几乎是查询未发送的, 这时候就可以给这个枚举值字段加上索引, 因为通过未发送这种情况可以筛选掉很多的数据量。

所以给 column_2 和 column_3 建立一个组合索引, 同时因为 column_2 的区分度更高, 所以将 column_2 放在 column_1 的前面。

6 table_5

表结构:

CEATE TABLE `table_5` (column_1,column_2,column_3KEY idx_001(column_1)
)

SQL:

select * from table_5 where column_1 = ? and column_2 = ? order by convert(column_1 using gbk)

执行计划:

select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_5refidx_001idx_001152const15Using index condition; Using where; Using filesort

同时是因为 filesort, 这里可以将排序的字段 column_1 加入到 idx 索引中。
但是因为 column_1 使用到了索引, 最终只会导致加的这个字段不起作用, 那么

  1. 去掉这个函数, 排序而已, 对数据的准确性没有影响, 但是排序的的顺序和生产的不一致
  2. 将这个排序移到代码中进行

7 table_6

CEATE TABLE `table_6` (column_1,column_2,column_3,is_deletedKEY idx_001(column_1, is_deleted),KEY idx_002(column_2, is_deleted)
)

当前表数据量 80155064, 使用了逻辑删除, 未删除:已删除 = 5:3 左右

select column_1, column_2, column_3 from table_6 where column_1 in (?+) and is_deleted = 0
select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_6rangeidx_001idx_001303210Using index condition; Using where
select column_1, column_2, column_3 from table_6 where column_2 in (?+) and is_deleted = 0
select_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
SIMPLEtable_6rangeidx_002idx_002153210Using index condition; Using where

2 条 SQL 的执行时间都是秒级别, 但是通过分析可以发现走的索引都很准确了, 通过调整索引的方式不太合适了。
那么有别的方式优化吗? 逻辑删除 --> 已经删除的数据还有保存的意义吗? --> 清除(或迁移到另一张表), 减轻表的数据量, 也能达到优化的效果。

7.1 本地尝试清除数据

7.1.1 初始数据
总数据量未删除已删除
1000000150026024997399

表内存情况

NameEngineVersionRow_formantRowsAvg_row_lengthData_lengthMax_data_lengthData_freeAuto_incrementCreate_timeUpdate_timeCheck_timeCollationChecksumCreate_options
table_6InnoDb10Dynamic96572341601547698176 (1476.00M)3801726976 (3625.60M)4194304100000032024-06-20 14:50:342024-06-20 16:27:10NULLutf8_general_ciNULL

备注:
date_length: 数据的大小
index_length: 索引的大小
data_free: 碎片空间的大小

7.1.2 继续往表追加数据

向表中追加 1529500 条数据

总数据量未删除已删除
1152950157668615762640
NameEngineVersionRow_formantRowsAvg_row_lengthData_lengthMax_data_lengthData_freeAuto_incrementCreate_timeUpdate_timeCheck_timeCollationChecksumCreate_options
table_6InnoDb10Dynamic114225301561785724928 (1703.00M)04484366336 (4276.62M)5242880115295032024-06-20 14:50:342024-06-20 16:53:30NULLutf8_general_ciNULL
7.1.3 尝试清除表中一半逻辑删除的数据
DELETE from table_6 where id <= '5764751' and is_deleted = 1
总数据量未删除已删除
864866557668612881804
NameEngineVersionRow_formantRowsAvg_row_lengthData_lengthMax_data_lengthData_freeAuto_incrementCreate_timeUpdate_timeCheck_timeCollationChecksumCreate_options
table_6InnoDb10Dynamic86109902071785724928 (1703.00M)04484366336 (4276.62M)42991616115295032024-06-20 14:50:342024-06-20 17:14:58NULLutf8_general_ciNULL

可以看到虽然删除了表中的部分数据, 但是实际占用的空间没有变化。 这时 InnoDB 内部的设计, 将删除的数据的位置标记为删除的, 后续有新的数据新增进来时, 就复用这个位置。
如果要强制进行空间的整理, 可以通过 alter table 表明 engine=innodb; 的方式进行整理, 但是这个会很耗时。

7.1.4 清除表中所有逻辑删除的数据
DELETE from table_6 where is_deleted = 1
总数据量未删除已删除
576686157668610

通过 alter 手动整理空间

alter table table_6 engine=innodb;

表内存情况

NameEngineVersionRow_formantRowsAvg_row_lengthData_lengthMax_data_lengthData_freeAuto_incrementCreate_timeUpdate_timeCheck_timeCollationChecksumCreate_options
table_6InnoDb10Dynamic57131551781021296640 (973.98M)01571340288 (1498.54M)3145728115295032024-06-20 14:50:34NULLNULLutf8_general_ciNULL

完整的清除数据和整理了碎片空间。

注: 最终落实到生产(保留前 3 个月的数据, 将 3 个月前的数据迁移到另一张表)

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

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

相关文章

工业网关的功能与作用解析-天拓四方

在工业4.0和智能制造的时代背景下&#xff0c;工业网关作为连接现场设备与云端平台的桥梁&#xff0c;正发挥着日益重要的作用。它不仅为工业设备的远程监控和管理提供了可能&#xff0c;还为企业实现数字化转型和智能化升级提供了有力支持。本文将对工业网关的功能与作用进行解…

Python:基于TSFEL库对时间序列进行特征分析

1. TSFEL 时间序列作为主要TSFEL提取方法的输入传递&#xff0c;要么作为先前加载在内存中的数组传递&#xff0c;要么存储在数据集中的文件中。 由于TSFEL可以处理多维时间序列&#xff0c;因此随后应用了一套预处理方法&#xff0c;以确保信号质量足够和时间序列同步&#xf…

AI音乐大模型:深度剖析创意与产业的双重变革

随着AI技术的飞速发展&#xff0c;音乐大模型在最近一个月内纷纷上线&#xff0c;这一变革性技术不仅颠覆了传统的音乐创作方式&#xff0c;更是对整个音乐产业及创意产业带来了深远的影响。本文将从多个维度出发&#xff0c;深度剖析AI音乐大模型对创意与产业的双重变革。 一、…

ONLYOFFICE 8.1:引领桌面办公新潮流,功能升级全面提升

目录 一、ONLYOFFICE是什么&#xff1f; 二、功能完善的PDF编辑器 三、幻灯片版式升级 四、改进从右至左显示 五、新的本地化选项 六、多媒体功能增强 七、应用价值探讨 一、ONLYOFFICE是什么&#xff1f; ONLYOFFICE 是一款功能强大的办公套件&#xff0c;旨在提供全面…

acme.sh泛证书申请

说明: 1、想每个项目都接入域名+端口访问,所以通过acme.sh申请泛域名证书 2、阿里云域名解析,并且指定公网ip地址对应的公共Nginx服务 3、acme.sh证书只有3个月,所以要用shell自动续签证书 4、阿里云域名已解析,所以二级域名、三级域名能正常解析,如下图所示, 一、阿里云…

charles破解

一、Charles官网下载安装包二、安装charles三、charles破解 一、Charles官网下载安装包 根据自己电脑系统 官网下载即可。 链接: https://www.charlesproxy.com/download/latest-release/ 二、安装charles 点击下载的安装包&#xff0c;然后进行安装。 三、charles破解 打…

【认识3D打印技术:如何走进你的生活】

知名苹果产品分析师郭明錤透露&#xff0c;Apple Watch Series 10从今年下半年开始采用由3D打印技术生产的部件。苹果在去年的Apple Watch Series 9上曾试验过3D打印部件&#xff0c;但并没有大规模量产&#xff0c;而在经过大量的测试之后&#xff0c;3D打印大规模生产的效率似…

服务器如何实现SSH免密码登录?

目录 一、服务器和电脑的区别二、什么是SSH三、什么是免密码登录四、服务器如何实现SSH免密码登录 一、服务器和电脑的区别 服务器和电脑是两种不同类型的计算机系统&#xff0c;它们在设计、功能和用途上存在明显的区别。首先&#xff0c;从硬件配置上看&#xff0c;服务器通…

202406240944_数组知识总结

202406240944_数组知识总结 ✏随笔数组理论知识语法回顾C length()、size()、sizeof()三者的区别 (Weather::上海 ⛅多云&#xff0c;23~30℃ 良 冷风徐徐&#x1f32c;️) ✏随笔 数组理论知识 数组是存放在连续内存空间上的相同类型数据的集合。 数组下标都是从0开始的。 …

MySQL学习(3):SQL语句之DDL

1.SQL通用语法与分类 &#xff08;1&#xff09;通用语法 &#xff08;2&#xff09;分类 2.DDL 2.1数据库操作 show DATABASES; #查询所有数据库select DATABASE(); #查询当前数据库create DATABASE 数据库名称 [default charest 字符集] [collate 排列规则]; #default cha…

时序分析(二):input delay分析

一、IO接口分析基本模型 数据按照同步方式可分为系统同步和源同步方式两种。所谓系统同步指发送端和接收端共用一个时钟源&#xff1b;源同步指发送端提供数据同步时钟&#xff0c;接收端根据该时钟进行数据接收。现在多数通信中使用源同步方式&#xff0c;例如以太网、ADC等。…

游戏开发中常用Api

文章目录 Windows PowerShell1.PowerShell的执行策略 Git_Api1.初始化仓库2.设置全局邮箱和用户名3.ssh相关操作3.1.检查是否存在ssh3.2.生成ssh3.3.测试和仓库的ssh连接 4.与远程仓库的操作4.1.连接远程仓库4.2.取消连接4.3.拉取代码4.4.提交相关 5.分支操作5.1.修改要提交的分…

洗地机怎么选择最好?四大洗地机精选放心入手

在当今生活节奏飞快的社会中&#xff0c;人们越来越渴望拥有一款高性能、实用方便的家用洗地机&#xff0c;能够帮助我们节省大量的清洁时间。因为洗地机它是吸尘器的升级版&#xff0c;清洁力比扫地机器人更强&#xff0c;洗地机通过高速旋转的风机&#xff0c;产生超大吸力&a…

java-冒泡排序 2

### 9. 冒泡排序的变种冒泡排序有许多变种&#xff0c;例如鸡尾酒排序&#xff08;Cocktail Shaker Sort&#xff09;&#xff0c;它是冒泡排序的双向版本。鸡尾酒排序在每次遍历时&#xff0c;先从左到右&#xff0c;再从右到左&#xff0c;双向 地“冒泡”&#xff0c;使得排…

Unity之HTC VIVE Cosmos环境安装(适合新手小白)(一)

提示&#xff1a;能力有限&#xff0c;错误之处&#xff0c;还望指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、unity版本电脑配置相关关于unity版本下载建议&#xff1a;0.先下载unity Hub1.不要用过于旧的版本2.不要下载最新版本或者其他非长期支持版本 二、官网下…

chatGPT?是什么,到底用了什么技术呢?

本文尽可能精简的讲解openai的chatgpt 文章目录 前言一、chatgpt是什么&#xff1f;1. 基础架构2. 训练过程3. 应用场景4. 技术特点5. 局限性 二、树形图ChatGPT 大致架构 总结 前言 随着人工智能的不断发展&#xff0c;Ai对话工具的使用也越来越广泛。由国外openai推出的chatg…

百日筑基第二天-随便学点

百日筑基第二天-随便学点 慢SQL发生的原因 缺乏索引&#xff1a;当查询中涉及的列没有合适的索引时&#xff0c;数据库管理系统可能需要执行全表扫描来查找匹配的行&#xff0c;这会大大增加查询时间。查询条件不当&#xff1a;复杂的查询条件、不必要的JOIN操作、过多的子查…

生命在于学习——Python人工智能原理(4.4)

三、Python的数据类型 3.2 Python的组合数据类型 特点&#xff1a;表示多个元素的组合&#xff0c;可以包含不同类型的元素&#xff0c;甚至是其他的组合数据类型。 在内存中通常需要额外的空间来存储元素间的关系。 组合数据类型能够将多个同类型或不同类型的数据组织起来&a…

stencil 简介

stencil 简介 stencil 出现的动机为何要学习 stencil 呢&#xff1f; stencil 是一个生成 Web Component 的编译器&#xff0c;但是其具有自己的特殊语法&#xff0c;使用 stencil 生成的组件可跨框架和在 html 中使用。 其号称结合了最流行框架(angular、react、vue)中的最好…

出版发行企业从传统分销到网格化营销的变革之路(AMT企源)

引言&#xff1a; 本文为该系列文章的第一篇&#xff0c;旨在介绍当前出版发行行业&#xff0c;尤其是各省级新华书店集团围绕“综合教育服务”和“大文化消费服务”两个领域的业务布局下&#xff0c;如何实现营销模式创新、营销组织创新&#xff0c;以推动新华书店集团从传统…