MySQL性能调优
- 1. 查询性能优化
- 1.1 什么是慢查询
- 1.2 慢查询的配置
- 1.3 慢查询解读分析
- 1.3.1 日志模式
- 1.3.2 慢查询分析
- mysqldumpshow
- 1.4 优化SQL查询方法论
- 1.4.1 业务层--请求了不需要的数据?
- 查询不需要的记录
- 总是取出全部列
- 重复查询相同的数据
- 1.4.2 执行层--是否在扫描额外的记录
- 响应时间
- 扫描的行数和返回的行数
- 扫描的行数和访问的类型
- 1.5 重构SQL查询的方法论
- 1.5.1 一个复杂查询还是多个简单查询
- 1.5.2 切分查询
- 1.5.3 分解关联查询
- 1.6 从MySQL执行全流程考虑性能优化
- 1.6.1 为什么查询速度会慢
- 1.6.2 查询执行的流程再回顾
- 1.6.3 MySQL客户端/服务端通信
- 1.6.4 生命周期中的查询优化处理
- 1.6.5 查询执行引擎
- 1.6.6 返回结果给客户端
- 1.6.7 查询状态
- 1.6.8 通过show profile分析SQL
- show processlist
- 通过show profile分析
本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。
MySQL性能优化其实是个很大的课题,在优化上存在着一个调优金字塔
的说法:
很明显从图上可以看出,越往上走,难度越来越高,收益却是越来越小的。比如硬件和 OS调优,需要对硬件和OS有着非常深刻的了解,例如磁盘阵列 (RAID)级别、是否可以分散磁盘IO、是否使用裸设备存放数据,使用哪种文件系统,还有操作系统的调度算法等等。
所以在进行优化时,首先需要关注和优化的应该是架构,如果架构不合理,即使是DBA 能做的事情其实是也是比较有限的。
对于架构调优,在系统设计时首先需要充分考虑业务的实际情况,是否可以把不适合数 据库做的事情放到数据仓库、搜索引擎或者缓存中去做;然后考虑写的并发量有多大, 是否需要采用分布式;最后考虑读的压力是否很大,是否需要读写分离。对于核心应用或者金融类的应用,需要额外考虑数据安全因素,数据是否不允许丢失。
对于MySQL调优,需要确认业务表结构设计是否合理,SQL语句优化是否足够,该添加的索引是否都添加了,是否可以剔除多余的索引等等。
最后对于系统、硬件上的调优系统瓶颈在哪里,哪些系统参数需要调整优 化,进程资源限制是否提到足够高;在硬件方面是否需要更换为具有更高I/O性能的存储硬件,是否需要升级内存、CPU、网络等。
知道了调优的方向,下面还是主要从mysql角度去优化。
1. 查询性能优化
1.1 什么是慢查询
慢查询日志,顾名思义,就是查询慢的日志,是指mysql记录所有执行超过long_query_time参数设定的时间阈值的SQL语句的日志。该日志能为SQL语句的优化带来很好的帮助,默认情况下,慢查询日志是关闭的,要使用慢查询日志功能,首先要开启慢查询日志功能。
优化数据访问方法论
- 业务层-请求了不需要的数据?
- 查询不需要的记录
- 总是取出全部列重复
- 查询相同的数据
- 执行层-是否在扫描额外的记录?
- 响应时间
- 扫描的行数和返回的行数
- 扫描的行数和访问类型
1.2 慢查询的配置
我们已经知道慢查询日志可以帮助定位可能存在问题的SQL语句,从而进行SQL语句层面的优化。但是默认值为关闭的OFF,需要我们手动开启ON:
show VARIABLES like 'slow_query_log';
开启:
set GLOBAL slow_query_log=1;
但是多慢算慢?MySQL中可以设定一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询日志中。long_query_time参数就是这个阈值。默认值为10,代表10秒。
show VARIABLES like '%long_query_time%';
当然也可以设置
set global long_query_time=0;---默认10秒,这里为了演示方便设置为0
设置成0秒的意思是我所有的sql语句都放到这个慢查询日志中去。
同时对于没有运行的SQL语句没有使用索引,则 MySQL数据库也可以将这条SQL语句记录到慢查询日志文件,控制参数是:
show VARIABLES like '%log_queries_not_using_indexes%";
对于产生的慢查询日志,可以指定输出的位置,通过参数 log_output来控制,可以输出到[TABLE][FILE][FILE,TABLE]。比如
set global log_output='FILE,TABLE'
缺省是输出到文件,我们的配置把慢查询输出到表,不过一般不推荐输出到表。
show VARIABLES like 'log_output';ls
1.3 慢查询解读分析
1.3.1 日志模式
开启慢查询功能以后,会根据我们的配置产生慢查询日志
从慢查询日志里面摘选一条慢查询日志,数据组成如下
“Time: 2021-04-05T07:50:53.243703Z”:查询执行时间
“User@Host: root[root] @ localhost [] Id: 3”:用户名 、用户的IP信息、线程ID号
“Query_time: 0.000495”:执行花费的时长【单位:秒】
“Lock_time: 0.000170”:执行获得锁的时长
“Rows_sent”:获得的结果行数
“Rows_examined”:扫描的数据行数
“SET timestamp”:这SQL执行的具体时间
最后一行:执行的SQL语句
1.3.2 慢查询分析
慢查询的日志记录非常多,要从里面找寻一条查询慢的日志并不是很容易的事情,一般来说都需要一些工具辅助才能快速定位到需要优化的SQL语句,下面介绍两个慢查询辅助工具:一个是mysqldumpshow,另一个直接在百度里面输入:pt-query-digest安装即可
mysqldumpshow
常用的慢查询日志分析工具,汇总除查询条件外其他完全相同的SQL,并将分析结果按照参数中所指定的顺序输出。当然它的参数不少,我们常用的也就是那么几个。
语法:
mysqldumpslow -s r -t 10 slow-mysql.log
-s order (c,t,l,r,at,al,ar)
c:总次数
t:总时间
l:锁的时间
r:获得的结果行数
at,al,ar :指t,l,r平均数 【例如:at = 总时间/总次数】
-s 对结果进行排序,怎么排,根据后面所带的 (c,t,l,r,at,al,ar),缺省为at
-t NUM just show the top n queries:仅显示前n条查询
-g PATTERN grep: only consider stmts that include this string:通过grep来筛选语句。
./mysqldumpslow -s t -t 10 /home/mysql/mysql57/data/iZwz9j203ithc4gu1uvb2wZ-slow.log
./mysqldumpslow -s t -t 10 /home/mysql/mysql57/data/iZwz9j203ithc4gu1uvb2wZ-slow.log -g select
1.4 优化SQL查询方法论
查询性能低下最基本的原因是访问的数据太多
。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效的查询,一般通过下面两个步骤来分析总是很有效:
1.确认应用程序是否在检索大量超过需要的数据。这通常意味着访问了太多的行,但有时候也可能是访问了太多的列。
2.确认MySQL服务器层是否在分析大量超过需要的数据行。
1.4.1 业务层–请求了不需要的数据?
有些查询会请求超过实际需要的数据,然后这些多余的数据会被应用程序丢弃。这会给MySQL服务器带来额外的负担,并增加网络开销,另外也会消耗应用服务器的CPU和内存资源。比如:
查询不需要的记录
例如先使用SELECT语句查询大量的结果,然后获取前面的N行后关闭结果集(例如取出100条记录,但是只是在页面上显示前面10条 limit这样的 或者count1)。
总是取出全部列
每次看到SELECT*的时候都需要用怀疑的眼光审视,是不是真的需要返回全部的列?很可能不是必需的。取出全部列,会让优化器无法完成索引覆盖扫描这类优化,还会为服务器带来额外的I/O、内存和CPU的消耗。因此,一些DBA是严格禁止SELECT *的写法的,这样做有时候还能避免某些列被修改带来的问题。
什么时候应该允许查询返回超过需要的数据?如果这种有点浪费数据库资源的方式可以简化开发,因为能提高相同代码片段的复用性,如果清楚这样做的性能影响,那么这种做法也是值得考虑的。
或者如果应用程序使用了某种缓存机制,或者有其他考虑,获取超过需要的数据也可能有其好处,但不要忘记这样做的代价是什么。获取并缓存所有的列的查询,相比多个独立的只获取部分列的查询可能就更有好处。
重复查询相同的数据
不断地重复执行相同的查询,然后每次都返回完全相同的数据。比较好的方案是,当初次查询的时候将这个数据缓存起来,需要的时候从缓存中取出,这样性能显然会更好。
1.4.2 执行层–是否在扫描额外的记录
在确定查询只返回需要的数据以后,接下来应该看看查询为了返回结果是否扫描了过多的数据。比如limit 10000,10 其实这个是查询了10010条数据。
对于MySQL,最简单的衡量查询开销的三个指标如下:
这三个指标都会记录到MySQL的慢日志中,所以检查慢日志记录是找出扫描行数过多的查询的好办法。
响应时间
响应时间是两个部分之和:服务时间和排队时间。
服务时间是指数据库处理这个查询真正花了多长时间。
排队时间是指服务器因为等待某些资源而没有真正执行查询的时间—-可能是等I/O操作完成,也可能是等待行锁,等等。
当你看到一个查询的响应时间的时候,首先需要问问自己,这个响应时间是否是一个合理的值。从我们前面章节的讲述中,我们知道如何分析一个SQL查询需要哪些索引以及它的执行计划是什么,然后计算大概需要读取多少个页面和记录数,是能够大致分析出当前响应时间是不是一个合理的值。
扫描的行数和返回的行数
分析查询时,查看该查询扫描的行数是非常有帮助的。这在一定程度上能够说明该查询找到需要的数据的效率高不高。
理想情况下扫描的行数和返回的行数应该是相同的。但实际情况中这种“美事”并不多。
例如不正确的使用Limit,在系统中需要进行分页操作的时候,我们通常会使用LIMIT加上偏移量的办法实现,同时加上合适的ORDER BY子句。
在偏移量非常大的时候,SQL语句就变成了类似select * from order_exp limit 10000,10;
这样的查询,这时MySQL需要查询10010条记录然后只返回最后10条,前面10 000条记录都将被抛弃,这样的代价非常高。这个在前面讲过使用主键id的优化,这里不再赘述(where id >10000 limit 10
)
又或者在做一个关联查询时,服务器必须要扫描多行才能生成结果集中的一行。扫描的行数对返回的行数的比率通常很小,一般在1:1和10:1之间,不过有时候这个值也可能非常非常大。
扫描的行数和访问的类型
在评估查询开销的时候,需要考虑一下从表中找到某一行数据的成本。MySQL有好几种访问方式可以查找并返回一行结果。有些访问方式可能需要扫描很多行才能返回一行结果,也有些访问方式可能无须扫描就能返回结果。
在EXPLAIN语句中的type列反应了访问类型。访问类型有很多种,从全表扫描到索引扫描、范围扫描、唯一索引查询、常数引用等,速度是从慢到快,扫描的行数也是从小到大。对其中相关的扫描表、扫描索引、范围访问和单值访问的概念要非常熟悉。
如果查询没有办法找到合适的访问类型,那么解决的最好办法通常就是增加一个合适的索引,为什么索引对于查询优化如此重要了。索引让 MySQL以最高效、扫描行数最少的方式找到需要的记录。
对于我们在SQL语句中常见的WHERE条件,一般 MySQL能够使用如下三种方式应用WHERE条件,从效率和扫描行数多少来评价的话,从好到坏依次为:
1、在索引中使用WHERE条件来过滤不匹配的记录。这是在存储引擎层完成的。
2、使用索引覆盖扫描(在Extra列中出现了Using index)来返回记录,直接从索引中过滤不需要的记录并返回命中的结果。这是在 MySQL服务器层(server)完成的,但无须再回表查询记录。
3、从数据表中返回数据,然后过滤不满足条件的记录(在Extra列中出现Using Where)。这在 MySQL服务器层完成,MySQL需要先从数据表读出记录然后过滤。
好的索引可以让查询使用合适的访问类型,尽可能地只扫描需要的数据行。
如果发现查询需要扫描大量的数据但只返回少数的行,那么通常可以尝试下面的技巧去优化它:
1、使用索引覆盖扫描,把所有需要用的列都放到索引中,这样存储引擎无须回表获取对应行就可以返回结果了(在前面的章节中我们已经讨论过了)。
2、改变库表结构。例如使用单独的汇总表。
3、重写这个复杂的查询,让 MySQL优化器能够以更优化的方式执行这个查询。
sysytem innodb达不到 myisam才有的 innodb最多到const