MySQL慢查询定位及其优化建议

本文最近更新于2021.11.23

(一)慢查询配置

慢查询的用途是用来发现执行时间长的查询语句,以便对这些语句进行优化

[mysqld]              #在这里面增加,其它地方无效
#server-id=1
#log-bin=master-bin
slow_query_log=1      #开启查询
slow_query_log_file=/bp/mysql.log       
#慢查询日志文件存放位置,注意这个比较坑,要求目标目录可写,最好还是自己创建一个目录,并-R 添加写权限,这个是网上说的,不过我实验时发现赋777权限才可以
long_query_time=1                       #定义超过1秒就算是慢查询,一般是5到10秒吧
log_queries_not_using_indexes = ON    #记录没有使用索引的查询

重启服务

MariaDB [(none)]> show variables like '%slow%';
+---------------------+--------------------------------------------------------------------------------------------------------------+
| Variable_name       | Value                                                                                                        |
+---------------------+--------------------------------------------------------------------------------------------------------------+
| log_slow_filter     | admin,filesort,filesort_on_disk,full_join,full_scan,query_cache,query_cache_miss,tmp_table,tmp_table_on_disk |
| log_slow_queries    | ON                #开启成功                                                                                           |
| log_slow_rate_limit | 1                                                                                                            |
| log_slow_verbosity  |                                                                                                              |
| slow_launch_time    | 2                                                                                                            |
| slow_query_log      | ON                                                                                                           |
| slow_query_log_file | /bp/mysql.log                                                                                                |
+---------------------+--------------------------------------------------------------------------------------------------------------+
7 rows in set (0.00 sec)

MariaDB [(none)]> 

(二)查看慢查询

那么怎么测试慢查询是否成功呢
比较简单的一种方法就是select sleep(5);这条语句会执行5秒钟
看一下慢查询日志文件

终端中执行以下命令
mysqldumpslow -t 10 查看访问时间最长的10个sql语句

mysqldumpslow -s c -t 10 查看访问次数最多的10个sql语句

(三)慢查询定位

这里简单说下三种方法

1.explain
MySQL自带命令,可用于对select语句的执行计划进行分析,并详细输出分析结果。例如SQL有没有用上索引,有没有全表查询,都可以通过explain查看(但不会执行这条SQL)。
使用方法,只需要在查询语句前加explain关键字即可。

type
表示MySQL在表中找到所需行的方式,又称“访问类型”,常见类型从最好到最差依次如下:
system>const>eq_ref>ref>range>index>All
一般来说,得保证查询达到range级别,最好达到ref。
system是const的特列,表中只有一行元素匹配时为system
const指常量查询,在整个查询过程中个,该表最多有一个匹配行,当使用主键或唯一索引的所有部分与常量值进行比较时,将使用const。
eq_ref:primay key或 unique key索引的所有部分被连接使用,最多只会返回一条符合条件的记录。这可能是const之外最好的联接类型,简单的select查询不会出现这种type。
ref:相比eq_ref,不适用唯一索引,而是使用普通索引或者唯一索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。简单select查询,name是普通索引(非主键索引或唯一索引)
range:范围扫描通常出现在in(), between,>,<,>=等操作中。使用一个索引来检索给定范围的行。
index:扫描全表索引,通常比All快一些
all:即全表扫描,意味着MySQL需要从头到尾去查找所需要的行。这种情况下需要增加索引来进行优化。

2.show profile
show profile是会话级别的,进入会话后执行set profiling=on;启用show profile,退出会话后会自动关闭show profile

[root@localhost h]# mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.36-log MySQL Community Server (GPL)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show variables like 'profiling';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| profiling     | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

mysql> set profiling=on;  #启动profiling
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show variables like 'profiling';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| profiling     | ON    |
+---------------+-------+
1 row in set (0.00 sec)

mysql> select user from mysql.user;
+------+
| user |
+------+
| root |
+------+
1 row in set (0.00 sec)

mysql> show profiles;  #默认开查看启动profiling后的最近15条SQL执行记录,可通过profiling_history_size修改,最大为100
+----------+------------+---------------------------------+
| Query_ID | Duration   | Query                           |
+----------+------------+---------------------------------+
|        1 | 0.00104075 | show variables like 'profiling' |
|        2 | 0.00026425 | select user from mysql.user     |
+----------+------------+---------------------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> show profile all for query 1;  #查看某一条SQL具体资源消耗信息,格式为show profile all for query id;
+----------------------+----------+----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+----------------------+-------------+
| Status               | Duration | CPU_user | CPU_system | Context_voluntary | Context_involuntary | Block_ops_in | Block_ops_out | Messages_sent | Messages_received | Page_faults_major | Page_faults_minor | Swaps | Source_function       | Source_file          | Source_line |
+----------------------+----------+----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+----------------------+-------------+
| starting             | 0.000066 | 0.000005 |   0.000056 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | NULL                  | NULL                 |        NULL |
| checking permissions | 0.000014 | 0.000001 |   0.000013 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | check_access          | sql_authorization.cc |         809 |
| Opening tables       | 0.000016 | 0.000001 |   0.000014 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | open_tables           | sql_base.cc          |        5815 |
| init                 | 0.000044 | 0.000003 |   0.000041 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | handle_query          | sql_select.cc        |         128 |
| System lock          | 0.000007 | 0.000001 |   0.000006 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_lock_tables     | lock.cc              |         330 |
| optimizing           | 0.000002 | 0.000000 |   0.000002 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         158 |
| optimizing           | 0.000002 | 0.000000 |   0.000002 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         158 |
| statistics           | 0.000010 | 0.000001 |   0.000009 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         374 |
| preparing            | 0.000011 | 0.000001 |   0.000010 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         482 |
| statistics           | 0.000009 | 0.000000 |   0.000008 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         374 |
| preparing            | 0.000004 | 0.000000 |   0.000003 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc     |         482 |
| executing            | 0.000006 | 0.000001 |   0.000006 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | exec                  | sql_executor.cc      |         126 |
| Sending data         | 0.000005 | 0.000000 |   0.000004 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | exec                  | sql_executor.cc      |         202 |
| executing            | 0.000002 | 0.000000 |   0.000002 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | exec                  | sql_executor.cc      |         126 |
| Sending data         | 0.000798 | 0.000801 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | exec                  | sql_executor.cc      |         202 |
| end                  | 0.000007 | 0.000004 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | handle_query          | sql_select.cc        |         206 |
| query end            | 0.000005 | 0.000005 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_execute_command | sql_parse.cc         |        4959 |
| closing tables       | 0.000003 | 0.000002 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_execute_command | sql_parse.cc         |        5018 |
| removing tmp table   | 0.000005 | 0.000005 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | free_tmp_table        | sql_tmp_table.cc     |        2420 |
| closing tables       | 0.000005 | 0.000005 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | free_tmp_table        | sql_tmp_table.cc     |        2449 |
| freeing items        | 0.000013 | 0.000013 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_parse           | sql_parse.cc         |        5637 |
| cleaning up          | 0.000011 | 0.000010 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | dispatch_command      | sql_parse.cc         |        1933 |
+----------------------+----------+----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+----------------------+-------------+
22 rows in set, 1 warning (0.00 sec)

mysql> 

Duration 持续时间(耗时)
Block io 指块IO开销
Context Switches 指上下文切换开销
Cpu 指CPU开销信息

3.Performance schema
在MySQL5.7中,performance schema有很大改进,包括引入大量新加入的监控项、降低占用空间和负载,以及通过新的sys schema机制显著提升易用性。
常用SQL如下:

---查看表的访问量:(可以监控每张表访问量的情况,或者监控某个库的访问量的变化)
select table_schema,table_name,sum(io_read_requests+io_write_requests) from schema_table_statistics;
select table_schema,table_name,io_read_requests+io_write_requests as io_total from schema_table_statistics;
---冗余索引和未使用索引的检查:(schema_redundant_indexes和schema_unused_indexes查看索引的情况)
select * from sys.schema_redundant_indexes\G
select * from sys.schema_unused_indexes;
(如果有冗余索引和长期未使用的索引,应该及时清理,)
---查看表自增ID使用情况:
select * from schema_auto_increment_columns\G 
(可以使用schema_auto_increment_columns视图,就能很简单的查到每个表的自增量使用情况,甚至可以精确到某个表的自增量情况)
---监控全表扫描的sql语句:
select * from sys.statements_with_full_table_scans where db='test2'\G
(使用statements_with_full_table_scans视图可以查看哪些表查询使用了全表扫描,其中exec_count是执行的次数,等信息)
---查看实例消耗的磁盘I/O情况:()
select file,avg_read+avg_write as avg_io from io_global_by_file_by_bytes order by avg_io desc limit 10;
(查看io_global_by_file_by_bytes视图可以检查磁盘I/O消耗过大的原因,定位问题)

参考连接:
http://www.deiniu.com/article/120164.htm
https://www.pianshen.com/article/8994646542/

(四)慢查询优化

1.只列出需要用到的字段,不要列出所有字段,以节省内存
2.避免使用NULL,使用该NULL会导致该字段无法命中索引
3.使用count(*),而不是count(某列),后者会导致判断值是不是null,会返回非null数据,增加判断成本
4.使用like查询,尽量避免不再字段前加%("%mysql"),可以在后面带%("mysql%"),前%不走索引
5.添加合理索引,减少磁盘IO。例如查询常用列,表数据大于1万,同时建立索引的字段长度应尽量小,并且该列数据越少重复越好
6.避免在where子句中使用!=或<>或OR
7.字段多的标分解成多个标,有些字段使用频率很低,可以将这些字段分离出来形成新表。当表数据量大时,低频自读的存在会导致查询变慢
8.增加中间表。对于需要经常联合查询的表,可以建立中间表,把需要联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表查询,已提高查询效率
9.分解关联查询。将复杂的关联查询进行分解,对每个表进行一次单表查询,然后将查询结果在应用程序中关联,很多场景下这样会更高效。把涉及左右连接等复杂查询,分解为简单select查询,查询结果在应用中传递即可

posted on 2017-10-25 18:06  标配的小号  阅读(382)  评论(0编辑  收藏  举报

导航