【0】如何分析mysql中sql执行较慢的问题
【总结】
==============================================================================================
【1】exists + order by + group by 优化
1.1、查询优化:使用 explain 查看执行计划,看是否可以基于索引查询;
1.2、小表驱动大表:当两个表做连接操作时, 其实就是双层for循环, 小表驱动大表的意思是, 小表(小数据集的表)放在第1层循环,大表(大数据集的表)放在第2层循环;
【补充】关于exists 语法 与 in 的区别:
exists语法:把 where id in 换位 where exists 即可;
select ... from tbl where exists (subquery);
exists语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果 true 或 false, 来决定主查询的数据结果是否保留;
关于exists的提示:
exists用法荔枝(exists与in的区别):
-- exists 语法
select * from emp_tbl a where exists (select 1 from dept_tbl b where b.dept_id = a.dept_id)
order by a.rcrd_id
limit 10
;
-- in 语法
select * from emp_tbl a where a.dept_id in (select b.dept_id from dept_tbl b)
order by a.rcrd_id
limit 10
;
-- 执行计划18
explain select * from emp_tbl a where exists (select 1 from dept_tbl b where b.dept_id = a.dept_id)
;
-- 执行计划19
explain select * from emp_tbl a where a.dept_id in (select b.dept_id from dept_tbl b)
;
==============================================================================================
1.2、order by 关键字优化
优化1、尽量使用index方式排序,避免使用 filesort方式;
-- 建表
drop table if exists birth_tbl;
create table birth_tbl (
`rcrd_id` int(10) unsigned primary key auto_increment COMMENT '记录编号'
, age int default 0 comment '年龄'
, birth timestamp default current_timestamp comment '生日'
) engine=innodb default charset=utf8 comment '生日表'
;
-- 插入数据
insert into birth_tbl(age) values
(floor(1+(rand()*100)))
, (floor(1+(rand()*100)))
, (floor(1+(rand()*100)))
, (floor(1+(rand()*100)))
, (floor(1+(rand()*100)))
;
-- 添加索引
alter table birth_tbl
add key `idx_age_birth`(`age`, `birth`)
;
-- 查看order by的执行计划是否使用了文件排序;
-- 执行计划20 索引排序
explain select * from birth_tbl where age > 30 order by age
;
-- 执行计划21 索引排序
explain select * from birth_tbl where age > 30 order by age, birth
;
-- 执行计划22 文件排序
explain select * from birth_tbl where age > 30 order by birth
;
-- 执行计划23 文件排序
explain select * from birth_tbl where age > 30 order by birth, age
;
-- 执行计划24 文件排序
explain select * from birth_tbl order by birth
;
-- 执行计划25 文件排序
explain select * from birth_tbl where birth > '2018-01-01 00:00:00' order by birth
;
-- 执行计划26 索引排序
explain select * from birth_tbl where birth > '2018-01-01 00:00:00' order by age
;
-- 执行计划27 文件排序
explain select * from birth_tbl order by age asc, birth desc
;
mysql支持两种方式的排序: 文件排序filesort 和 index 索引排序, index排序的效率较高; 它指mysql 扫描索引本身完成排序,文件排序filesort 效率较低;
【补充】 order by 满足两个情况:会使用索引排序:
优化2、尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀;
优化3、如果不在索引列上,文件排序filesort有两种算法: mysql需要启动双路排序或单路排序;
优化策略:
【总结】order by 总结-为排序使用索引:
key idx_a_b_c(a, b, c);
order by a;
order by a, b;
order by a, b, c;
order by a desc, b desc, c desc; -- (要么全部升序,要么全部降序)
where a = const order by b, c;
where a=const and b=const order by c;
where a = const and b > const order by b, c;
order by a asc, b desc, c desc;
where g = const order by b, c;
where a = const order by c;
where a=const order by a, d -- d不是索引的一部分;
where a in (...) order by b, c;
1.3、group by 关键字优化;(均和order by 一样)
=============================================================================================
【2】慢查询日志
0、什么是慢查询sql:sql运行时间超过 long_query_time 的sql 将会被记录到 慢查询日志中;
1)mysql的慢查询日志
2)查看是否开启慢查询日志以及如何开启?
默认: show variables like '%slow_query_log%';
开启: set global slow_query_log=1;
【注意】
如何设置long_query_time ?
show variables like 'long_query_time%';
查询 long_query_time的值?即查询当前多少秒算慢?
show variables like 'long_query_time'
设置慢的阈值时间?
set global long_query_time=3;
为什么设置后期 long_query_time 还是没变;
这个时候需要重新连接或新开一个会话; 或者执行 show global variables like 'long_query_time' ;
如何制造 执行时间超过3秒的SQL?
如 select sleep(4);
查看当前有多少条慢查询sql?
show global status like '%slow_queries%';
补充1:如何在my.ini文件中配置mysql的慢查询参数, 如下:
补充2: 日志分析工具 mysqldumpslow ,常用于在生产中分析sql的性能;
=============================================================================================
【3】、批量数据脚本;
-- 新建函数-产生随机的字符串
drop function if exists rand_str;
delimiter ##
create function rand_str(n int) returns varchar(255)
begin
declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
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 ;
-- 新建函数-产生随机的整数
drop function if exists rand_num;
delimiter ##
create function rand_num() returns int
begin
declare i int default 0;
set i=floor(100+rand()*10);
return i;
end ##
delimiter ;
-- 创建存储过程,函数没法单独被调用,只能通过存储过程进行调用;
-- 新建存储过程-调用函数批量插入数据
drop procedure if exists insert_emp;
delimiter ##
create procedure insert_emp(in start_num int, in max_num int)
begin
declare i int default 0;
set autocommit=0;
repeat
set i=i+1;
INSERT INTO mybatis.emp_tbl (emp_id, dept_id, name)VALUES(rand_num(), rand_num(), rand_str(20));
until i = max_num
end repeat;
commit;
end ##
delimiter ;
call insert_emp(0, 100000)
;
=============================================================================================
【4】show profile
4、show profile
4.0)intro: show profile 提供了比 explain 更加细粒度的sql执行计划分析和sql优化;
4.1)是什么: 是mysql提供可以用来分析当前回话中语句执行的资源消耗情况。可以用于sql的调优测量;
4.2)官网: https://dev.mysql.com/doc/refman/8.0/en/show-profile.html
4.3)默认情况下,参数处于关闭状态,并保持最近15次的运行结果;
4.4)分析步骤:
select * from emp_tbl e inner join dept_tbl d on e.dept_id = d.dept_id
;
select * from emp_tbl e left join dept_tbl d on e.dept_id = d.dept_id
;
select * from emp_tbl group by rcrd_id%10
;
【关于show profile的结论】
=============================================================================================
【5】、全局查询日志;
5.1、配置启用: 在mysql的 my.ini 中,配置如下:
#开启
general_log=1
#记录日志文件的路径
general_log_file=/path/logfile
#输出格式
log_output=FILE
5.2、编码启用:命令如下:
set global general_log=1;
set global log_output='TABLE';
此后, 你所编写的sql语句, 将会记录到 mysql库里的general_log 表,可以用下面的命令查看:
select * from mysql.general_log;
5.3、建议不要在生产环境开启这个功能, 仅在测试环境开启以便调试;
【建议】 建议使用 show profile 功能分析和优化sql性能;