当前位置: 首页 > article >正文

Mysql 查询性能调优总结

一、查询分析性能的相关配置

1.1 配置显示查询性能的参数

在 MySQL 中,SHOW PROFILES 命令用于显示最近的查询性能概况,帮助你分析哪些查询比较耗时。

show profiles;

  • 需要启用参数 profiling,才能使用上述功能,其相关参数设置如下:
// 所有与显示查询性能有关的参数
SHOW VARIABLES LIKE '%profiling%';

// 开启显示查询性能的功能
SET profiling = 1;

// 是否支持 profiling
SHOW VARIABLES LIKE 'have_profiling';

// profiling 历史记录的最大条数
SHOW VARIABLES LIKE 'profiling_history_size';

1.2、Explain 分析

explain 命令可获取 MySQL 如何执行 SELECT 语句的信息,包括是否使用了索引、进行了哪些类型的连接、扫描了多少行数据等。

  • 使用方法

在 select 语句前加上 explain 即可

  • 输出字段解释
字段名说明
select_type查询的类型:比如 SIMPLE(简单的 SELECT,不使用 UNION 或子查询)、PRIMARY(查询中最外层的 SELECT)、UNION(UNION 中的第二个或后续的 SELECT 语句)、DEPENDENT UNION(UNION 中的第二个或后续的 SELECT 语句,取决于外部查询)、SUBQUERY(子查询中的第一个 SELECT)、DEPENDENT SUBQUERY(子查询中的第一个 SELECT,取决于外部查询)等
table输出行所对应的表
type连接类型:ALL(全表扫描)、index(索引全扫描)、range(索引范围扫描)、ref(非唯一性索引扫描,返回匹配某个单值的所有行)、eq_ref(唯一性索引扫描,对于每个索引键,表中最多只有一条匹配行)、const/system(表最多有一个匹配行,通常用于主键或唯一索引比较)、NULL(不用访问表或索引,就能得到所需数据)
possible_keys可能应用在这张表上的索引
key实际使用的索引
ref显示索引的哪一列或常数被用于查找值
rows MySQL 认为必须检查的行数,以找到查询所需行,这是估算的行数

filtered

表示返回结果的行占开始查找行的百分比
extra包含不适合在其他列中显示的额外信息,比如是否使用了文件排序(Using filesort)、是否使用了临时表(Using temporary)、是否使用了回表(Using where)等

mysql官网中有关查询计划的说明:MySQL :: MySQL 8.4 Reference Manual :: 10.8.2 EXPLAIN Output Format

二、性能调优方案

2.1 表及数据量背景介绍

以表 order_master_test 为例,其中含有id主键索引及index_reportDate、index_createAt 辅助索引,数据量为470多万,具体表结构为:

// 创建表
CREATE TABLE `order_masters_test` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `group_id` bigint DEFAULT NULL,
  `shop_id` bigint DEFAULT NULL,
  `shop_name` longtext,
  `report_date` bigint DEFAULT NULL,
  `order_key` longtext,
  `order_type` longtext,
  `total_amount` float DEFAULT NULL,
  `area_name` longtext,
  `table_name` longtext,
  `channel_key` longtext,
  `channel_name` longtext,
  `order_status` int unsigned DEFAULT NULL,
  `create_by` longtext,
  `create_at` bigint DEFAULT NULL,
  `paid_amount` float DEFAULT NULL,
  `promotion_amount` float DEFAULT NULL,
  `checkout_by` longtext,
  `checkout_at` bigint DEFAULT NULL,
  `pay_desc` longtext,
  `customer_qty` int unsigned DEFAULT NULL,
  `order_remark` longtext,
  PRIMARY KEY (`id`),
  KEY `index_reportDate` (`report_date`),
  KEY `index_createAt` (`create_at`)
) ENGINE=InnoDB AUTO_INCREMENT=5111719 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

// 插入具体数据及批量插入数据
INSERT INTO order_masters_test
(`group_id`,
`shop_id`,
`shop_name`,
`report_date`,
`order_key`,
`order_type`,
`total_amount`,
`area_name`,
`table_name`,
`channel_key`,
`channel_name`,
`order_status`,
`create_by`,
`create_at`,
`paid_amount`,
`promotion_amount`,
`checkout_by`,
`checkout_at`,
`pay_desc`,
`customer_qty`,
`order_remark`)
VALUES
('955', '12345', '望京总店', '20240620', 'DD20240620111', '堂食', '90', '', '', '', '', '1', '001', '0', '0', '0', '', '0', '', '0', '测试订单11111');


insert into order_masters_test(`group_id`,
    `shop_id`,
    `shop_name`,
    `report_date`,
    `order_key`,
    `order_type`,
    `total_amount`,
    `area_name`,
    `table_name`,
    `channel_key`,
    `channel_name`,
    `order_status`,
    `create_by`,
    `create_at`,
    `paid_amount`,
    `promotion_amount`,
    `checkout_by`,
    `checkout_at`,
    `pay_desc`,
    `customer_qty`,
    `order_remark`
) 
select `group_id`,
    `shop_id`,
    `shop_name`,
    `report_date`,
    concat(date_format(now(), '%Y%m%d%H%i%s'), LPAD(FLOOR(1 + (RAND() * 10000)), 4, '0')),
    `order_type`,
    `total_amount`,
    `area_name`,
    `table_name`,
    `channel_key`,
    `channel_name`,
    `order_status`,
    `create_by`,
    date_format(now(), '%Y%m%d%H%i%s'),
    `paid_amount`,
    `promotion_amount`,
    `checkout_by`,
    `checkout_at`,
    `pay_desc`,
    `customer_qty`,
    `order_remark`
from order_masters_test;

 

需要优化的查询语句为:

select * from order_masters_test where report_date = 20240620 order by create_at desc limit 200000, 10;

当前执行该语句,查询超时,如图:

2.2 初级调优:强制使用更合适的索引

经查询,表中report_date=20240620的数据总共有 524288,且查询执行计划发现,mysql使用了索引 index_reportDate 和文件排序。

  •  分析原因:

mysql 使用 index_reportDate 索引,会首先查出满足条件(report_date=20240620)的52万条索引数据,再根据主键ID回表查找create_at字段,然后对52万条数据的create_at 进行降序排列,最后筛选出第200001到200010条最终数据。

注:此处省略了B+树索引的数据结构介绍,有关内容可以查看Mysql 存储引擎原理探究-CSDN博客

  • 调优方案:

强制使用索引 index_createAt,先查找排好序的create_at值,再回表筛选 reportDate = 20240620 的数据,省去了文件排序的时间

  • 使用方法:

在表名后增加语句 force index(index_createAt)

  • 优化结果:

由查询超时提升到12.7 秒

2.3 中级调优:使用联合索引

  • 分析原因:

经分析,查询语句使用了 report_date 字段过滤条件以及 create_at 字段排序,如果将这两个字段创建联合索引,则查询时间主要分布在查找联合索引及根据主键ID到主键索引中查找完整数据上,不用再进行文件排序等。

  • 调优方案:

创建联合索引,具体sql语句如下:

-- 新建 report_date 和 create_at 字段的联合索引
create index index_reportDate_createAt on order_masters_test(report_date, create_at);

  • 优化结果:

查询时间由12.7秒提升为6.29秒

2.4 高级调优:使用覆盖索引+join关联

  • 分析原因:

查询语句中的 limit 200000,10 表示需要跳过前20万条记录,取接下来的10条数据,在上述两种调优方案中,都需要回表20万+次,其实前20万次回表都是无效的,所以如果能跳过这20万次回表,则能提高很大的查询效率。

  • 调优方案:

通过联合索引 index_reportDate_createAt 先取出符合条件的10条数据的主键ID,当做临时表,再用这10个主键ID关联原表,从而获取完整数据,整个过程只需要回表10次,大大地提升查询性能。具体SQL语句如下:

select b.* from (
select id from order_masters_test 
where report_date = 20240620 order by create_at desc limit 200000, 10) temp
left join order_masters_test b on temp.id = b.id;

  • 优化结果:

 查询时间由6.29秒提升到69毫秒,性能大大提升。

2.5 索引合并

or 条件会使用索引合并,如下图:

不走索引的情况下,可以把 or 条件改为 union(去重)/ union all(不去重) ,如下:

实测中发现,分别执行上述的 where report_date = 20240623 和 create_at=20241219133733 耗时短,但用 union 合起来时则耗时了27s

注意:索引合并仅适用于单表查询

2.6 索引下推

索引下推(Index Condition Pushdown)的执行过程:mysql 数据库会在取出索引的同时,判断是否可以进行 where 条件的过滤,这样就不用先回表再进行where条件过滤发现数据不可用,减少了IO操作。ICP 优化支持 range、ref、eq_ref、ref_or_null 类型的查询,可在执行计划的列 Extra 看到 Using index condition。

2.7 索引失效

总结起来,就是两种场景:没有办法使用索引;使用索引还不如不使用索引

以下情况会导致索引失效:

  • 数据量很小
  • 索引区分度太低:比如性别字段(男、女、未知三个值)建立索引
  • 模糊查询:如 like '%23',不满足最左前缀原则
  • 函数计算
  • 类型隐式转换:字段是字符串型,给定的值是数字,如varchar类型的 address = 1234
  • 占比过大:范围查询数据量超过全表30%,但这不是一个固定比例,还有表大小、行数、IO块大小等影响因素

三、总结

优化概览:

mysql 查询优化的本质是减少IO次数。

3.1 业务层级优化

根据业务场景判断有些查询是否每次都必须。比如查询订单信息时,是否必须查询对应商品明细?如果在订单主表中有字段标识出该订单没有商品明细,则不用再去查询商品明细表。

3.2 数据库层级优化

3.2.1 数据库表设计合理,主键最好使用 int 或 bigint。

不使用uuid等字符串的原因:

①插入顺序问题,可能导致页分裂

②存储占用空间大,而且会导致辅助索引占用的空间也大(辅助索引会保存主键ID)

3.2.2 索引是否合理:是否使用了联合索引、索引覆盖等

3.2.3 表字段类型是否合理

3.2.4 查询的字段是否必须

3.2.5 对于频繁查询的表,可以考虑进行分库分表

3.2.6 使用缓存技术:对于一些相对稳定的查询结果,可以考虑使用缓存技术,如redis等,减少对数据库的访问

3.3 硬件层级优化

CPU、内存、磁盘等

参考视频:MySQL 的一条语句是如何执行的?_哔哩哔哩_bilibili


http://www.kler.cn/a/453450.html

相关文章:

  • VS Code AI开发之Copilot配置和使用详解
  • K8s证书过期
  • Java抽象工厂+单例模式
  • STM32 高级 谈一下IPV4/默认网关/子网掩码/DNS服务器/MAC
  • Day7补代码随想录 454.四数相加II 383赎金信 15.三数之和 18.四数之和
  • 007-spring-bean的相关配置(重要)
  • PyQt5 学习方法之悟道
  • FPGA实时红外相机采集输出系统,提供工程源码和技术支持
  • 大模型Weekly|月之暗面发布Kimi视觉思考模型 k1;谷歌发布最新视频生成模型Veo 2
  • HarmonyOS Next 应用元服务开发-分布式数据对象迁移数据权限与基础数据
  • SpringCloudAlibaba技术栈-Dubbo
  • kubernetes Gateway API-部署和基础配置
  • 【gulp】gulp 的基本使用
  • 从数据仓库到数据中台再到数据飞轮:电信行业的数据技术进化史
  • 质数生成函数、质数判断备份
  • <论文>语言模型可以进行无监督的多任务学习?
  • 从源码到应用:在线问诊系统与医疗陪诊APP的开发全过程详解
  • 12.26 学习卷积神经网路(CNN)
  • npm淘宝镜像
  • Dilateformer实战:使用Dilateformer实现图像分类任务(二)
  • BLE core 内容整理解释
  • FFMPEG结构体分析
  • Linux高并发服务器开发 第六天(rwx 对于目录和文件的区别 gcc编译器 动态库静态库)
  • yolov4算法及其改进
  • C#异步1
  • 蚂蚁集团 CTO 线大规模调整、多个 AI 业务部门被合并