分析:
1、至少跑一天,看看生产慢SQL情况
2、开启慢查询日志,设置阈值,比如超过5秒的就是慢SQL,并将它抓取出来。
3、explain+慢SQL分析
4、show profile(分析效果与explain差不多)
5、运维经理 or DBA 进行SQL数据库服务器的参数调优。
小表驱动大表
即小的数据集驱动大的数据集
in:
select * from A where id in (select id from B)
这个语句,等价于先查询B表,再查询A表。小表应该是B,大表应该是A。B驱动A
exists:
select * from A where exists(select 1 from B where B.id = A.id)
现从A做一个查询,之后在查询B表。
意思是查看A表记录是否与B表满足 B.id = A.id,如果是,则返回一条记录;如果否,则不返回。
这个语句,先查询A,再查询B。因此,A应该是小表。B应该是大表。 A驱动B
select ... from table where exists(subquery)
该语句理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(true 或者Flase)来决定主查询的数据结果是否予以保留。
hint:
1、exists(subquery)只返回true 或者Flase,因此子查询中select 1 或者select 没有区别。
2、exists子查询实际上经过了优化而不是我们理解的逐条对比。
3、exists子查询往往也可以用条件表达式、其他子查询后者join来代替,何种最优需要具问具分。
order by(讨论会不会产生filesort):
order by尽量使用index而不是filesort排序。
建表:
age ,birth两个字段。 建立索引age,birth
1、
select * from tbla where age > 20 order by age;
以前关注用没用索引,现在关注order by后面会不会产生filesort。这句话不会差生filesort。
2、
select * from tbla where age > 20 order by age,birth;order by age,birth
用到了索引排序,因此不会产生filesort
3、
select * from tbla where age > 20 order by birth;
产生filesort,order by后面缺少带头大哥,因为age所引用到了但是是一个范围,因此不能“从一楼到二楼”。
4、
select * from tbla where age > 20 order by birth,age;
产生filesort,这就不像where后面的and,顺序反了可以优化,order by后面顺序反了不会优化。
5、
select * from tbla order by birth;
产生filesort,带头大哥没有
6、
select * from tbla where birth > '2020-04-23 00:00:00' order by birth;
产生filesort,带头大哥没有
7、
select * from tbla where birth > '2020-04-23 00:00:00' order by age;
不会产生filesort,order by后面用到了索引排序。
8、
select * from tbla order by age ASC,birth DESC;
带头大哥在,顺序和索引相同。但仍会产生filesort,要么同升,要么同降。
总结:
MySQL支持两种方式排序:index和filesort。index效率要高。
order by满足下列两个情况会用到索引排序:
1、order by后面的语句使用索引最左前缀匹配原则。
2、使用where子句与order by子句条件列组合满足最左前缀匹配原则
如果用不到index排序,filesort排序会有两种算法:
双路排序:MySQL4.1之前使用双路排序,两次扫描磁盘得到数据。
单路排序:从磁盘读取所有列,按照order by列在buffer对他们进行排序,然后扫描排序后的列表进行输出,它的效率更快些,避免了第二次读取数据。并且把随机IO变成顺序IO。
单路总体而言好于双路。
提高order by速度:
1、order by时候尽量不要用select *。
2、尝试提高sort_buffer_size
3、尝试提高max_length_for_sort_data
group by:
group by实质是线排序后分组,遵循索引建立的最左前缀原则
where 优先于HAVING。能写where就不要用HAVING。
慢查询日志:
MySQL慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
long_query_time默认10秒。
默认MySQL没有开启慢查询日志。没事不要开启,开启会影响性能。
查看开启:SHOW VARIABLES LIKE ‘%slow_query_log%’
开启:
set global slow_query_log = 1 (仅对当前数据库有效,重启失效)
查看阈值:
SHOW global VARIABLES LIKE ‘%long_query_time%’
设置阈值:
set global long_query_time = 10(表示10秒)
可使用select sleep(6)验证。
mysqldumpslow:官方提供的日志分析工具
得到返回记录最多的10个SQL:
mysqldumpslow -s r -t 10 /var/lib/mysql/XXXXX-slow.log
得到访问次数最多的10个SQL:mysqldumpslow -s c -t 10 /var/lib/mysql/XXXXX-slow.log
得到按时间排序前10条里面含有左连接的查询语句:mysqldumpslow -s t -t 10 -g “left join” /var/lib/mysql/XXXXX-slow.log
存储过程:
DELIMITER $$
意思是将结束符改为$$。
show profile:
MySQL调优思路:
step1:察觉到系统变慢
step2:给SQL跑一圈(重现故障)利用慢查询日志
step3:采取explain分析具体SQL
step4:第三部没摆平,使用show profile
show profile是MySQL提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL调优的测量。
默认情况下是关闭状态。
查看show profile是否开启:
show variables like ‘profiling’;
开启show profile:
set profiling=on;
开启之后,show profile会在后台自动抓取查询信息。
查看所有执行过的SQL。
show profiles;
诊断SQL:
show profile cpu,block io for query Query_ID(show profiles里面的id);
这句话诊断出一条SQL生命周期里发生的所有事件以及发生的时长。
上述SQL中,cpu和block IO是可替换项:可添加一个或者多个,中间用逗号隔开。
type:
| ALL --显示所有的开销信息
| BLOCK IO --显示块IO相关开销
| CONTEXT SWITCHES --上下文切换相关开销
| CPU --显示CPU相关开销信息
| IPC --显示发送和接收相关开销信息
| MEMORY --显示内存相关开销信息
| PAGE FAULTS --显示页面错误相关开销信息
| SOURCE --显示和Source_function,Source_file,Source_line相关的开销信息
| SWAPS --显示交换次数相关开销的信息
在status状态下出现以下item说明有问题:
1、converting HEAP to MyISAM 查询结果太大了,内存都不够用往磁盘上搬了
2、creating tmp table 创建临时表(拷贝数据到临时表,用完再删除)
3、copying to tmp table on disk 把内存中临时表复制到磁盘,危险!!
4、locked
MySQL锁:
对数据操作类型分:(读锁/写锁)
读锁:针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
对锁的粒度分:
表锁:
开销小、加锁快,无死锁,锁的粒度大,发生锁冲突的概率最高,并发度最低。
建表:
create table mylock(
id int primary key auto_increment,
name varchar(20),
) engine myisam;
插入数据:
insert into mylock(name) values ('a'),('b'),('c'),('d'),('e');
开两个终端(链接数据库):
手动增加表锁:
lock table 表名字 read/write , 表名字2 read/write , 其他;
解锁:
unlock tables;(所有表全解锁了)
查看表是否加过锁:
show open tables;
database tables in_use name_locked
是否加锁0/1
如何分析表锁定:
show status like ‘table%’;
table_locks_immediate:产生表级锁定次数,表示可以立即获取锁的查询次数,每立即获得锁值加1。
table_locks_waited:出现表级锁定争用而发生的等待次数,此值高说明存在着较为严重的表级锁争用情况。
MyISAM读写锁调度是写锁优先(即读锁写锁竞争时,写锁能抢到),由于写锁特性,写锁优先会导致其他进程读写永远阻塞。
读锁:
session1对表mylock加读锁。
session1可以读mylock,不能写mylock,不能读写其他未锁定的表。
session2可以读mylock,写mylock时会进入阻塞状态,直到session释放锁才执行写操作。可以读写其他未锁定的表。
写锁:
session1对表mylock加写锁。
session1可以读写mylock,不能读写其他未加锁的表
session2可以读写其他未加锁的表,读写mylock会阻塞,等待session释放锁。
简而言之(针对session2):读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞。
行锁(innodb默认加锁?):
开销大、加锁慢、会出现死锁;锁粒度最小,发生锁冲突概率最低,并发度也高。InnoDB和MyISAM区别:1、InnoDB支持事务。2、InnoDB采用了行级锁。
课堂实验:
建表:
create table test_innodb_lock(a int(11) , b varchar(16))engine = innodb;
insert into test_innodb_lock values(1,'b2'),(3,'3'),(4,'4000'),(5,'5000'),(6,'6000'),(7,'7000'),(8,'8000'),(9,'9000'),(1,'b1');
建索引:
create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_lock_b_ind on test_innodb_lock(b);
设置自动提交为0;
set autocommit = 0;
在session1中更改数据(未提交),session1自己看到的数据更改了,但是session2看得的数据是原来的数据。
两个session更改同一行数据:
两个session更改不同行数据:
可自由更改无影响。
索引建立之后使用不当导致行锁升级为表锁
case:
在session1中执行下列语句:
update test_innodb_lock set a = 10 where b = 4000;
由于b是varchar而where后面没有加单引号,因此导致索引失效。此时,行锁变表锁。seesion2中再去写会被阻塞。
间隙锁:
当我们用范围条件而不是相等条件检索数据时,会给这个范围内的所有键值加锁。
例如:
表如图所示:
session1执行:
update test_innodb_lock set b = ‘0987’ where a >1 and a<6;
此时,未提交,MySQL会将a>1 ,<6的键值全上锁。(即使没有a=2的记录)。
seesion2执行:
insert into test_innodb_lock values(2,‘2000’);
commit;
由于seesion1没有commit,所以seesion2执行此句后会阻塞,直到seesion1 commit之后释放锁。
如何锁定一行:
seesion1执行:
select * from test_innodb_lock where a = 8 for update;
手工为a=8这条记录上锁。
session2更改a=8这行数据会阻塞,直到session1执行commit。