数据库调优中,目标是 响应时间更快,吞吐量更大,利用宏观的监控工具和微观的日志分析帮助我们快速找到调优的思路和方式。
整个流程划分成了 观察(Show status) 和 行动(Action) 两个部分。字母 S 的部分代表观察(会使 用相应的分析工具),字母 A 代表的部分是行动(对应分析可以采取的行动)。
在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:删除操作的次数。
查看是否开启
show variables like ‘%low_query_log%’;
开启命令
set global slow_query_log=‘ON’;
查看当前session
show variables like '%long_query_time%';
show global variables like '%long_query_time%';
全局设置
set global long_query_time = 1;
当前session
set long_query_time = 1;
执行一个耗时sql
select student_id,create_time, count(*) from student_info GROUP BY student_id ,create_time;
SHOW GLOBAL STATUS LIKE '%Slow_queries%';
执行一个耗时sql
select student_id,create_time, count(*) from student_info GROUP BY student_id ,create_time;
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具 mysqldumpslow 。
-a: 不将数字抽象成N,字符串抽象成S
-s: 是表示按照何种方式排序:
c: 访问次数
l: 锁定时间
r: 返回记录
t: 查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间 (默认方式)
ac:平均查询次数
-t: 即为返回前面多少条的数据;
-g: 后边搭配一个正则匹配模式,大小写不敏感的;
<1>按照查询时间排序,查看前五条 SQL 语句
mysqldumpslow -s t -t 5 /var/lib/mysql/mysql202-slow.log
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
设置为OFF
[mysqld]
slow_query_log=OFF
或直接删除或注释
[mysqld]
#slow_query_log =OFF
SET GLOBAL slow_query_log=off;
全局设置
show global variables like 'profiling';
show variables like 'profiling';
全局设置
set global profiling = 'ON';
当前session
set profiling = 'ON';
show profiles;
show profile;
show profile for query 2;
EXPLAIN SELECT select_options
或者
DESCRIBE SELECT select_options
EXPLAIN SELECT 1;
依次执行
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);
同一组中,排在上边的就做驱动表,后边的叫做 被驱动表
查询语句中不包含UNION
或者子查询的查询都算作是SIMPLE
类型
连接查询也算是SIMPLE
类型
对于包含UNION
或者UNION ALL
或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type
值就是PRIMARY
对于包含UNION
或者UNION ALL
的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type
值就是UNION
MySQL选择使用临时表来完成UNION
查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT
如果包含子查询的查询语句不能够转为对应的semi-join
的形式,并且该子查询是不相关子查询。该子查询的第一个SELECT
关键字代表的那个查询的select_type
就是SUBQUERY
如果包含子查询的查询语句不能够转为对应的semi-join
的形式,并且该子查询是相关子查询,则该子查询的第一个SELECT
关键字代表的那个查询的select_type
就是DEPENDENT SUBQUERY
, select_type为DEPENDENT SUBQUERY
的查询可能会被执行多次。
在包含UNION
或者UNION ALL
的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的select_type
的值就是DEPENDENT UNION
。
对于包含派生表
的查询,该派生表对应的子查询的select_type
就是DERIVED
当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的select_type
属性就是MATERIALIZED
当表中只有一条记录
并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system
。
当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL
值时,那么对该表的访问方法就可能是ref_or_null
单表访问方法时在某些场景下可以使用Intersection
、Union
、Sort-Union
这三种索引合并的方式来执行查询
unique_subquery
是针对在一些包含IN
子查询的查询语句中,如果查询优化器决定将IN
子查询转换为EXISTS
子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type
列的值就是unique_subquery
如果使用索引获取某些范围区间
的记录,那么就可能使用到range
访问方法
当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index
全表扫描
实际使用到的索引长度(即:字节数),帮助检查是否充分利用上了索引,值越大越好,主要针对于联合索引,有一定参考意义。
当使用索引列等值查询时,与索引列进行等值匹配的对象信息。比如只是一个常数或者是某个列。
预估的需要读取的记录条数 ,值越小越好
某个表经过搜索条件过滤后剩余记录条数的百分比如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。