该篇文章主要介绍 MySQL 中内置的一些监控工具
Show Profile:监控语句各个阶段所消耗的情况,便于 SQL 调优的测量
Show Processlist:用于监控数据库的连接信息
Performance Schema:通过监视 server 事件来实现监视 server 内部运行情况
Explain:如何知晓 SQL 语句的执行结果,需要查看 SQL 语句的具体执行流程,例如:表中的索引是否正常运用,查询筛选的行数有多少等
MySQL 官网 sakila 库脚本及数据下载:sakila 库,以下所有 SQL 测试都以该库数据为例.
MySQL 官网链接:https://dev.mysql.com/doc/refman/5.7/en/show-profile.html
如何查看 SQL 具体执行时间,步骤如下:
show profile for query 展示的是上面的 Query_ID 对应的值…
通过上图可以看出,第一个开始运行时,starting 浪费时间比较多,第二个就是我们在发送数据的时候,浪费时间也比较多,其他时间都是比较少的,如果行数据开始变多时,较多的时长会在 executing.
摘至 MySQL 官网:
SHOW PROFILE [type [, type] ... ]
[FOR QUERY n]
[LIMIT row_count [OFFSET offset]]
type: {
ALL:显示所有性能信息—>show profile all for query n
| BLOCK IO:显示块 IO 操作次数—>show profile block io for query n
| CONTEXT SWITCHES:显示上下文切换次数,被动和主动—>show profile context switches for query n
| CPU:显示用户 CPU 时间、系统 CPU 系统—> show profile cpu for query n
| IPC:显示发送和接受的消息数量—>show profile ipc for query n
| MEMORY:暂未实现
| PAGE FAULTS:显示页错误数量—>show profile page faults for query n
| SOURCE:显示源码中函数名称与位置—>show profile source for query n
| SWAPS:显示 swap 次数—>show profile swaps for query n
}
在低版本 MySQL 还可以使用这样的一些 profile 属性,目前包括 8 版本里面也可以进行使用,但官方有下面那一句非常重要的话
Note
The SHOW PROFILE and SHOW PROFILES statements are deprecated; expect them to be removed in a future MySQL release. Use the Performance Schema instead; see Section 25.19.1, “Query Profiling Using Performance Schema”.
大致意思:show profile 慢慢会被丢弃,甚至在未来的 MySQL 版本可能就没有了,使用 performance schema 取而代之
注意:show profile 在当前会话中生效可使用,若当前会话关闭以后,分析的信息将会消失掉.
官网:https://dev.mysql.com/doc/refman/5.7/en/show-processlist.html
除了可以监控 MySQL 性能之外,还有一个非常重要的点,就是监控我们数据库的连接,执行如下命令:show processlist;
Id 是编号,User 是说明使用者,Host 表示访问的 IP 地址,db 表示访问的是哪个数据库,command 是运行什么样的命令:
Time 表示运行时间,状态 State,Info 表示一些详细信息
Performance Schema(性能模块) 官网:https://dev.mysql.com/doc/refman/5.7/en/performance-schema.html
Performance 介绍:主要是用来监控我们的数据库,执行在一个比较低的级别里面,Performance Schema 开启还是有必要的,虽然开启之后会消耗一部分系统资源,但是损失一点系统资源后面可以更快的定位到问题.
Performance 特征
”检测点“
来实现事件数据的收集,对于 performance_schema 实现机制本身的代码没有相关的单独线程来检测,这与其他功能(如复制或事件计划程序)不同在 MySQL 5.7 版本中,性能模式是默认开启的,如果想要显示关闭的话需要修改配置文件,不能直接修改,会报错
在配置文件中修改 performance_schema 属性值,ON 表示开启,OFF 表示关闭
[mysqld]
performance_schema=ON
查看 performance_schema 属性
mysql> SHOW VARIABLES LIKE 'performance_schema';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| performance_schema | ON |
+--------------------+-------+
查看创建表时的表结构
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)
两个基础概念
performance_schema 库下的表可以按照监视不同的维度进行分组
语句事件记录表:这些表记录了语句事件信息
summary
,summary 表还可以根据账号(account)、主机(host)、程序(program)、线程(thread)、用户(user)和全局(global)再进行细分mysql> show tables like '%statement%';
+----------------------------------------------------+
| Tables_in_performance_schema (%statement%) |
+----------------------------------------------------+
| events_statements_current |
| events_statements_history |
| events_statements_history_long |
| events_statements_summary_by_account_by_event_name |
| events_statements_summary_by_digest |
| events_statements_summary_by_host_by_event_name |
| events_statements_summary_by_program |
| events_statements_summary_by_thread_by_event_name |
| events_statements_summary_by_user_by_event_name |
| events_statements_summary_global_by_event_name |
| prepared_statements_instances |
+----------------------------------------------------+
11 rows in set (0.00 sec)
等待事件记录表:与语句事件类型相关记录表类似
mysql> show tables like '%wait%';
+-----------------------------------------------+
| Tables_in_performance_schema (%wait%) |
+-----------------------------------------------+
| events_waits_current |
| events_waits_history |
| events_waits_history_long |
| events_waits_summary_by_account_by_event_name |
| events_waits_summary_by_host_by_event_name |
| events_waits_summary_by_instance |
| events_waits_summary_by_thread_by_event_name |
| events_waits_summary_by_user_by_event_name |
| events_waits_summary_global_by_event_name |
| table_io_waits_summary_by_index_usage |
| table_io_waits_summary_by_table |
| table_lock_waits_summary_by_table |
+-----------------------------------------------+
12 rows in set (0.00 sec)
阶段事件记录表:记录语句执行的阶段事件表
mysql> show tables like '%stage%';
+------------------------------------------------+
| Tables_in_performance_schema (%stage%) |
+------------------------------------------------+
| events_stages_current |
| events_stages_history |
| events_stages_history_long |
| events_stages_summary_by_account_by_event_name |
| events_stages_summary_by_host_by_event_name |
| events_stages_summary_by_thread_by_event_name |
| events_stages_summary_by_user_by_event_name |
| events_stages_summary_global_by_event_name |
+------------------------------------------------+
8 rows in set (0.00 sec)
事务事件记录表:记录事务相关的事件表
mysql> show tables like '%transaction%';
+------------------------------------------------------+
| Tables_in_performance_schema (%transaction%) |
+------------------------------------------------------+
| events_transactions_current |
| events_transactions_history |
| events_transactions_history_long |
| events_transactions_summary_by_account_by_event_name |
| events_transactions_summary_by_host_by_event_name |
| events_transactions_summary_by_thread_by_event_name |
| events_transactions_summary_by_user_by_event_name |
| events_transactions_summary_global_by_event_name |
+------------------------------------------------------+
8 rows in set (0.00 sec)
监控文件系统层调用的表
mysql> show tables like '%file%';
+---------------------------------------+
| Tables_in_performance_schema (%file%) |
+---------------------------------------+
| file_instances |
| file_summary_by_event_name |
| file_summary_by_instance |
+---------------------------------------+
3 rows in set (0.00 sec)
监视内存使用的表
mysql> show tables like '%memory%';
+-----------------------------------------+
| Tables_in_performance_schema (%memory%) |
+-----------------------------------------+
| memory_summary_by_account_by_event_name |
| memory_summary_by_host_by_event_name |
| memory_summary_by_thread_by_event_name |
| memory_summary_by_user_by_event_name |
| memory_summary_global_by_event_name |
+-----------------------------------------+
5 rows in set (0.00 sec)
动态对 performance_schema 进行配置的配置表
mysql> show tables like '%setup%';
+----------------------------------------+
| Tables_in_performance_schema (%setup%) |
+----------------------------------------+
| setup_actors |
| setup_consumers |
| setup_instruments |
| setup_objects |
| setup_timers |
+----------------------------------------+
5 rows in set (0.00 sec)
虽然说 performance_schema 服务默认情况下是开启的,但是它里面的一些属性并不是开启的,执行如下语句
SELECT * FROM setup_instruments;
ENABLED 代表这个属性是否开启,TIMED 代表计时器是否开启,为了能够完整地监控我们的信息,都把这些开启一下
打开等待事件的采集器配置项开关,需要修改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 表来得知,该表中每个线程只包含一行数据,用于显示每个线程的最新监视事件
mysql> select * from events_waits_current\G
*************************** 1. row ***************************
THREAD_ID: 13
EVENT_ID: 485
END_EVENT_ID: 485
EVENT_NAME: wait/synch/mutex/innodb/buf_dblwr_mutex
SOURCE:
TIMER_START: 6364820648454270
TIMER_END: 6364820648844465
TIMER_WAIT: 390195
SPINS: NULL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
INDEX_NAME: NULL
OBJECT_TYPE: NULL
OBJECT_INSTANCE_BEGIN: 60264520
NESTING_EVENT_ID: NULL
NESTING_EVENT_TYPE: NULL
OPERATION: lock
NUMBER_OF_BYTES: NULL
FLAGS: NULL
该信息表示线程id为 13 的线程正在等待 buf_dblwr_mutex 锁,等待时间为 30880
属性说明:
id:事件来自哪个线程,事件编号是多少
event_name:表示检测到的具体的内容
source:表示这个检测代码在哪个源文件中以及行号
timer_start:表示该事件的开始时间
timer_end:表示该事件的结束时间
timer_wait:表示该事件总的花费时间
注意:_current 表中每个线程只保留一条记录,一旦线程完成工作,该表中不会再记录该线程的事件信息
select thread_id,event_id,event_name,timer_wait from events_waits_history order by thread_id limit 21;
history 表中记录每个线程应该执行完成的事件信息,但每个线程的事件信息只会记录 10 条,再多就会被覆盖,*_history_long表中记录所有线程的事件信息,但总记录数量是 10000,超过就会被覆盖掉
SELECT EVENT_NAME,COUNT_STAR FROM events_waits_summary_global_by_event_name ORDER BY COUNT_STAR DESC LIMIT 10;
summary 表提供所有事件的汇总信息,该组中的表以不同的方式汇总事件数据(如:按用户,按主机,按线程等等)。例如:要查看哪些 instruments 占用最多的时间,可以通过对 events_waits_summary_global_by_event_name 表的 COUNT_STAR 或SUM_TIMER_WAIT 列进行查询(这两列是对事件的记录数执行COUNT(*)、事件记录的 TIMER_WAIT 列执行SUM 统计而来)
select * from file_instances limit 20;
instance 表记录了哪些类型的对象会被检测。这些对象在被server使用时,在该表中将会产生一条事件记录,例如,file_instances表列出了文件I/O操作及其关联文件名
基本了解了表的相关信息之后,可以通过这些表进行实际的查询操作来进行实际的分析
SELECT DIGEST_TEXT,COUNT_STAR,FIRST_SEEN,LAST_SEEN FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC
SELECT DIGEST_TEXT,AVG_TIMER_WAIT FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC;
SELECT DIGEST_TEXT,SUM_SORT_ROWS FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC;
SELECT DIGEST_TEXT,SUM_ROWS_EXAMINED FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC;
SELECT DIGEST_TEXT,SUM_CREATED_TMP_TABLES,SUM_CREATED_TMP_DISK_TABLES FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC;
SELECT DIGEST_TEXT,SUM_ROWS_SENT FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC;
SELECT file_name,event_name,SUM_NUMBER_OF_BYTES_READ,SUM_NUMBER_OF_BYTES_WRITE FROM file_summary_by_instance ORDER BY SUM_NUMBER_OF_BYTES_READ + SUM_NUMBER_OF_BYTES_WRITE DESC;
SELECT object_name,COUNT_READ,COUNT_WRITE,COUNT_FETCH,SUM_TIMER_WAIT FROM table_io_waits_summary_by_table ORDER BY sum_timer_wait DESC
SELECT OBJECT_NAME,INDEX_NAME,COUNT_FETCH,COUNT_INSERT,COUNT_UPDATE,COUNT_DELETE FROM table_io_waits_summary_by_index_usage ORDER BY SUM_TIMER_WAIT DESC
SELECT OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME FROM table_io_waits_summary_by_index_usage WHERE INDEX_NAME IS NOT NULL AND COUNT_STAR = 0 AND OBJECT_SCHEMA <> 'mysql' ORDER BY OBJECT_SCHEMA,OBJECT_NAME;
SELECT EVENT_NAME,COUNT_STAR,SUM_TIMER_WAIT,AVG_TIMER_WAIT FROM events_waits_summary_global_by_event_name WHERE event_name != 'idle' ORDER BY SUM_TIMER_WAIT DESC
SELECT EVENT_ID,sql_text FROM events_statements_history WHERE sql_text LIKE '%count(*)%';
SELECT event_id,EVENT_NAME,SOURCE,TIMER_END - TIMER_START FROM events_stages_history_long WHERE NESTING_EVENT_ID = 1553;
SELECT event_id,event_name,source,timer_wait,object_name,index_name,operation,nesting_event_id FROM events_waits_history_longWHERE nesting_event_id = 1553;
Questions=SHOW GLOBAL STATUS LIKE 'Questions';
Uptime = SHOW GLOBAL STATUS LIKE 'Uptime';
QPS=Questions/Uptime
在企业的应用场景中,为了知道优化 SQL 语句的执行,需要查看 SQL 语句的具体执行流程,以加快 SQL 语句的执行效率.
可以使用 explain+SQL 语句来模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理 SQL 语句的.
官网地址: https://dev.mysql.com/doc/refman/5.5/en/explain-output.html
Column | Meaning |
---|---|
id | The SELECT identifier |
select_type | The SELECT type |
table | The table for the output row |
partitions | The matching partitions |
type | The join type |
possible_keys | The possible indexes to choose |
key | The index actually chosen |
key_len | The length of the chosen key |
ref | The columns compared to the index |
rows | Estimate of rows to be examined |
filtered | Percentage of rows filtered by table condition |
extra | Additional information |
测试脚本及数据
CREATE TABLE `dept`(
`deptno` INT(2) NOT NULL,
`dname` VARCHAR(14),
`loc` VARCHAR(13),
CONSTRAINT pk_dept PRIMARY KEY(deptno)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO dept VALUES (10,'ACCOUNTING','NEW YORK');
INSERT INTO dept VALUES (20,'RESEARCH','DALLAS');
INSERT INTO dept VALUES (30,'SALES','CHICAGO');
INSERT INTO dept VALUES (40,'OPERATIONS','BOSTON');
CREATE TABLE `emp` (
`empno` int(4) NOT NULL PRIMARY KEY,
`ename` VARCHAR(10),
`job` VARCHAR(9),
`mgr` int(4),
`hiredate` DATE,
`sal` float(7,2),
`comm` float(7,2),
`deptno` int(2),
CONSTRAINT fk_deptno FOREIGN KEY(deptno) REFERENCES dept(deptno)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO EMP VALUES (7369,'SMITH','CLERK',7902,'1980-12-17',800,NULL,20);
INSERT INTO EMP VALUES (7499,'ALLEN','SALESMAN',7698,'1981-02-20',1600,300,30);
INSERT INTO EMP VALUES (7521,'WARD','SALESMAN',7698,'1981-02-22',1250,500,30);
INSERT INTO EMP VALUES (7566,'JONES','MANAGER',7839,'1981-04-02',2975,NULL,20);
INSERT INTO EMP VALUES (7654,'MARTIN','SALESMAN',7698,'1981-09-28',1250,1400,30);
INSERT INTO EMP VALUES (7698,'BLAKE','MANAGER',7839,'1981-05-01',2850,NULL,30);
INSERT INTO EMP VALUES (7782,'CLARK','MANAGER',7839,'1981-06-09',2450,NULL,10);
INSERT INTO EMP VALUES (7788,'SCOTT','ANALYST',7566,'1987-07-13',3000,NULL,20);
INSERT INTO EMP VALUES (7839,'KING','PRESIDENT',NULL,'1981-11-07',5000,NULL,10);
INSERT INTO EMP VALUES (7844,'TURNER','SALESMAN',7698,'1981-09-08',1500,0,30);
INSERT INTO EMP VALUES (7876,'ADAMS','CLERK',7788,'1987-07-13',1100,NULL,20);
INSERT INTO EMP VALUES (7900,'JAMES','CLERK',7698,'1981-12-03',950,NULL,30);
INSERT INTO EMP VALUES (7902,'FORD','ANALYST',7566,'1981-12-03',3000,NULL,20);
INSERT INTO EMP VALUES (7934,'MILLER','CLERK',7782,'1982-01-23',1300,NULL,10);
CREATE TABLE `bonus`(
`ename` VARCHAR(10),
`job` VARCHAR(9),
`sal` INT,
`comm` INT
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `salgrade` (
`grade` int,
`losal` int,
`hisal` int
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO SALGRADE VALUES (1,700,1200);
INSERT INTO SALGRADE VALUES (2,1201,1400);
INSERT INTO SALGRADE VALUES (3,1401,2000);
INSERT INTO SALGRADE VALUES (4,2001,3000);
INSERT INTO SALGRADE VALUES (5,3001,9999);
select 查询的序列号,包含一组数字,表示查询中执行 select 子句或者操作表的顺序,id 号分为三种情况
EXPLAIN SELECT * FROM emp e JOIN dept d ON e.deptno = d.deptno JOIN salgrade sg ON e.sal BETWEEN sg.losal AND sg.hisal;
如果 id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
id 相同和不同的,同时存在:相同的可以认为是一组,从上往下顺序执行,在所有组中,id 值越大,优先级越高,越先执行
主要用来分辨查询的类型,是普通查询还是联合查询还是子查询
explain select * from emp;
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno;
explain select * from emp where deptno = 10 union select * from emp where sal >2000;
explain select * from emp e where e.empno in ( select empno from emp where deptno = 10 union select empno from emp where sal >2000);
explain select * from emp where deptno = 10 union select * from emp where sal >2000;
explain select * from emp where sal > (select avg(sal) from emp) ;
explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size);
对应行正在访问哪一个表,表名或者别名,可能是临时表或者 union 合并结果集
type 显示的是访问类型,访问类型表示以何种方式去访问我们的数据,最容易想的是全表扫描,直接暴力的遍历一张表去寻找需要的数据,效率非常低下,访问的类型有很多,效率从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般情况下,得保证查询至少达到 range 级别,最好能达到 ref
explain select * from emp;
explain select empno from emp;
explain select * from emp where empno between 7000 and 7500;
explain select * from emp where emp.job in (select job from t_job);
explain select * from emp e where e.deptno in (select distinct deptno from dept);
index_merge:在查询过程中需要多个索引组合使用
ref_or_null:对于某个字段即需要关联条件,也需要 null 值的情况下,查询优化器会选择这种访问方式
explain select * from emp e where e.mgr is null or e.mgr=7369;
create index idx_3 on emp(deptno);
explain select * from emp e,dept d where e.deptno =d.deptno;
explain select * from emp,emp2 where emp.empno = emp2.empno;
explain select * from emp where empno = 7369;
显示可以应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
实际使用的索引,如果为 null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的 select 字段重叠
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
表示索引中使用的字节数,可以通过 key_len 计算查询中使用的索引长度,在不损失精度的情况下越短越好.
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
显示索引的哪一列被使用了,如果可能的话,是一个常数
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应 SQL 找了多少数据,在完成目的情况下越少越好
explain select * from emp;
包含额外的信息
explain select * from emp order by sal;
explain select ename,count(*) from emp where deptno = 10 group by ename;
explain select deptno,count(*) from emp group by deptno limit 10;
explain select * from t_user where id = 1;
explain select * from emp where empno = 7469;
MySQL 使用作为后端人员是必须牢牢掌握的一门技术,而调优的路上是作为后端晋升的必经之路,所以,先了解 MySQL 本身拥有的监控工具也是必备的,当你后面涉及到数据库调优的东西时,就可以从这些工具上入手了!!
当然,现在市场上提供了大部分的第三方数据监控工具,先学到自带的再应用别的工具时,其实也能发现其实就运用这上面的功能和新增了一些业务上比较经常需要监控的指标信息
更多技术文章可以查看:vnjohn 个人博客