---分析---
1.观察一天,看看生产的慢SQL情况;
2.开启慢查询日志,设置阈值,必读超过5秒就是 慢SQL,并将它抓取出来;
3.ecplain+慢SQL分析
4.show profile
5.运维经理or DBA,进行SQL数据库服务器的参数调优
==总结
1.慢查询的开启并捕获
2.explain+慢SQL分析
3.show profile查询SQL在MYSQL服务器里面的执行细节和生命周期
4.SQL数据库服务器的参数调优。
3.5.1 单表查询优化
1.建表
2. 实例
#查询 category_id 为1 且 comments 大于 1 的情况下,views 最多的 article_id。
#结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
#开始优化:
# 1.1 新建索引+删除索引create index idx_article_ccv on article(category_id,comments,views);
#结论:
#type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。
#但是我们已经建立了索引,为啥没用呢?
#这是因为按照 BTree 索引的工作原理,
# 先排序 category_id,
# 如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。
#当 comments 字段在联合索引里处于中间位置时,
#因comments > 1 条件是一个范围值(所谓 range),
#MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。# 1.3 删除第一次建立的索引
DROP INDEX idx_article_ccv ON article;
# 1.4 第2次新建索引
#ALTER TABLE `article` ADD INDEX idx_article_cv ( `category_id` , `views` ) ;
create index idx_article_cv on article(category_id,views);# 1.5 第3次EXPLAIN
#结论:可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。
DROP INDEX idx_article_cv ON article;
3.5.2 双表查询优化
1.建表
2.案例:
#结论:type 有All
# 添加索引优化
ALTER TABLE `book` ADD INDEX Y ( `card`);
# 第2次explain
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;#可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。
#这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,
#所以右边是我们的关键点,一定需要建立索引。建立一个右表索引
# 删除旧索引 + 新建 + 第3次explain
DROP INDEX Y ON book;
ALTER TABLE class ADD INDEX X (card);建议:
1、保证被驱动表的join字段已经被索引。被驱动表 join 后的表为被驱动表 (需要被查询)
2、left join 时,选择小表作为驱动表,大表作为被驱动表。但是 left join 时一定是左边是驱动表,右边是被驱动表
3、inner join 时,mysql会自己帮你把小结果集的表选为驱动表。mysql 自动选择。小表作为驱动表。因为 驱动表无论如何都会被全表扫描?。所以扫描次数越少越好
4、子查询尽量不要放在被驱动表,有可能使用不到索引。若必须用到子查询,可将子查询设置为驱动表,,因为驱动表的type 肯定是 all,而子查询返回的结果表没有索引,必定也是all。
5、 左连接建在右表,右连接建在左表。
6.、索引最好设置早要经常查询的字段中
7.优先优化内层循环
3.5.3 案例(索引失效)
若一个字段上有多种索引呢?某一索引失效,可以继续使用其他索引不影响。
建表
1. 全值匹配我最爱
索引 idx_staffs_nameAgePos 建立索引时 以 name , age ,pos 的顺序建立的。全值匹配表示 按顺序匹配的
2.最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
and 忽略左右关系。既即使没有没有按顺序 由于优化器的存在,会自动优化。
经过试验结论 建立了 idx_nameAge 索引 id 为主键
1.当使用覆盖索引的方式时,(select name/age/id from staffs where age=10 (后面没有其他没有索引的字段条件)),即使不是以 name 开头,也会使用 idx_nameAge 索引。
既 select 后的字段 有索引,where 后的字段也有索引,则无关执行顺序。
2.除开上述条件 才满足最左前缀法则。3、不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
4.、存储引擎不能使用索引中范围条件右边的列
范围 若有索引则能使用到索引,范围条件右边的索引会失效(范围条件右边与范围条件使用的同一个组合索引,右边的才会失效。若是不同索引则不会失效)
5、尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
6、mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
索引 idx_nameAgeJob
idx_name
使用 != 和 <> 的字段索引失效( != 针对数值类型。 <> 针对字符类型
前提 where and 后的字段在混合索引中的位置比比当前字段靠后 where age != 10 and name='xxx' ,这种情况下,mysql自动优化,将 name='xxx' 放在 age !=10 之前,name 依然能使用索引。只是 age 的索引失效)7、is not null 也无法使用索引,但是is null是可以使用索引的
8、like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作
like ‘%abc%’ type 类型会变成 all
like ‘abc%’ type 类型为 range ,算是范围,可以使用索引问题:解决like '%字符串%'时索引不被使用的方法??
使用覆盖索引解决这个问题
9、字符串不加单引号索引失效
底层进行转换使索引失效,使用了函数造成索引失效
10、少用or,用它来连接时会索引失效
11、小总结
假设index(a,b,c)
Where语句 索引是否被使用 where a = 3 Y,使用到a where a = 3 and b = 5 Y,使用到a,b where a = 3 and b = 5 and c = 4 Y,使用到a,b,c where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N,用不到索引 where a = 3 and c = 5 使用到a, 但是c不可以,b中间断了 where a = 3 and b > 4 and c = 5 使用到a和b, c不能用在范围之后,b后断了 where a = 3 and b like 'kk%' and c = 4 Y,使用到a,b, c where a = 3 and b like '%kk' and c = 4 Y,只用到a where a = 3 and b like '%kk%' and c = 4 Y,只用到a where a = 3 and b like 'k%kk%' and c = 4 Y,使用到a,b,c
3.5.4 面试题讲解
建表
【建索引】
create index idx_test03_c1234 on test03(c1,c2,c3,c4);问题:我们创建了复合索引idx_test03_c1234 ,根据以下SQL分析下索引使用情况?
2、
3、用到3个索引
4、 用到4个索引
5、c3作用在排序而不是查找
6、c3作用在排序而不是查找
7、出现了filesort
8.1、 只用c1一个字段索引,但是c2、c3用于排序,无filesort
8.2 出现了filesort,我们建的索引是1234,它没有按照顺序来,3 2 颠倒了
9、用c1、c2两个字段索引,但是c2、c3用于排序,无filesort
10、用c1、c2两个字段索引,但是c2、c3用于排序,无filesort
本例有常量c2的情况,排序字段已经是一个常量,和8.2对比,无filesort
有 filesort
11、 用到了一个索引
12. 分组之前必排序,grop by 和odder by几乎一致
3.5.5、一般性建议
对于单键索引,尽量选择针对当前query过滤性更好的索引
在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。(避免索引过滤性好的索引失效)
在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的
3.5.6 、去重优化
尽量不要使用 distinct 关键字去重:优化
例子:select kcdz form t_mall_sku where id in( 3,4,5,6,8 ) 将产生重复数据,
select distinct kcdz form t_mall_sku where id in( 3,4,5,6,8 ) 使用 distinct 关键字去重消耗性能
优化: select kcdz form t_mall_sku where id in( 3,4,5,6,8 ) group by kcdz 能够利用到索引
子查询优化,用in 还是 exists
有索引的情况下 用 inner join 是最好的 其次是 in ,exists最糟糕
无索引的情况下用
小表驱动大表 因为join 方式需要distinct ,没有索引distinct消耗性能较大
所以 exists性能最佳 in其次 join性能最差?
无索引的情况下大表驱动小表
in 和 exists 的性能应该是接近的 都比较糟糕 exists稍微好一点 超不过5% 但是inner join 优于使用了 join buffer 所以快很多
如果left join 则最慢
4.1.2.1 ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
建表
建立索引:CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);
MySQL支持二种方式的排序,FileSort和Index,Index效率高。它指MySQL扫描索引本身完成排序。FileSort方式效率较低。
ORDER BY满足两情况,会使用Index方式排序:
ORDER BY 语句使用索引最左前列
使用Where子句与Order BY子句条件列组合满足索引最左前列
where子句中如果出现索引的范围查询(即explain中出现range)会导致order by 索引失效。
4.1.2.2 如果不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序
双路排序:
MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,
读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出多路排序需要借助 磁盘来进行排序。所以 取数据,排好了取数据。两次 io操作。比较慢
单路排序 ,将排好的数据存在内存中,省去了一次 io 操作,所以比较快,但是需要内存空间足够。从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
单路排序:
从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,
它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,
因为它把每一行都保存在内存中了。结论及引申出的问题:
由于单路是后出的,总体而言好过双路
但是用单路有问题:
在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。
本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
优化策略:增大sort_buffer_size参数的设置,用于单路排序的内存大小
增大max_length_for_sort_data参数的设置,单次排序字段大小。(单次排序请求)
去掉select 后面不需要的字段,select 后的多了,排序的时候也会带着一起,很占内存,所以去掉没有用的
提高Order By的速度
1. Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:
1.1 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
1.2 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
2. 尝试提高 sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的
3. 尝试提高 max_length_for_sort_data
提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率.
尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀:
第二种中,where a = const and b > const order by b , c 不会出现 using filesort b , c 两个衔接上了
但是:where a = const and b > const order by c 将会出现 using filesort 。因为 b 用了范围索引,断了。而上一个 order by 后的b 用到了索引,所以能衔接上 c
group by实质是先排序后进行分组,遵照索引建的最佳左前缀
当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
where高于having,能写在where限定的条件就不要去having限定了。
5.2.1 说明
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件
5.2.2 查看是否开启及如何开启
默认:SHOW VARIABLES LIKE '%slow_query_log%';
开启:set global slow_query_log=1;
使用set global slow_query_log=1开启了慢查询日志只对当前数据库生效,
如果MySQL重启后则会失效。如果要永久生效,就必须修改配置文件my.cnf(其它系统变量也是如此)
修改my.cnf文件,[mysqld]下增加或修改参数
slow_query_log 和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件
slow_query_log =1
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
关于慢查询的参数slow_query_log_file ,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件host_name-slow.log(如果没有指定参数slow_query_log_file的话)
5.2.3 那么开启了慢查询日志后,什么样的SQL才会记录到慢查询日志里面呢?
这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒,
命令:SHOW VARIABLES LIKE 'long_query_time%';可以使用命令修改,也可以在my.cnf参数里面修改。
假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,
在mysql源码里是判断大于long_query_time,而非大于等于。
5.2.4 Case
1. 查看当前多少秒算慢
SHOW VARIABLES LIKE 'long_query_time%';
2.设置慢的阙值时间
使用命令
set global long_query_time=1
修改为阙值到1秒钟的就是慢sql修改后发现long_query_time并没有改变。
为什么设置后看不出变化?
- 需要重新连接或新开一个会话才能看到修改值。 SHOW VARIABLES LIKE 'long_query_time%';
- SHOW GLOBAL VARIABLES LIKE 'long_query_time%';
3.记录慢SQL并后续分析
实验一条慢sql
跟踪日志信息
4. 查询当前系统中有多少条慢查询记录
show global status like '%Slow_queries%';
5.2.5 配置版
【mysqld】下配置:
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3;
log_output=FILE
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。
5.3.1 查看mysqldumpslow的帮助信息
mysqldumpslow --help
5.3.2 工作常用参考
得到返回记录集最多的10个SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more
往表里插入1000W数据
1. 建表
# 新建库
create database bigData;
use bigData;
#1 建表dept
CREATE TABLE dept(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
dname VARCHAR(20) NOT NULL DEFAULT "",
loc VARCHAR(13) NOT NULL DEFAULT ""
) ENGINE=INNODB DEFAULT CHARSET=UTF8 ;
#2 建表emp
CREATE TABLE emp
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*编号*/
ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/
job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/
mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/
hiredate DATE NOT NULL,/*入职时间*/
sal DECIMAL(7,2) NOT NULL,/*薪水*/
comm DECIMAL(7,2) NOT NULL,/*红利*/
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/
)ENGINE=INNODB DEFAULT CHARSET=UTF8 ;
2.设置参数log_bin_trust_function_creators
创建函数,假如报错:This function has none of DETERMINISTIC......
# 由于开启过慢查询日志,因为我们开启了 bin-log, 我们就必须为我们的function指定一个参数。
show variables like 'log_bin_trust_function_creators';
set global log_bin_trust_function_creators=1;
# 这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法:
windows下my.ini[mysqld]加上log_bin_trust_function_creators=1
linux下 /etc/my.cnf下my.cnf[mysqld]加上log_bin_trust_function_creators=1
3.创建函数,保证每条数据都不同
创建函数,保证每条数据都不同
DELIMITER $$ CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255) BEGIN ##方法开始 DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; ##声明一个 字符窜长度为 100 的变量 chars_str ,默认值 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)); ##concat 连接函数 ,substring(a,index,length) 从index处开始截取 SET i = i + 1; END WHILE; RETURN return_str; END $$ #假如要删除 #drop function rand_string;
随机产生部门编号
#用于随机产生部门编号 DELIMITER $$ CREATE FUNCTION rand_num( ) RETURNS INT(5) BEGIN DECLARE i INT DEFAULT 0; SET i = FLOOR(100+RAND()*10); RETURN i; END $$ #假如要删除 #drop function rand_num;
4. 创建存储过程
创建往emp表中插入数据的存储过程
DELIMITER $$ CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10)) BEGIN DECLARE i INT DEFAULT 0; #set autocommit =0 把autocommit设置成0 ;提高执行效率 SET autocommit = 0; REPEAT ##重复 SET i = i + 1; INSERT INTO emp(empno, ename ,job ,mgr ,hiredate ,sal ,comm ,deptno ) VALUES ((START+i) ,rand_string(6),'SALESMAN',0001,CURDATE(),FLOOR(1+RAND()*20000),FLOOR(1+RAND()*1000),rand_num()); UNTIL i = max_num ##直到 上面也是一个循环 END REPEAT; ##满足条件后结束循环 COMMIT; ##执行完成后一起提交 END $$ #删除 # DELIMITER ; # drop PROCEDURE insert_emp;
创建往dept表中插入数据的存储过程
#执行存储过程,往dept表添加随机数据 DELIMITER $$ CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10)) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO dept (deptno ,dname,loc ) VALUES (START +i ,rand_string(10),rand_string(8)); UNTIL i = max_num END REPEAT; COMMIT; END $$ #删除 # DELIMITER ; # drop PROCEDURE insert_dept;
5.调用存储过程
dept:
DELIMITER ;
CALL insert_dept(100,10);
emp:
#执行存储过程,往emp表添加50万条数据
DELIMITER ; #将 结束标志换回 ;
CALL insert_emp(100001,500000);
CALL insert_emp10000(100001,10000);
6. 大量数据案例
#查询 部门编号为101的,且员工编号小于100100的用户,按用户名称排序
#结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
#开始优化:
思路: 尽量让where的过滤条件和排序使用上索引
但是一共两个字段(deptno,empno)上有过滤条件,一个字段(ename)有索引
1、我们建一个三个字段的组合索引可否?create index idx_dno_eno_ena on emp(deptno,empno,ename);
我们发现using filesort 依然存在,所以ename 并没有用到索引。
原因是因为empno是一个范围过滤,所以索引后面的字段不会再使用索引了所以
drop index idx_dno_eno_ena on emp;
但是我们可以把索引建成
create index idx_dno_ena on emp(deptno,ename);也就是说empno 和ename这个两个字段我只能二选其一。
这样我们优化掉了 using filesort。
执行一下sql速度果然提高了3倍。
.......
但是
如果我们建立
create index idx_dno_eno on emp(deptno,empno);
而放弃ename使用索引呢?果然出现了filesort,意味着排序没有用到索引。
我们来执行以下sql结果竟然有 filesort的 sql 运行速度,超过了已经优化掉 filesort的 sql ,而且快了近10倍。何故?
原因是所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<100100 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择。
结论: 当范围条件和group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量
1.是否支持,看看当前的mysql版本是否支持
Show variables like 'profiling';
2. 开启功能,默认是关闭,使用前需要开启
set profiling=1;
3. 运行SQL
select * from emp group by id%10 limit 150000;
select * from emp group by id%20 order by 5
4. 查看结果,show profiles;
5. 诊断SQL,show profile cpu,block io for query n (n为上一步前面的问题SQL数字号码);
参数备注:
type:
| ALL --显示所有的开销信息
| BLOCK IO --显示块IO相关开销
| CONTEXT SWITCHES --上下文切换相关开销
| CPU --显示CPU相关开销信息
| IPC --显示发送和接收相关开销信息
| MEMORY --显示内存相关开销信息
| PAGE FAULTS --显示页面错误相关开销信息
| SOURCE --显示和Source_function,Source_file,Source_line相关的开销信息
| SWAPS --显示交换次数相关开销的信息6 日常开发需要注意的结论
converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了
Creating tmp table 创建临时表 ,拷贝数据到临时表,用完再删除
Copying to tmp table on disk 把内存中临时表复制到磁盘,危险!!!
locked
在mysql的my.cnf中,设置如下:
#开启
general_log=1
# 记录日志文件的路径
general_log_file=/path/logfile
#输出格式
log_output=FILE
#全局日志可以存放到日志文件中,也可以存放到Mysql系统表中。存放到日志中性能更好一些,存储到表中
set global log_output='TABLE';
此后 ,你所编写的sql语句,将会记录到mysql库里的general_log表,可以用下面的命令查看
select * from mysql.general_log;
尽量不要在生产环境开启这个功能。