mysql 常用查询优化策略详解

news2024/11/27 22:28:10

前言

在程序上线运行一段时间后,一旦数据量上去了,或多或少会感觉到系统出现延迟、卡顿等现象,出现这种问题,就需要程序员或架构师进行系统调优工作了,其中,大量的实践经验表明,调优的手段尽管有很多,但涉及到SQL调优的内容仍然是非常重要的一环,本文将结合实例,总结一些工作中可能涉及到的SQL优化策略;

查询优化

可以说,对于大多数系统来说,读多写少一定是常态,这就表示涉及到查询的SQL是非常高频的操作;

前置准备,给一张测试表添加10万条数据

使用下面的存储过程给单表造一批数据,将表换成自己的就好了

create procedure addMyData()

	begin

		declare num int;
		set num =1;
		
		while num <= 100000 do
		
			insert into XXX_table values(
				replace(uuid(),'-',''),concat('测试',num),concat('cs',num),'123456'
			);
 
			set num =num +1;
		end while;

	end ;

然后调用该存储过程

call addMyData();

本篇准备了3张表,分别为学生(student)表,班级(class)表,账户(account)表,各自有50万,1万和10万条数据用于测试;

 

1、分页查询优化

分页查询是开发中经常会遇到的,有一种情况是,当分页的数量非常大的时候,查询的时候往往非常耗时,比如查询student表,使用下面的sql查询,耗时达到0.2秒;

实践经验告诉我们,越往后,分页查询效率越低,这就是分页查询的问题所在, 因为,当在进行分页查询时,如果执行 limit 400000,10 ,此时需要 MySQL 排序前4000 10 录,仅仅返回400000 - 4 00010 的记录,其他记录丢弃,查询排序的代价非常大

优化思路:

一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化;
1) 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容

SELECT * FROM student t1,(SELECT id FROM student ORDER BY id LIMIT 400000,10) t2 WHERE t1.id =t2.id;

执行上面的sql,可以看到响应时间有一定的提升;

2)对于主键自增的表,可以把Limit 查询转换成某个位置的查询

select * from student where id > 400000 limit 10;

执行上面的sql,可以看到响应时间有一定的提升;

 

2、关联查询优化

在实际的业务开发过程中,关联查询可以说随处可见,关联查询的优化核心思路是,最好为关联查询的字段添加索引,这是关键,具体到不同的场景,还需要具体分析,这个跟mysql的引擎在执行优化策略的方案选择时有一定关系;

2.1 左连接或右连接

下面是一个使用left join 的查询,可以预想到这条sql查询的结果集非常大

select t.* from student t left join class cs on t.classId = cs.id;

为了检查下sql的执行效率,使用explain做一下分析,可以看到,第一张表即left join左边的表student走了全表扫描,而class表走了主键索引,尽管结果集较大,还是走了索引;

 

针对这种场景的查询,思路如下:

  • 让查询的字段尽量包含在主键索引或者覆盖索引中;
  • 查询的时候尽量使用分页查询;

 

关于左连接(右连接)的explain结果补充说明

  • 左连接左边的表一般为驱动表,右边的表为被驱动表;
  • 尽可能让数据集小的表作为驱动表,减少mysql内部循环的次数;
  • 两表关联时,explain结果展示中,第一栏一般为驱动表;

2.2 关联查询关联的字段建立索引

看下面的这条sql,其关联字段非表的主键,而是普通的字段;

explain select u.* from tenant t left join `user` u on u.account = t.tenant_name where t.removed is null and u.removed is null;

 通过explain分析可以发现,左边的表走了全表扫描,可以考虑给左边的表的tenant_name和user表的account 各自创建索引;

create index idx_name on tenant(tenant_name);

create index idx_account on `user`(account);

再次使用explain分析结果如下

 可以看到第二行type变为ref,rows的数量优化比较明显。这是由左连接特性决定的,LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引 。

2.3 内连接关联的字段建立索引

我们知道,左连接和右连接查询的数据分别是完全包含左表数据,完全包含右表数据,而内连接(inner join 或join) 则是取交集(共有的部分),在这种情况下,驱动表的选择是由mysql优化器自动选择的;

在上面的基础上,首先移除两张表的索引

ALTER TABLE `user` DROP INDEX idx_account;
ALTER TABLE `tenant` DROP INDEX idx_name;

使用explain语句进行分析

 然后给user表的account字段添加索引,再次执行explain我们发现,user表竟然被当作是被驱动表了;

 此时,如果我们给tenant表的tenant_name加索引,并移除user表的account索引,得出的结果竟然都没有走索引,再次说明,使用内连接的情况下,查询优化器将会根据自己的判断进行选择;

 

3、子查询优化

子查询在日常编写业务的SQL时也是使用非常频繁的做法,不是说子查询不能用,而是当数据量超出一定的范围之后,子查询的性能下降是很明显的,关于这一点,本人在日常工作中深有体会;

比如下面这条sql,由于student表数据量较大,执行起来耗时非常长,可以看到耗费了将近3秒;

select st.* from student st where st.classId in (
	
	select id from class where id > 100

);

 通过执行explain进行分析得知,内层查询 id > 100的子查询尽管用上了主键索引,但是由于结果集太大,带入到外层查询,即作为in的条件时,查询优化器还是走了全表扫描;

 

针对上面的情况,可以考虑下面的优化方式

select st.id from student st join class cl on st.classId = cl.id where cl.id > 100;

子查询性能低效的原因

  • 子查询时,MySQL需要为内层查询语句的查询结果建立一个临时表 ,然后外层查询语句从临时表中查询记录,查询完毕后,再撤销这些临时表 。这样会消耗过多的CPU和IO资源,产生大量的慢查询;
  • 子查询结果集存储的临时表,不论是内存临时表还是磁盘临时表都不能走索引 ,所以查询性能会受到一定的影响;
  • 对于返回结果集比较大的子查询,其对查询性能的影响也就越大;

使用mysql查询时,可以使用连接(JOIN)查询来替代子查询。连接查询不需要建立临时表 ,其速度比子查询要快 ,如果查询中使用索引的话,性能就会更好,尽量不要使用NOT IN 或者 NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xx IS NULL替代;

一个真实的案例

在下面的这段sql中,优化前使用的是子查询,在一次生产问题的性能分析中,发现某个tenant_id下的数据达到了35万多,这样直接导致某个列表页面的接口查询耗时达到了5秒左右;

 

找到了问题的根源后,尝试使用上面的优化思路进行解决即可,优化后的sql大概如下,

 

4、排序(order by)优化

在mysql,排序主要有两种方式

  • Using filesort : 通过表索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort
    buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序;
  • Using index : 通过有序的索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高;

对于以上两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序操作时,尽量要优化为 Using index

4.1 使用age字段进行排序

由于age字段未加索引,查询结果按照age排序的时候发现使用了filesort,排序性能较低;

 

给age字段添加索引,再次使用order by时就走了索引;

 

4.2 使用多字段进行排序

通常在实际业务中,参与排序的字段往往不只一个,这时候,就可以对参与排序的多个字段创建联合索引;

如下根据stuno和age排序

 给stuno和age添加联合索引

create index idx_stuno_age on `student`(stuno,age);

再次分析时结果如下,此时排序走了索引

关于多字段排序时的注意事项 

1)排序时,需要满足最左前缀法则,否则也会出现 filesort;

在上面我们创建的联合索引顺序是stuno和age,即stuno在前面,而age在后,如果查询的时候调换排序顺序会怎样呢?通过分析结果发现,走了filesort;

2)排序时,排序的类型保持一致

在保持字段排序顺序不变时,默认情况下,如果都按照升序或者降序时,order by可以使用index,如果一个是升序,另一个是降序会如何呢?分析发现,这种情况下也会走filesort;

 

5、分组(group by)优化

group by 的优化策略和order by 的优化策略非常像,主要列举如下几个要点:

  • group by 即使没有过滤条件用到索引,也可以直接使用索引;
  • group by 先排序再分组,遵照索引建的最佳左前缀法则;
  • 当无法使用索引列时,增大 max_length_for_sort_data 和 sort_buffer_size 参数的设置;
  • where效率高于having,能写在where限定的条件就不要写在having中了;
  • 减少使用order by,能不排序就不排序,或将排序放到程序去做。Order by、groupby、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的;
  • 如果sql包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢;

5.1 给group by的字段添加索引

如果字段未加索引,分析结果如下,这种结果性能显然很低效

 给stuno添加索引之后

  给stuno和age添加联合索引

 

如果不遵循最佳左前缀,group by 性能将会比较低效

 遵循最佳左前缀的情况如下

 

6、count 优化

count() 是一个聚合函数,对于返回的结果集,一行行判断,如果 count 函数的参数不是NULL,累计值就加 1,否则不加,最后返回累计值;

用法:count(*)、count(主键)、count(字段)、count(数字)

如下列举了count的几种写法的详细说明

用法说明
count(主键)InnoDB 会遍历整张表,把每一行的主键id值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为null);
count(*)InnoDB不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加;
count(字段)没有not null 约束 : InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加,有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加;
count(数字)InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加;

经验值总结

按照效率排序来看,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽量使用 count(*)

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

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

相关文章

Linux学习——目录操作和库使用

目录 一、打开目录 二、读取目录 三、关闭目录 四、修改文件权限 五、获取文件属性 六、库的概念&#xff1a; 1、静态库 缺点&#xff1a; 优点&#xff1a; 创建静态库步骤&#xff1a; 链接静态库&#xff1a; 2、动态库 动态库的生成步骤&#xff1a; 练习题&a…

论文阅读之Enhancing Transformer with Sememe Knowledge(2020)

文章目录论文阅读Transformmer-SETransformer-SP实验结果总结参考论文阅读 文章建议结合两种简单的方法将义原知识整合&#xff1a; 1&#xff09;基于语言学假设&#xff0c;我们将聚合义原嵌入添加到每个词嵌入中以增强其语义表示&#xff1b; 2&#xff09;我们使用义原预测…

OpenCV实战项目 -- 口罩识别

每次我忘记戴口罩去食堂吃饭的时候&#xff0c;门口都会有志愿者学生提醒你&#xff1a;“你好&#xff0c;麻烦戴下口罩。” 进门后里面那块大屏幕还会发出声音&#xff1a;“请佩戴口罩”。 上次博客仿照宿舍楼下那块大屏幕写了个人脸考勤&#xff0c;所以这次我打算弄一个口…

std::logic_error 错误的解决

今天测试一个程序&#xff0c;突然出现一个 std::logic_error 错误&#xff0c;详细如下&#xff1a; 这个应该是 std::string 相关的一个错误&#xff0c;具体的错误信息还得用 GDB 跟踪一下了。 看第 8 栈帧已经是系统库里的东西了&#xff0c;第 9 帧是我本地的代码&#x…

【C进阶】之定义结构体及使用typedf

1 结构体中包含函数指针类型成员 声明的格式&#xff1a; struct 结构体名 { 返回类型 (*函数指针名)(形参列表); }; 定义结构体类型的变量并对结构体中的函数指针成员进行初始化 struct 结构体名 结构体变量名; 结构体变量名.函数指针名 函数名; // 函数指针指向的函数具有相…

目标检测(7)—— YOLO系列V3

一、YOLOV3 多scale 三种scale&#xff1a; 为了检测到不同大小的物体&#xff0c;设计了3个scale。 特征融合不好。 感受野大的特征图预测大的&#xff0c;中的预测中的&#xff0c;小的预测小的。各自预测各自的&#xff0c;不用做特征融合。 三个候选框&#xff1a; 每个特…

「C++小游戏教程」基本技巧(2)——系统 DOS 命令

0. 引言 「C小游戏教程」基本技巧(1)——随机化 在 (1) 中&#xff0c;我在使用 random_shuffle() 时加了一个 system("pause");。其中 system() 是系统发出 DOS 命令的函数&#xff0c;原型为 int system(char *command);。我们今天就来谈谈这个函数的主要功能用途…

Redis持久化

目录 一、Redis高可用 1.持久化 2.主从复制 3.哨兵 4.Cluster集群 二、Redis持久化 三、RDB持久化 1.概念 2.触发条件 &#xff08;1&#xff09;手动触发 &#xff08;2&#xff09;自动触发 3.执行流程 4.启动时自动加载 四、AOF持久化 1.概念 2.开启AOF持久…

微信公众号获取openid流程

说明 微信公众号获取openid&#xff0c;在官方文档中称为网页授权&#xff0c;授权有两种scope&#xff0c;snsapi_base和snsapi_userinfo&#xff0c;snsapi_base是静默授权&#xff0c;不需要用户同意&#xff0c;以下要说的就是静默授权。 关于网页授权的两种 scope 的区别…

[CISCN2019 华北赛区 Day1 Web2]ikun

要买lv6 然后下一页下一页的找&#xff0c;也找不到 来个脚本爆破一下 #coding&#xff1a;utf-8 import requests import time for i in range(1,200):print(i)url http://17dfa3f4-2826-4b97-9d61-a920b6c8976f.node4.buuoj.cn:81/shop?page{}.format(i)r requests.get…

目标检测(6)—— YOLO系列V2

一、YOLOV2改进的概述 做的改进如下图&#xff1a; Batch Normalization 批量归一化层 不加BN层&#xff0c;网络可能学偏&#xff0c;加上归一化进行限制。 从今天来看&#xff0c;conv后加BN是标配。 更大的分辨率 V1训练的时候使用224224&#xff0c;测试用448448。 V2训…

胡扯推荐算法(协同)及其dome实现

文章目录前言推荐系统定义基本分类相似度计算欧式距离皮尔逊系数余弦相似度协同过滤案例数据定义相似度计算推荐svd奇异值分解优化完整代码总结前言 打瞌睡遇到送枕头的感觉真爽嘿嘿 BoyC啊 废话不多说&#xff0c;开始吧。 推荐系统定义 推荐系统(Recommendation System,…

张益唐与黎曼猜想

一个人活在世界上&#xff0c;可以有不同层次的贡献&#xff0c;有正的有负的。唐山烧烤店打人的那种人&#xff0c;就是典型的负的贡献。正的贡献有大有小&#xff0c;像我这样开发一个小软件&#xff0c;写一段小博文&#xff0c;这是小的贡献&#xff1b;像张益唐这样&#…

zlMediaKit 9 ThreadPool模块

semaphore.h TaskQueue.h threadgroup.h ThreadPool.h ThreadPool semaphore 基于条件变量和锁实现的信号量post和wait语义 #include <mutex> #include <condition_variable>namespace toolkit {class semaphore { public:explicit semaphore(size_t initia…

Linux命令从入门到实战 ----查找文件和目录压缩和解压缩

文章目录搜索查找find查找文件和目录locate快速定位文件路径grep 过滤查找| 管道符which命令用于查找文件。whereis压缩和解压缩gzip/gunzip 压缩zip/unzip压缩tar打包总结搜索查找 find查找文件和目录 find指令将从指令指定目录下向下遍历其各个子目录&#xff0c;将满足条件…

算法60天:day46

算法60天&#xff1a;day46动态规划-单词拆分动态规划-多重背包问题动态规划-背包总结动态规划-单词拆分 力扣链接 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(),wordDict…

RabbitMQ消息发送和接收(实例)

消息发送&#xff1a; 1.首先启动rabbitmq 2.查看防火墙状态&#xff0c;如果是开启状态则需要关闭防火墙 3.通过浏览器访问rabbitmq控制台&#xff0c;ip15672端口号 &#xff0c;例如http://192.168.174.129:15672 登录时输入自己的此前设置的登录名和密码 4.打开idea,创建r…

农产品果蔬商城交易系统(Java+Web+MySQL)

目录 摘要 I Abstract II 前言 1 1 课题简介 2 1.1 选题背景 2 1.2 课题的意义 2 1.3 系统目标 3 2. 可行性研究 5 2.1 技术可行性 5 2.2 经济可行性 5 2.3 操作可行性 5 2.4 法律可行性 6 3. 需求分析 7 3.1 系统需要解决的主要问题 7 3.2 系统具备的基本功能 7 3.3 数据流图…

MySQL开篇:简单的库操作,表操作,数据类型

✨博客主页: 心荣~ ✨系列专栏:【MySQL】 ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. 什么是MySQL二. 基础库操作1. 创建数据库2. 查看所有数据库3. 选中数据库4. 删除数据库三. 设置数据库的编码字符集四. MySQL数据类型1. 数值类型2. 字符串类型3. 日期数据类型五…

_Linux 动态库

文章目录0. 前言1. 生成动态库1.1 我们把静态库和动态库打包1.2 当动静库同时存在的时候默认生成的是动态库1.3 -static2. 动态库的使用2.1 运行动态库的方法3. 库文件名称和引入库的名称0. 前言 链接&#xff1a;静态库文章 上一章我们讲解了静态库的生成和两种使用&#xff…