一、介绍
https://dev.mysql.com/doc/refman/8.0/en/slow-query-log.html
- MySQL的慢查询,全名是慢查询日志,是MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阈值的语句。
- 默认情况下,MySQL数据库并不启动慢查询日志,需要手动来设置这个参数。
- 如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件和数据库表。
二、核心参数
- 执行下面的语句
sql
mysql> show variables like '%slow_query_log%';
+---------------------+------------------------------+
| Variable_name | Value |
+---------------------+------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/test-slow.log |
+---------------------+------------------------------+
mysql> show variables like '%long_query%';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
- MySQL 慢查询的相关参数解释:
- slow_query_log :是否开启慢查询日志
ON(1)
表示开启OFF(0)
表示关闭。
- slow-query-log-file:新版(5.6及以上版本)MySQL数据库慢查询日志存储路径。
- long_query_time : 慢查询阈值,当查询时间多于设定的阈值时,记录日志。 单位-秒
- slow_query_log :是否开启慢查询日志
三、配置
- 默认情况下slow_query_log的值为OFF,表示慢查询日志是禁用的
sql
mysql> show variables like '%slow_query_log%';
+---------------------+------------------------------+
| Variable_name | Value |
+---------------------+------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/test-slow.log |
+---------------------+------------------------------+
- 可以通过设置slow_query_log的值来开启
sql
mysql> set global slow_query_log=1;
- 使用
set global slow_query_log=1
开启了慢查询日志只对当前数据库生效,MySQL重启后则会失效。如果要永久生效,就必须修改配置文件my.cnf(其它系统变量也是如此)
sql
-- 编辑配置
vim /etc/my.cnf
-- 添加如下内容
slow_query_log =1
slow_query_log_file=/var/lib/mysql/test-slow.log
-- 重启MySQL
service mysqld restart
mysql> show variables like '%slow_query%';
+---------------------+--------------------------------+
| Variable_name | Value |
+---------------------+--------------------------------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/test-slow.log |
+---------------------+--------------------------------+
- 那么开启了慢查询日志后,什么样的SQL才会记录到慢查询日志里面呢? 这个是由参数
long_query_time
控制,默认情况下long_query_time的值为10秒.
sql
mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
mysql> set global long_query_time=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
- 修改了变量long_query_time,但是查询变量long_query_time的值还是10,难道没有修改到呢?注意:使用命令 set global long_query_time=1 修改后,需要重新连接或新开一个会话才能看到修改值。
sql
mysql> show variables like 'long_query_time';
+-----------------+----------+
| Variable_name | Value |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
log_output
参数是指定日志的存储方式。log_output='FILE'
表示将日志存入文件,默认值是'FILE'。log_output='TABLE'
表示将日志存入数据库,这样日志信息就会被写入到 mysql.slow_log 表中。
sql
mysql> SHOW VARIABLES LIKE '%log_output%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_output | FILE |
+---------------+-------+
MySQL数据库支持同时两种日志存储方式,配置的时候以逗号隔开即可,如:log_output='FILE,TABLE'。日志记录到系统的专用日志表中,要比记录到文件耗费更多的系统资源,因此对于需要启用慢查询日志,又需要能够获得更高的系统性能,那么建议优先记录到文件.
- 系统变量
log-queries-not-using-indexes
:未使用索引的查询也被记录到慢查询日志中(可选项)。如果调优的话,建议开启这个选项。
sql
mysql> show variables like 'log_queries_not_using_indexes';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| log_queries_not_using_indexes | OFF |
+-------------------------------+-------+
mysql> set global log_queries_not_using_indexes=1;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'log_queries_not_using_indexes';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| log_queries_not_using_indexes | ON |
+-------------------------------+-------+
四、慢查询测试&验证
执行 test_index.sql 脚本,监控慢查询日志内容
shell
[root@localhost mysql]# tail -f /var/lib/mysql/test-slow.log
/usr/sbin/mysqld, Version: 5.7.30-log (MySQL Community Server (GPL)). started with:
Tcp port: 0 Unix socket: /var/lib/mysql/mysql.sock
Time Id Command Argument
执行下面的SQL,执行超时 (超过1秒) 我们去查看慢查询日志
sql
SELECT * FROM test_index WHERE
hobby = '20009951' OR hobby = '10009931' OR hobby = '30009931'
OR dname = 'name4000' OR dname = 'name6600' ;
日志内容分析
我们得到慢查询日志后,最重要的一步就是去分析这个日志。我们先来看下慢日志里到底记录了哪些内容。
如下图是慢日志里其中一条SQL的记录内容,可以看到有时间戳,用户,查询时长及具体的SQL等信息.
sql
# Time: 2022-02-23T13:50:45.005959Z
# User@Host: root[root] @ localhost [] Id: 3
# Query_time: 3.724273 Lock_time: 0.000371 Rows_sent: 5 Rows_examined: 5000000
SET timestamp=1645624245;
select * from test_index where hobby = '20009951' or hobby = '10009931' or hobby = '30009931' or dname = 'name4000' or dname = 'name6600';
- Time: 执行时间
- User: 用户信息 ,Id信息
- Query_time: 查询时长
- Lock_time: 等待锁的时长
- Rows_sent:查询结果的行数
- Rows_examined: 查询扫描的行数
- SET timestamp: 时间戳
- SQL的具体信息
五、慢查询 SQL 的优化思路
5.1 核心原因
在日常的运维过程中,经常会遇到DBA将一些执行效率较低的SQL发过来找开发人员分析,当我们拿到这个SQL语句之后,在对这些SQL进行分析之前,需要明确可能导致SQL执行性能下降的原因进行分析,执行性能下降可以体现在以下两个方面:
-
等待时间长
锁表导致查询一直处于等待状态,后续我们从MySQL锁的机制去分析SQL执行的原理
-
执行时间长
1.查询语句写的烂
2.索引失效
3.关联查询太多join
4.服务器调优及各个参数的设置
5.2 优化思路
优化高并发执行的SQL
优先选择优化高并发执行的SQL,因为高并发的SQL发生问题带来的后果更为严重。比如下面两种情况:
- SQL1:每小时执行10000次,每次20个IO。优化后每次18个IO,每小时节省2万次IO。
- SQL2:每小时10次,每次20000个IO。每次优化减少2000个IO,每小时节省2万次IO。
尽管SQL2更难优化,但SQL1属于高并发SQL,更急需优化,且成本更低。
定位优化对象的性能瓶颈
在优化之前,需要了解性能瓶颈所在。在优化SQL时,选择优化的方向有三个:
- IO:数据访问消耗的时间过多,查看是否正确使用了索引。
- CPU:数据运算花费的时间过多,数据的运算、分组、排序是否有问题。
- 网络带宽:是否需要加大网络带宽。
明确优化目标
根据数据库当前状态、与该条SQL的关系以及当前SQL的具体功能,明确优化目标。优化的结果应该能够给用户带来更好的体验,同时需要考虑最好和最差情况下的资源消耗。
从EXPLAIN
执行计划入手
只有EXPLAIN
能够告诉你当前SQL的执行状态,通过执行计划可以更好地理解SQL的执行过程。
永远用小的结果集驱动大的结果集
小的数据集驱动大的数据集,可以减少内层表读取的次数,从而提高性能。
例如,嵌套循环中,如果小的循环在外层,则数据库连接只会发生5次,进行5000次操作,相比之下,如果大的循环在外层,则会导致1000次数据库连接,增加资源消耗。
尽可能在索引中完成排序
排序操作较为常见,如果排序字段在索引中,速度会更快,因为索引本身就是排好序的。否则,需要从表中获取数据,在内存中进行排序,可能会涉及到磁盘IO操作。
只获取自己需要的列
避免使用SELECT *
,因为SELECT *
很可能不会使用索引,并且会增加数据传输的开销。
只使用最有效的过滤条件
避免使用不必要的过滤条件,应该选择最短的路径访问数据,以提高查询效率。
尽可能避免复杂的JOIN和子查询
复杂的JOIN操作会增加资源消耗,建议每条SQL的JOIN操作不要超过三张表。可以将复杂的SQL拆分成多个小的SQL单独执行,并在程序中进行结果封装。
合理设计并利用索引
如何判定是否需要创建索引?
- 较为频繁作为查询条件的字段应该创建索引。
- 唯一性较差的字段不适合创建索引,即使频繁作为查询条件。唯一性较差的字段指的是数据重复度较高的字段,如状态字段、类型字段等。
- 更新非常频繁的字段不适合创建索引,因为更新索引会增加额外开销。
- 不会出现在WHERE子句中的字段不应该创建索引。
如何选择合适的索引?
- 对于单键索引,选择过滤性更好的索引。
- 对于联合索引,过滤性最好的字段应该排在索引字段顺序的前面。
shell
count(distinct column)/count(*)
shell
select something from order_table where user_id='1234' order by id limit 23000,100;
改写语句
plsql
select a.something from order_table a,
(select id from order_table where user_id='1234' order by id limit 23000,100) b
where a.id=b.id
order by a.id;