MySQL自带性能监控工具
MySQL自带了一些工具可以分析SQL执行的情况,这里简单总结一下常用的工具。
References
- 《高性能MySQL 第3版》
- https://dev.mysql.com/doc/refman/8.0/en/show-profile.html
- https://dev.mysql.com/doc/refman/8.0/en/performance-schema.html
这些东西资料比较少,网上的文章也大多是摘录书中的,因此最新的尽量以官网为准。
- SHOW PROFILE
SHOW PROFILE命令是在MySQL 5.1以后的版本中引入的,默认是禁用的,但可以通过服务器变量在会话(连接)级别动态地修改。
执行:
mysql> set profiling = 1;
然后,在服务器上执行的所有语句,都会测量其耗费的时间和其他一些查询执行状态变更相关的数据。这个功能有一定的作用,而且最初的设计功能更强大,但未来版本中可能会被Performance Schema所取代。尽管如此,这个工具最有用的作用还是在语句执行期间剖析服务器的具体工作。
当一条查询提交给服务器时,此工具会记录剖析信息到一张临时表,并且给查询赋予一个从1开始的整数标识符。
如下所示,在开启profile后执行了3条查询SQL,查询profile信息即可得到SQL执行耗费的时间。
mysql> show profiles;
+----------+------------+----------------------------+
| Query_ID | Duration | Query |
+----------+------------+----------------------------+
| 1 | 0.00024350 | select * from balance |
| 2 | 0.00215230 | select * from user |
| 3 | 0.00028760 | select * from cst_customer |
+----------+------------+----------------------------+
3 rows in set (0.000 sec)
如需对某一条SQL详细分析,则可使用对应的Query_ID查询,也可以添加要profile的类型:
mysql> show profile for query 2;
+------------------------+----------+
| Status | Duration |
+------------------------+----------+
| Starting | 0.000050 |
| checking permissions | 0.000006 |
| Opening tables | 0.000019 |
| After opening tables | 0.000005 |
| System lock | 0.000004 |
| table lock | 0.000007 |
| Opening tables | 0.000016 |
| After opening tables | 0.000003 |
| System lock | 0.000003 |
| table lock | 0.000023 |
| Unlocking tables | 0.000003 |
| closing tables | 0.000010 |
| init | 0.000016 |
| Optimizing | 0.000007 |
| Statistics | 0.000012 |
| Preparing | 0.000012 |
| Executing | 0.000003 |
| Sending data | 0.001883 |
| End of update loop | 0.000010 |
| Query end | 0.000003 |
| Commit | 0.000005 |
| closing tables | 0.000003 |
| Unlocking tables | 0.000003 |
| closing tables | 0.000007 |
| Starting cleanup | 0.000004 |
| Freeing items | 0.000006 |
| Updating status | 0.000028 |
| Reset for next command | 0.000003 |
+------------------------+----------+
28 rows in set (0.000 sec)
mysql> show profile cpu for query 2;
+------------------------+----------+----------+------------+
| Status | Duration | CPU_user | CPU_system |
+------------------------+----------+----------+------------+
| Starting | 0.000050 | 0.000000 | 0.000000 |
| checking permissions | 0.000006 | 0.000000 | 0.000000 |
| Opening tables | 0.000019 | 0.000000 | 0.000000 |
| After opening tables | 0.000005 | 0.000000 | 0.000000 |
| System lock | 0.000004 | 0.000000 | 0.000000 |
| table lock | 0.000007 | 0.000000 | 0.000000 |
| Opening tables | 0.000016 | 0.000000 | 0.000000 |
| After opening tables | 0.000003 | 0.000000 | 0.000000 |
| System lock | 0.000003 | 0.000000 | 0.000000 |
| table lock | 0.000023 | 0.000000 | 0.000000 |
| Unlocking tables | 0.000003 | 0.000000 | 0.000000 |
| closing tables | 0.000010 | 0.000000 | 0.000000 |
| init | 0.000016 | 0.000000 | 0.000000 |
| Optimizing | 0.000007 | 0.000000 | 0.000000 |
| Statistics | 0.000012 | 0.000000 | 0.000000 |
| Preparing | 0.000012 | 0.000000 | 0.000000 |
| Executing | 0.000003 | 0.000000 | 0.000000 |
| Sending data | 0.001883 | 0.000000 | 0.000000 |
| End of update loop | 0.000010 | 0.000000 | 0.000000 |
| Query end | 0.000003 | 0.000000 | 0.000000 |
| Commit | 0.000005 | 0.000000 | 0.000000 |
| closing tables | 0.000003 | 0.000000 | 0.000000 |
| Unlocking tables | 0.000003 | 0.000000 | 0.000000 |
| closing tables | 0.000007 | 0.000000 | 0.000000 |
| Starting cleanup | 0.000004 | 0.000000 | 0.000000 |
| Freeing items | 0.000006 | 0.000000 | 0.000000 |
| Updating status | 0.000028 | 0.000000 | 0.000000 |
| Reset for next command | 0.000003 | 0.000000 | 0.000000 |
+------------------------+----------+----------+------------+
28 rows in set (0.000 sec)
剖析报告给出了查询执行的每个步骤及其花费的时间,看结果很难快速地确定哪个步骤花费的时间最多。因为输出是按照执行顺序排序,而不是按花费的时间排序的——而实际上我们更关心的是花费了多少时间,这样才能知道哪些开销比较大。但不幸的是无法通过诸如ORDER BY之类的命令重新排序。如果要按照花费大小排序,就不能使用show prifile命令,而是直接使用INFORMATION_SCHEMA中对应的表。
mysql> set @query_id = 2;
Query OK, 0 rows affected (0.001 sec)
mysql> SELECT STATE,
-> SUM(DURATION) AS Total_R,
-> ROUND(100 * SUM(DURATION) /
-> (SELECT SUM(DURATION)
-> FROM INFORMATION_SCHEMA.PROFILING
-> WHERE QUERY_ID = @query_id),
-> 2) AS Pct_R,
-> COUNT(*) AS Calls,
-> SUM(DURATION) / COUNT(*) AS "R/Call"
-> FROM INFORMATION_SCHEMA.PROFILING
-> WHERE QUERY_ID = @query_id
-> GROUP BY STATE
-> ORDER BY Total_R DESC;
+------------------------+----------+-------+-------+--------------+
| STATE | Total_R | Pct_R | Calls | R/Call |
+------------------------+----------+-------+-------+--------------+
| Sending data | 0.001883 | 87.42 | 1 | 0.0018830000 |
| Starting | 0.000050 | 2.32 | 1 | 0.0000500000 |
| Opening tables | 0.000035 | 1.62 | 2 | 0.0000175000 |
| table lock | 0.000030 | 1.39 | 2 | 0.0000150000 |
| Updating status | 0.000028 | 1.30 | 1 | 0.0000280000 |
| closing tables | 0.000020 | 0.93 | 3 | 0.0000066667 |
| init | 0.000016 | 0.74 | 1 | 0.0000160000 |
| Preparing | 0.000012 | 0.56 | 1 | 0.0000120000 |
| Statistics | 0.000012 | 0.56 | 1 | 0.0000120000 |
| End of update loop | 0.000010 | 0.46 | 1 | 0.0000100000 |
| After opening tables | 0.000008 | 0.37 | 2 | 0.0000040000 |
| System lock | 0.000007 | 0.32 | 2 | 0.0000035000 |
| Optimizing | 0.000007 | 0.32 | 1 | 0.0000070000 |
| checking permissions | 0.000006 | 0.28 | 1 | 0.0000060000 |
| Unlocking tables | 0.000006 | 0.28 | 2 | 0.0000030000 |
| Freeing items | 0.000006 | 0.28 | 1 | 0.0000060000 |
| Commit | 0.000005 | 0.23 | 1 | 0.0000050000 |
| Starting cleanup | 0.000004 | 0.19 | 1 | 0.0000040000 |
| Executing | 0.000003 | 0.14 | 1 | 0.0000030000 |
| Reset for next command | 0.000003 | 0.14 | 1 | 0.0000030000 |
| Query end | 0.000003 | 0.14 | 1 | 0.0000030000 |
+------------------------+----------+-------+-------+--------------+
21 rows in set (0.001 sec)
效果好多了!通过这个结果可以很容易看到查询时间主要消耗在哪些操作上。尽管剖析报告能帮助我们定位到哪些活动花费了最多的时间,但并不会告诉我们为什么会这样。要弄清楚为什么复制数据到临时表要花费这么多时间,就需要深入下去。
注:在MySQL官网上已将SHOW PROFILE
和SHOW PROFILES
标记为deprecated,可能会在未来的版本中移除,替代品是Performance Schema。
- Performance Schema
提供了一种在数据库运行时实时检查server的内部执行情况的方法。performance_schema 数据库中的表使用performance_schema存储引擎。该数据库主要关注数据库运行过程中的性能相关的数据,与information_schema不同,information_schema主要关注server运行过程中的元数据信息。
在mysql的5.7版本中,性能模式是默认开启的,如果想要显式的关闭的话需要修改配置文件,不能直接进行修改,会报错Variable 'performance_schema' is a read only variable。
-- 查看performance_schema的属性
mysql> SHOW VARIABLES LIKE 'performance_schema';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| performance_schema | ON |
+--------------------+-------+
1 row in set (0.01 sec)
-- 在配置文件中修改performance_schema的属性值,on表示开启,off表示关闭
[mysqld]
performance_schema=ON
-- 切换数据库
use performance_schema;
-- 查看当前数据库下的所有表,会看到有很多表存储着相关的信息
show tables;
-- 可以通过show create table tablename来查看创建表的时候的表结构
mysql> show create table setup_consumers;
+-----------------+---------------------------------
| Table | Create Table
+-----------------+---------------------------------
| setup_consumers | CREATE TABLE `setup_consumers` (
`NAME` varchar(64) NOT NULL,
`ENABLED` enum('YES','NO') NOT NULL
) ENGINE=PERFORMANCE_SCHEMA DEFAULT CHARSET=utf8 |
+-----------------+---------------------------------
1 row in set (0.00 sec)
数据库刚刚初始化并启动时,并非所有instruments(事件采集项,在采集项的配置表中每一项都有一个开关字段,或为YES,或为NO)和consumers(与采集项类似,也有一个对应的事件类型保存表配置项,为YES就表示对应的表保存性能数据,为NO就表示对应的表不保存性能数据)都启用了,所以默认不会收集所有的事件,可能你需要检测的事件并没有打开,需要进行设置,可以使用如下两个语句打开对应的instruments和consumers(行计数可能会因MySQL版本而异)。
-- 打开等待事件的采集器配置项开关,需要修改setup_instruments配置表中对应的采集器配置项
UPDATE setup_instruments SET ENABLED = 'YES', TIMED = 'YES'where name like 'wait%';
-- 打开等待事件的保存表配置开关,修改setup_consumers配置表中对应的配置项
UPDATE setup_consumers SET ENABLED = 'YES'where name like '%wait%';
-- 当配置完成之后可以查看当前server正在做什么,可以通过查询events_waits_current表来得知,该表中每个线程只包含一行数据,用于显示每个线程的最新监视事件
select * from events_waits_current\G
*************************** 1. row ***************************
THREAD_ID: 11
EVENT_ID: 570
END_EVENT_ID: 570
EVENT_NAME: wait/synch/mutex/innodb/buf_dblwr_mutex
SOURCE:
TIMER_START: 4508505105239280
TIMER_END: 4508505105270160
TIMER_WAIT: 30880
SPINS: NULL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
INDEX_NAME: NULL
OBJECT_TYPE: NULL
OBJECT_INSTANCE_BEGIN: 67918392
NESTING_EVENT_ID: NULL
NESTING_EVENT_TYPE: NULL
OPERATION: lock
NUMBER_OF_BYTES: NULL
FLAGS: NULL
/*该信息表示线程id为11的线程正在等待buf_dblwr_mutex锁,等待事件为30880
属性说明:
id:事件来自哪个线程,事件编号是多少
event_name:表示检测到的具体的内容
source:表示这个检测代码在哪个源文件中以及行号
timer_start:表示该事件的开始时间
timer_end:表示该事件的结束时间
timer_wait:表示该事件总的花费时间
注意:_current表中每个线程只保留一条记录,一旦线程完成工作,该表中不会再记录该线程的事件信息
*/
/*
_history表中记录每个线程应该执行完成的事件信息,但每个线程的事件信息只会记录10条,再多就会被覆盖,*_history_long表中记录所有线程的事件信息,但总记录数量是10000,超过就会被覆盖掉
*/
select thread_id,event_id,event_name,timer_wait from events_waits_history order by thread_id limit 21;
/*
summary表提供所有事件的汇总信息,该组中的表以不同的方式汇总事件数据(如:按用户,按主机,按线程等等)。例如:要查看哪些instruments占用最多的时间,可以通过对events_waits_summary_global_by_event_name表的COUNT_STAR或SUM_TIMER_WAIT列进行查询(这两列是对事件的记录数执行COUNT(*)、事件记录的TIMER_WAIT列执行SUM(TIMER_WAIT)统计而来)
*/
SELECT EVENT_NAME,COUNT_STAR FROM events_waits_summary_global_by_event_name ORDER BY COUNT_STAR DESC LIMIT 10;
/*
instance表记录了哪些类型的对象会被检测。这些对象在被server使用时,在该表中将会产生一条事件记录,例如,file_instances表列出了文件I/O操作及其关联文件名
*/
select * from file_instances limit 20;
- SHOW PROCESSLIST
使用SHOW PROCESSLIST查看连接的线程个数,来观察是否有大量线程处于不正常的状态或者其他不正常的特征。例如查询很少会长时间处于“statistics”状态,这个状态一般是指服务器在查询优化阶段如何确定表关联的顺序——通常都是非常快的。另外,也很少会见到大量线程报告当前连接用户是“未经验证的用户(Unauthenticated user)”,这只是在连接握手的中间过程中的状态,当客户端等待输入用于登录的用户信息的时候才会出现。
使用SHOW PROCESSLIST命令时,在尾部加上\G可以垂直的方式输出结果,这样会将每一行记录的每一列都单独输出为一行:
mysql> show processlist;
+----+------+-----------------+--------------------+---------+------+----------+------------------+----------+
| Id | User | Host | db | Command | Time | State | Info | Progress |
+----+------+-----------------+--------------------+---------+------+----------+------------------+----------+
| 3 | root | localhost:56662 | test | Sleep | 8325 | | NULL | 0.000 |
| 4 | root | localhost:56664 | test | Sleep | 8325 | | NULL | 0.000 |
| 5 | root | localhost:56700 | test | Sleep | 8336 | | NULL | 0.000 |
| 6 | root | localhost:56712 | performance_schema | Query | 0 | starting | show processlist | 0.000 |
+----+------+-----------------+--------------------+---------+------+----------+------------------+----------+
4 rows in set (0.000 sec)
mysql> show processlist\G;
*************************** 1. row ***************************
Id: 3
User: root
Host: localhost:56662
db: test
Command: Sleep
Time: 8387
State:
Info: NULL
Progress: 0.000
*************************** 2. row ***************************
Id: 4
User: root
Host: localhost:56664
db: test
Command: Sleep
Time: 8387
State:
Info: NULL
Progress: 0.000
*************************** 3. row ***************************
Id: 5
User: root
Host: localhost:56700
db: test
Command: Sleep
Time: 8397
State:
Info: NULL
Progress: 0.000
*************************** 4. row ***************************
Id: 6
User: root
Host: localhost:56712
db: performance_schema
Command: Query
Time: 0
State: starting
Info: show processlist
Progress: 0.000
4 rows in set (0.000 sec)
ERROR: No query specified
属性 | 含义 |
---|---|
id | session id |
User | 操作的用户 |
Host | 操作的主机 |
db | 操作的数据库 |
Command | 命令类型 |
Info | 详细的sql语句 |
Time | 命令执行时间 |
State | 命令执行状态 |
一般项目都会使用数据库连接池来控制连接。