数据库调优中,目标是 响应时间更快,吞吐量更大,利用宏观的监控工具和微观的日志分析帮助我们快速找到调优的思路和方式。
1. 数据库服务器优化步骤
整个流程划分成了 观察(Show status) 和 行动(Action) 两个部分。字母 S 的部分代表观察(会使 用相应的分析工具),字母 A 代表的部分是行动(对应分析可以采取的行动)。
2. 查看系统性能参数
在MySQL中,可以使用 SHOW STATUS 语句查询一些MySQL数据库服务器的 性能参数 、 执行频率 。
SHOW [GLOBAL|SESSION] STATUS LIKE '参数';
一些常用的性能参数如下:
• Connections:连接MySQL服务器的次数。
• Uptime:MySQL服务器的上线时间。
• Slow_queries:慢查询的次数。
• Innodb_rows_read:Select查询返回的行数
• Innodb_rows_inserted:执行INSERT操作插入的行数
• Innodb_rows_updated:执行UPDATE操作更新的行数
• Innodb_rows_deleted:执行DELETE操作删除的行数
• Com_select:查询操作的次数。
• Com_insert:插入操作的次数。对于批量插入的 INSERT 操作,只累加一次。
• Com_update:更新操作的次数。
• Com_delete:删除操作的次数。
3. 统计SQL的查询成本:last_query_cost
4. 定位执行慢的SQL:慢日志查询
<1> 概述
<2> 开启slow_query_log (global参数)
查看是否开启
show variables like ‘%low_query_log%’;
开启命令
set global slow_query_log=‘ON’;
<3> 修改long_query_time阈值(global&session参数)
1. 查看阈值
查看当前session
show variables like '%long_query_time%';
查看全局
show global variables like '%long_query_time%';
2. 设置阈值命令
全局设置
set global long_query_time = 1;
当前session
set long_query_time = 1;
3. min_examined_row_limit
<4> 查看慢查询数目
执行一个耗时sql
select student_id,create_time, count(*) from student_info GROUP BY student_id ,create_time;
SHOW GLOBAL STATUS LIKE '%Slow_queries%';
<5> 读取慢查询日志文件
执行一个耗时sql
select student_id,create_time, count(*) from student_info GROUP BY student_id ,create_time;
<6> 慢查询日志分析工具:mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具 mysqldumpslow 。
1. 语法
mysqldumpslow 命令的具体参数如下:
-a: 不将数字抽象成N,字符串抽象成S
-s: 是表示按照何种方式排序:
c: 访问次数
l: 锁定时间
r: 返回记录
t: 查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间 (默认方式)
ac:平均查询次数
-t: 即为返回前面多少条的数据;
-g: 后边搭配一个正则匹配模式,大小写不敏感的;
2. 案例
<1>按照查询时间排序,查看前五条 SQL 语句
mysqldumpslow -s t -t 5 /var/lib/mysql/mysql202-slow.log
<2> 得到返回记录集最多的10个SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/mysql202-slow.log
<3> 得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/mysql202-slow.log
<4> 得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/mysql202-slow.log
<5> 另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/mysql202-slow.log | more
<7> 关闭慢查询日志
1. 配置文件(永久性)
设置为OFF
[mysqld]
slow_query_log=OFF
或直接删除或注释
[mysqld]
#slow_query_log =OFF
2. 命令方式(临时性)
SET GLOBAL slow_query_log=off;
<8> 删除慢查询日志
- 查询文件位置,然后去文件目录中删除或备份即可
- 重新生成日志文件:mysqladmin -uroot -p flush-logs slow
5. 查看SQL执行成本:profiler(global&session参数)
<1> 查看状态
全局设置
show global variables like 'profiling';
当前session
show variables like 'profiling';
<2> 开启关闭
全局设置
set global profiling = 'ON';
当前session
set profiling = 'ON';
<3> 查看sql列表
show profiles;
<4> 查看最近一个sql详细内容
show profile;
<5> 查看指定请求详细内容
show profile for query 2;
<6> 常用查询参数
- ALL:显示所有的开销信息。
- BLOCK IO:显示块IO开销。
- CONTEXT SWITCHES:上下文切换开销。
- CPU:显示CPU开销信息。
- IPC:显示发送和接收开销信息。
- MEMORY:显示内存开销信息。
- PAGE FAULTS:显示页面错误开销信息。
- SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
- SWAPS:显示交换次数开销信息。
<7> 注意
6. 分析查询语句:EXPLAIN
<1> 概述
1. 简介
2. 作用
3. 版本情况
<2> 基本语法
- 语法
EXPLAIN SELECT select_options
或者
DESCRIBE SELECT select_options
- 案例
EXPLAIN SELECT 1;
<3> 数据准备
依次执行
CREATE TABLE s1 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
CREATE TABLE s2 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
set global log_bin_trust_function_creators=1;
DELIMITER //
CREATE FUNCTION rand_string1(n INT)
RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;
DELIMITER //
CREATE PROCEDURE insert_s1 (IN min_num INT (10),IN max_num INT (10))
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO s1 VALUES(
(min_num + i),
rand_string1(6),
(min_num + 30 * i + 5),
rand_string1(6),
rand_string1(10),
rand_string1(5),
rand_string1(10),
rand_string1(10));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
DELIMITER //
CREATE PROCEDURE insert_s2 (IN min_num INT (10),IN max_num INT (10))
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO s2 VALUES(
(min_num + i),
rand_string1(6),
(min_num + 30 * i + 5),
rand_string1(6),
rand_string1(10),
rand_string1(5),
rand_string1(10),
rand_string1(10));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
CALL insert_s1(10001,10000);
CALL insert_s2(10001,10000);
<4> EXPLAIN 输出列详解
1. table
2. id
同一组中,排在上边的就做驱动表,后边的叫做 被驱动表
3. select_type
<1> SIMPLE
查询语句中不包含UNION
或者子查询的查询都算作是SIMPLE
类型
连接查询也算是SIMPLE
类型
<2> PRIMARY
对于包含UNION
或者UNION ALL
或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type
值就是PRIMARY
<3> UNION
对于包含UNION
或者UNION ALL
的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type
值就是UNION
<4> UNION RESULT
MySQL选择使用临时表来完成UNION
查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT
<5> SUBQUERY
如果包含子查询的查询语句不能够转为对应的semi-join
的形式,并且该子查询是不相关子查询。该子查询的第一个SELECT
关键字代表的那个查询的select_type
就是SUBQUERY
<6> DEPENDENT SUBQUERY
如果包含子查询的查询语句不能够转为对应的semi-join
的形式,并且该子查询是相关子查询,则该子查询的第一个SELECT
关键字代表的那个查询的select_type
就是DEPENDENT SUBQUERY
, select_type为DEPENDENT SUBQUERY
的查询可能会被执行多次。
<7> DEPENDENT UNION
在包含UNION
或者UNION ALL
的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的select_type
的值就是DEPENDENT UNION
。
<8> DERIVED
对于包含派生表
的查询,该派生表对应的子查询的select_type
就是DERIVED
<9> MATERIALIZED
当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的select_type
属性就是MATERIALIZED
4. partitions
5. type
<1> system
当表中只有一条记录
并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system
。
<2> const
当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const
<3> eq_ref
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
<4> ref
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
<5> ref_or_null
当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL
值时,那么对该表的访问方法就可能是ref_or_null
<6> index_merge
单表访问方法时在某些场景下可以使用Intersection
、Union
、Sort-Union
这三种索引合并的方式来执行查询
<7> unique_subquery
unique_subquery
是针对在一些包含IN
子查询的查询语句中,如果查询优化器决定将IN
子查询转换为EXISTS
子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type
列的值就是unique_subquery
<8> range
如果使用索引获取某些范围区间
的记录,那么就可能使用到range
访问方法
<9> index
当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index
<10>all
全表扫描
6. possible_keys 和key
7. key_len
实际使用到的索引长度(即:字节数),帮助检查是否充分利用上了索引,值越大越好,主要针对于联合索引,有一定参考意义。
<1> key_len的长度计算公式
- varchar(10)变长字段且允许NULL
10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段) - varchar(10)变长字段且不允许NULL
10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段) - char(10)固定字段且允许NULL
10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL) - char(10)固定字段且不允许NULL
10 * ( character set:utf8=3,gbk=2,latin1=1)
8. ref
当使用索引列等值查询时,与索引列进行等值匹配的对象信息。比如只是一个常数或者是某个列。
9. rows
预估的需要读取的记录条数 ,值越小越好
10. filtered
某个表经过搜索条件过滤后剩余记录条数的百分比如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。