🌈MySQL真的就CRUD吗?✨来看看2k和12k之间的差距(下)

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战html

🌈往期回顾

    感谢阅读,但愿能对你有所帮助,博文如有瑕疵请在评论区留言或在主页我的介绍中添加我私聊我,感谢每一位小伙伴不吝赐教。我是XiaoLin,既会写bug也会唱rap的男人java

4、Explain性能分析

4.一、概述

    使用 EXPLAIN 关键字能够模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈。使用语法是:Explatn+SQL语句,他执行后返回的信息有好几列。python

image-20210526152321356

4.二、数据准备

create table course
(
cid int(3),
cname varchar(20),
tid int(3)
);
create table teacher
(
tid int(3),
tname varchar(20),
tcid int(3)
);

create table teacherCard
(
tcid int(3),
tcdesc varchar(200)
);


insert into course values(1,'java',1);
insert into course values(2,'html',1);
insert into course values(3,'sql',2);
insert into course values(4,'web',3);

insert into teacher values(1,'tz',1);
insert into teacher values(2,'tw',2);
insert into teacher values(3,'tl',3);

insert into teacherCard values(1,'tzdesc') ;
insert into teacherCard values(2,'twdesc') ;
insert into teacherCard values(3,'tldesc') ;
复制代码

4.三、id

    explain返回的结果集中的id列表示select查询的序列号,表示查询中执行 select 子句或操做表的顺序。mysql

    id 的每一个号码,表示一趟独立的查询,一个 sql 的查询趟数越少越好。web

# 查询课程编号为2或者教师编号为3的老师信息
EXPLAIN select t.* from teacher t 
left join teacherCard tc 
on tc.tcid = t.tcid
left join course c 
on c.tid = t.tid
where c.cid = 2 or t.tid = 3
复制代码

image-20210526154029652

    咱们能够发现,id值相同,从上往下顺序执行。算法

img    那要是id值不一样呢?sql

# 查询教授SQL课程的老师的描述(desc)
# 咱们若是不使用子查询的话,会发现id值仍是相同,因此下面将展现子查询的形式
EXPLAIN select tc.tcdesc from teacherCard tc where tc.tcid = 
(select t.tcid from teacher t where  t.tid =  
	(select c.tid from course c where c.cname = 'sql')
);
复制代码

image-20210526160427308

    id 不一样,若是是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行,在嵌套子查询时,先查内层 再查外层。因此先查询c表,而后是t表,最后是tc表。shell

img    那继续深刻,若是相同1又有不一样的id呢?数据库

# 查询教授SQL课程的老师的描述(desc)
# 咱们采用子查询1加多表的形式进行查询
explain select t.tname ,tc.tcdesc from teacher t,teacherCard tc where t.tcid= tc.tcid
and t.tid = (select c.tid from course c where cname = 'sql') ;
复制代码

    id值越大越优先,若id值相同,从上往下顺序执行。vim

image-20210526163447435

4.四、select_type

    select_type 表明查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。

属性 含义
SIMPLE 简单的 select 查询,查询中不包含子查询或者 UNION链接查询
PRIMARY 查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary
DERIVED 使用到了临时表
SUBQUERY 包含了子查询SQL中的子查询(非最外层)
DEPEDENT SUBQUERY 在SELECT或WHERE列表中包含了子查询,子查询基于外层
UNCACHEABLE SUBQUERY 没法使用缓存的子查询
UNION 若是有table1 union table2 ,则table1 就是derived,table2就是union
UNION RESULT 告知开发人员,那些表之间存在union查询

4.4.一、SIMPLE

    简单的 select 查询,查询中不包含子查询或者 UNION链接查询。

select * from teacher
复制代码

image-20210526170025751

4.4.二、PRIMARY

    查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary。

4.4.三、DERIVED

    使用到了临时表就会被标记为DERIVED。他有两种1状况:

  1. 在from子查询中只有一张表。
explain select  cr.cname  from ( select * from course where tid in (1,2) ) cr ;
复制代码
  1. 在from子查询中, 若是有table1 union table2 ,则table1 就是derived,table2就是union
explain select  cr.cname 	from ( select * from course where tid = 1  union select * from course where tid = 2 ) cr ;
复制代码

4.4.四、UNION

    若第二个 SELECT 出如今 UNION 以后,则被标记为 UNION,第一个SELECT会被标记为DERIVED

explain select  cr.cname 	from ( select * from course where tid = 1  union select * from course where tid = 2 ) cr ;
复制代码

image-20210526165729964

4.4.五、UNION RESULT

    告知开发人员,那些表之间存在union查询

explain select  cr.cname 	from ( select * from course where tid = 1  union select * from course where tid = 2 ) cr ;
复制代码

image-20210526165837982

4.五、table

    table代表这个数据是基于哪张表的。

# 给一条复杂一点的SQL
explain select  cr.cname 	from ( select * from course where tid = 1  union select * from course where tid = 2 ) cr ;
复制代码

image-20210526165108816

    在id = 1的查询的table中,有一个<derived2>的值,说明是这个查询用到了衍生表,衍生表的出处是id为2的衍生表。

4.六、type

    type表示的是索引类型,是较为重要的一个指标,性能从高到低依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL

    通常来讲,得保证查询至少达到 range 级别,最好能达到 ref,其中system、const是理想状况,通常很难达到,通常达到的是ref或者range。

    若是想对type优化的前提是必须有索引。

4.6.一、system

    只有一条数据的系统表或者是衍生表中只有一条数据的主查询,通常是没法达到的,忽略不计。

4.6.二、const

    表示经过索引一次就找到了,仅仅能查询到一条数据的SQL,用于Primary key 或 unique,只针对这两个索引有效。也是很难达到的。

# 建立表
create table user
(
	tid int(3),
	tname varchar(20)
);

insert into test01 values(1,'xiaolin') ;
commit;

# 添加索引
alter table test01 add constraint tid_pk primary key(tid) ;

# 测试
explain select * from (select * from test01 )t where tid =1 ;
复制代码

image-20210526172245760

4.6.三、eq_ref

    惟一性索引扫描,对于每一个索引键,表中只有一条记录与之匹配(有且只有一个,不能多也不能为0)。常见于主键或惟一索引扫描。是可遇不可求的。

explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid
复制代码

image-20210526182358229

4.6.四、ref

    非惟一性索引扫描,对于每一个索引键的查询,返回匹配的全部行。

# 先给teacher表的name字段加一个索引
alter table teacher add index index_name(tname);

# 在修改数据库表,两条语句用同一个name
explain SELECT * from teacher where tname = 'tw';
复制代码

image-20210526193422920

4.6.五、range

    只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪一个索引通常就是在你的 where 语句中出现了 between<>in (有时候会失效,会转为无索引状态)等的查询这种范围扫描索引扫描比全表扫描要好,由于它只须要开始于索引的某一点,而结束语另外一点,不用扫描所有索引。

# 给teacher的tid加一个普通索引
alter table teacher add index index_id(tid);

# 查询id小于3的老师
explain select * from teacher where tid < 3
复制代码

image-20210526195452461

4.6.六、index

    查询所有加了索引的那一列数据。

# 咱们刚刚给tid加了索引
explain select tid from teacher;
复制代码

image-20210526200118276

4.6.七、all

    查询表中的全部数据,通常是没有索引的状况。tname字段是没有索引的。

image-20210526200213992

4.6.八、总结

  • system\const:结果只有一条数据。
  • eq_ref:结果多条数据,可是每条数据是惟一的。
  • ref:结果多条,可是每条数据是0条或者多条。

4.七、possible_keys

    显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不必定被查询实际使用。

4.八、key

    实际使用的索引。若是为NULL,则没有使用索引。

4.九、key_len

    表示索引中使用的字节数,可经过该列计算查询中使用的索引的长度。 key_len 字段可以帮你检查是否充分的利用上了索引。ken_len 越长,说明索引使用的越充分。

image-20210526201202193

    key_len的计算方式:

  1. 先看索引上字段的类型+长度好比 int=4 、varchar(20) =20 、 char(20) =20。
  2. 若是是 varchar 或者 char 这种字符串字段,视字符集要乘不一样的值,好比 utf-8要乘 3,GBK 要乘 2。
  3. archar 这种动态字符串要加 2 个字节。
  4. 容许为空的字段要加 1 个字节。
  5. 若是是复合索引的话,key_len的长度是当前索引以及以前的索引之和。

4.十、ref

    指明当前表所参照的字段,若是可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。

4.十一、rows

    rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!

4.十二、Extra

    其余的额外重要的信息。

4.12.一、Using filesort

    出现这个说明你的SQL性能消耗大,须要额外的依次排序(查询),比方说有年龄、名字字段,我先经过名字查找出来,而后再根据年龄排序。

    对于单索引来讲,若是排序和查找是同一个字段,就不会出现Using filesort,反之亦然。

    对于复合索引来讲,不能跨列,要知足最佳左前缀,where和order by按照复合索引顺序使用,不要跨列或者无序使用。

# 咱们先删除掉course中cid的主键,再执行查询
EXPLAIN select * from course where tid=1 order by cid
复制代码

image-20210527101047367

4.12.二、Using temporary

    使了用临时表保存中间结果,表示性能损耗比较大。MySQL 在对查询结果排序时使用临时表。常见于排序order by 和分组查询 group by,已经有了一张表,可是不使用,必须使用额外一张表来进行存储。

    避免出现Using temporary的方法:查询哪些列就用哪些列来分组。

4.12.三、Using index

    Using index 表明表示相应的 select 操做中使用了覆盖索引(Covering Index),只要使用到的列所有都在索引中,就是索引覆盖。他**避免访问了表的数据行,性能获得了提高!**缘由在于这条SQL查询不读取源文件,只从索引文件中获取数据,不在原表中查询(不回表查询)。

  1. 若是同时出现 using where,代表索引被用来执行索引键值的查找。

  2. 若是没有同时出现 using where,代表索引只是用来读取数据而非利用索引执行查找。

  3. 若是用到了覆盖索引的时候,会对possible_keys和key形成影响:

    • 若是没有where,则索引只出如今key中。
    • 若是有where,则索引会出如今possible_keys和key中

4.12.四、Using where

    代表使用了 where 过滤(既须要从索引中去查,又须要回原表中查询)。

# 索引列id能够从索引中查询,可是除了id以外的其余列须要去原表中查询 
explain SELECT * from course c where c.tid =1
复制代码

image-20210527115242995

4.12.五、Using join buffer

    代表使用了链接缓存。

explain SELECT * from course c,teacher t where t.tid = c.tid
复制代码

image-20210527104617003

5、单表SQL优化

    建表语句:

CREATE TABLE `dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
ceo INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`empno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
复制代码

5.一、全值索引我最爱

    全值索引我最爱指的是,查询的字段按照顺序在索引中均可以匹配到!咱们要根据联合索引字段的顺序,不能出现跨列的现象。

    SQL 中查询字段的顺序,跟使用索引中字段的顺序,没有关系。优化器会在不影响 SQL 执行结果的前提下,给你自动地优化。

创建索引

create index index_age_depid_name on emp(age,deptid,name);

# 查看emp的索引,检测咱们创建索引是否成功
show index from emp
复制代码

书写SQL测试

EXPLAIN SELECT  * FROM emp WHERE emp.age=30;
复制代码

image-20210529221955036

EXPLAIN SELECT  * FROM emp WHERE emp.age=30 and deptid=4;
复制代码

image-20210529222442893

EXPLAIN SELECT  * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd';
复制代码

image-20210529222500414

5.二、最佳左前缀法则

5.2.一、索引失效状况

explain select * from emp where  deptid = 4 and name = "xiaolin"
复制代码

image-20210530143035021

    咱们能够发现,这个时候索引失效了。

5.2.二、索引有效的状况

explain select * from emp where  age = 18 and deptid = 4
复制代码

image-20210530143311904

5.2.三、总结

    查询字段与索引字段顺序的不一样会致使,索引没法充分使用,甚至索引失效!使用复合索引,须要遵循最佳左前缀法则,即若是索引了多列,要遵照最左前缀法则。指的是查询从索引的最左前列开始而且不跳过索引中的列。

    过滤条件要使用索引必须按照索引创建时的顺序,依次知足,一旦跳过某个字段,索引后面的字段都没法被使用。

5.三、不要在索引列上作任何计算

explain select * from emp where  age +1  = 18  and deptid = 4
复制代码

image-20210530143910916

    咱们能够发现,这个时候索引失效了,由于咱们在索引列——age上进行了+1的操做,咱们不能在索引列上作任何操做(计算、函数、(自动 or 手动)类型转换),由于会致使索引失效而转向全表扫描。

5.四、尽可能使用覆盖索引

    咱们先看不适用覆盖索引的状况。

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=5 AND emp.name = 'xiaolin';
复制代码

image-20210530145053614

    再看使用到了覆盖索引的状况。

image-20210530145133951

    出现了一个Using Index,说明性能获得了提高。查询列和索引列一直,**不要写 select ***

5.五、尽可能不要出现前缀模糊匹配

    在平常的使用过程当中,模糊匹配能够说是使用不少的关键字了,在使用的过程当中,咱们须要避免使用前缀的模糊匹配,由于会形成索引失效。也就是说like进行以常量开头,不要以%开头

# 先给name字段加上一个索引
create index index_name on emp(name);

# 测试后缀模糊匹配
explain select * from emp where  name like  "a%";
复制代码

image-20210530145922716

# 测试前缀模糊匹配
explain select * from emp where  name like  "%a";
复制代码

image-20210530145954424

# 测试先后都模糊匹配
explain select * from emp where  name like  "%a";
复制代码

image-20210530150158598

    咱们能够发现,只要是出现了前缀的模糊匹配的时候,都会出现索引失效的问题。若是必定须要使用%开头的模糊查询,咱们可使用索引覆盖来必定程度提升性能。

5.六、减小使用or

# 咱们使用or的时候也很容易形成索引失效的问题。
explain select * from emp where  age = 18  or deptid = 4
复制代码

image-20210530150413315

    若是咱们在实际开发中,须要使用到or的话,咱们可使用 union all 或者 union 来替代。

# 使用union all替代
explain select * from emp where  age = 18  union all select * from emp where  deptid = 4;
复制代码

image-20210530150635887

# 使用union替代
explain select * from emp where  age = 18  union select * from emp where  deptid = 4;
复制代码

image-20210530150658153

5.八、尽可能不要使用显示、隐式类型转换

# 先试一下正常状况
explain select * from emp where name="123";
复制代码

image-20210530161451102

# 再试试索引失效状况
explain select * from emp where name=123;
复制代码

image-20210530161503769

索引失效的缘由是由于mysql底层会把int类型的123转化为varchar类型的123,索引失效。

5.七、总结

全职匹配我最爱,最左前缀要遵照。 带头大哥不能死,中间兄弟不能断。 索引列上少计算,范围以后全失效。 LIKE 百分写最右,覆盖索引不写*。 不等空值还有 OR,索引影响要注意。 VAR 引号不可丢,SQL 优化有诀窍。

6、多表SQL优化

6.一、建表语句

create table teacher2
(
	tid int(4) primary key,
	cid int(4) not null
);

insert into teacher2 values(1,2);
insert into teacher2 values(2,1);
insert into teacher2 values(3,3);

create table course2
(
	cid int(4) ,
	cname varchar(20)
);

insert into course2 values(1,'java');
insert into course2 values(2,'python');
insert into course2 values(3,'kotlin');
commit;
复制代码

6.二、left join

    当咱们进行连表查询的时候,会想到一个问题,索引往哪张表加?

explain select *from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';
复制代码

image-20210530153441436

    通常状况下,咱们把数据量小的表放在左边,数据量大的表放在右边,在进行连表查询的时候,是左表驱动右表,也就是数据量小的表驱动数据量大的表,这是由于这条SQL查询的底层,其实是两个循环,一个外层循环,一个内层循环,在开发中,通常是将数据小的循环放外层,数据大的循环放内存。

    索引创建在常用的字段上,因此可得,若是是左外链接,索引创建在左表的字段,右外链接,索引创建在右表的字段。

    按照规则,咱们给teacher2这张表加上索引。

alter table teacher2 add index index_teacher2_cid(cid);
# 再次执行
explain select *from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';
复制代码

image-20210530153655103

    咱们能够发现,有一张表已经用上了索引了。通常来讲,where后面的字段也要加索引,因而咱们进一步优化。

# 给cname字段加上索引
alter table course2 add index index_course2_cname(cname);
# 再次执行
explain select *from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';
复制代码

image-20210530154000937

7、其余的优化方法

7.一、exist

    exist语法是将主查询的结果,放到子查询的进行校验(判断子查询是否有数据,若是有数据则校验成功),若是符合校验就保留数据。

select tname from teacher where exists (select * from teacher);
# 等价于
select * from teacher;
复制代码
  1. 若是主查询的数据集大,用in
  2. 若是子查询的数据集很大,用exist

7.二、order by

7.2.一、MySQL的排序算法

    咱们通常使用order by的时候都会出现using filesort。using filesort有两种算法:

  1. 双路排序:MySQL4.1以前默认使用双路排序,所谓的双路就是扫描2次磁盘。第一次从磁盘中读取排序字段,在buffer缓冲区对排序字段进行排序。第二次扫描其余字段。这种两次IO是很消耗性能的。
  2. 单路排序:MySQL4.1以后,为了减小IO访问次数,就改成了单路排序。他只读取一次所有字段,在buffer中挑出排序字段进行排序。但
# 单位是字节,若是max_length_for_sort_data值过低(须要排序的总大小超过了max_length_for_sort_data定义的字节数),MySQL会自动从单路切换到双路。
set max_length_for_sort_data = 2048;
复制代码

7.2.1.一、双路排序

    MySQL 4.1 以前是使用双路排序,字面意思就是两次扫描磁盘,最终获得数据,第一次读取行指针和 orderby 列,对他们进行排序,而后第二次扫描已经排序好的列表,按照列表中的值从新从列表中读取对应的数据出。从磁盘取排序字段,在 buffer 进行排序,再从磁盘取其余字段。

    简单来讲,取一批数据,要对磁盘进行了两次扫描,众所周知,I\O 是很耗时的,因此在 mysql4.1 以后,出现了第二种改进的算法,就是单路排序。

7.2.1.二、单路排序

    从磁盘读取查询须要的全部列,按照 order by 列在 buffer 对它们进行排序,而后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。而且把随机 IO 变成了顺序 IO,可是它会使用更多的空间, 由于它把每一行都保存在内存中了。

7.2.1.三、单路排序存在的问题

    单路排序会有必定的隐患,他有可能不是一次IO,多是屡次IO。由于若是数据量太大的话会进行数据拆分,拆分红屡次在buffer中进行排序,分片读取,屡次读取。咱们能够经过sql语句来调大buffer的大小。

7.2.二、提升order by查询的策略

7.2.2.一、增大 sort_butter_size 参数的设置

    无论用哪一种算法,提升这个参数都会提升效率,固然,要根据系统的能力去提升,由于这个参数是针对每一个进程的1M-8M 之间调整。

7.2.2.二、增大 max_length_for_sort_data 参数的设置

    mysql 使用单路排序的前提是排序的字段大小要小于max_length_for_sort_data。

    提升这个参数,会增长用改进算法的几率。可是若是设的过高,数据总容量超出 sort_buffer_size 的几率就增大,明显症状是高的磁盘 I/O 活动和低的处理器使用率。(1024-8192 之间调整)。

7.2.2.三、减小 select 后面的查询的字段

    当 Query 的字段大小总和小于 max_length_for_sort_data 并且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 不然用老算法——多路排序。

    两种算法的数据都有可能超出 sort_buffer 的容量,超出以后,会建立 tmp 文件进行合并排序,致使屡次 I/O,可是用单路排序算法的风险会更大一些,因此要提升 sort_buffer的大小。

    因此千万不要使用select * ...;

7.2.2.四、使用覆盖索引

    SQL 只须要经过索引就能够返回查询所须要的数据,而没必要经过二级索引查到主键以后再去查询数据。

7.2.2.五、保证排序的一致性

    咱们要保证所有的排序字段排序的一致性,要么所有升序,要么所有降序,不要出现某些部分升序,某些部分降序。

8、慢查询日志

8.一、慢查询日志

8.1.一、是什么

    MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。

    具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。

    由他来查看哪些SQL超出了咱们的最大忍耐时间值,好比一条sql执行超过5秒钟,咱们就算慢SQL,但愿能收集超过5秒的sql,结合以前explain进行全面分析。

8.1.二、怎么用

    默认状况下,MySQL 数据库没有开启慢查询日志,须要咱们手动来设置这个参数。

    固然,若是不是调优须要的话,通常不建议启动该参数,由于开启慢查询日志会或多或少带来必定的性能影响。慢查询日志支持将日志记录写入文件。通常在开发的时候打开,上线部署的时候关闭。

# 检查是否开启了慢查询日志,默认是off表示未开启。
show variables like '%slow_query_log';
复制代码

image-20210620160710116

# 临时开启,在内存中开启,MySQL服务关闭时就关闭了
set global slow_query_log = 1;
复制代码

image-20210620160829916

# 永久开启,须要在MySQL的配置文件中进行编辑
# 进入MySQL的配置文件
vim /etc/my.cnf
复制代码

在[mysqld]中添加两行配置。

image-20210620161452157

# 开启慢查询日志
slow_query_log=1
# 指定慢查询日志的存放路径
slow_query_log_file=/var/lib/mysql/localhost-slow.log
复制代码

image-20210620161607808

# 查询慢查询的阈值
show variables like '%long_query_time%';
复制代码

image-20210620161904875

# 设置慢查询阈值
# 临时设置,设置完毕后,须要从新登录后才生效
set global long_query_time = 5;
复制代码

image-20210620162128482

# 永久开启,须要在MySQL的配置文件中进行编辑,步骤和设置是否开启慢查询相同,只是写的参数不一样。
# 进入MySQL的配置文件
vim /etc/my.cnf
# 在[mysqld]下面追加
long_query_time=3
复制代码
# 查询超过慢查询阈值的sql数量
# 睡眠4s,模拟一条超过了4s的SQL
select sleep(4);
show global status like '%slow_queries%';
复制代码

image-20210620163625563

# 若是咱们想知道具体是哪条SQL的话,咱们须要去刚刚指定的慢查询日志文件中进行查询
cat /var/lib/mysql/localhost-slow.log
复制代码

image-20210620164508302

8.二、日志分析工具 mysqldumpslow

    咱们能够发现用原生的慢查询日志十分不友好,咱们能够经过mysql自带的日志分析工具 mysqldumpslow来分析慢查询。

# 在Linux中查看mysqldumpslow的帮助信息
mysqldumpslow --help
复制代码
参数 描述
-s 是表示按照何种方式排序
c 访问次数
l 锁定时间
r 返回记录
t 查询时间
al 平均锁定时间
ar 平均返回记录数
at 平均查询时间
-t 返回前面多少条的数据
-g 后边搭配一个正则匹配模式,大小写不敏感的
# 获得返回记录集最多的 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
复制代码

9、锁

9.一、什么是锁机制

    在MySQL中有不少种类型的锁,好比咱们最熟悉的行锁,那这里有个问题,为何MySQL中会有行锁呢?其实缘由就是:MySQL要保证数据的一致性。当数据 update 时首先要进行当前读(读取最新的数据)获得数据,并且要保证查出来的数据到更改完成的这段时间内数据不会被其余事务更改。这样的话你的 update 语句执行获得的结果和语义上是“一致的”。

9.二、锁的分类

  • 根据操做类型分:
    1. 读锁(共享锁):对同一个数据,多个读操做能够同时进行,互不干扰。
    2. 写锁(互斥锁):若是当前写操做没有完毕,则没法进行其余的读(至关于A在买衣服的时候把本来在前台展现的衣服带到了是试衣间里面了,B连看都没法看了)写操做。
  • 操做范围:
    1. 表锁:一次性锁一整张表,对一张表总体枷锁,粒度粗。MyISAM存储引擎使用的是表锁,开销小,加锁快,无死锁,可是锁的范围大,容易发生锁冲突,并发度低。
    2. 行锁:一次性对一条数据加锁,粒度细不容易发生冲突,InnoDB存储引擎使用的是行锁,开销大,加锁慢,容易出现死锁,锁的范围小,并发度高很小几率发生脏读、幻读、不可重复读等高并发问题。
    3. 页锁

9.三、锁的操做

/* MYSQL/SQLSERVER 支持自增,Oracle 须要借助于序列来实现自增 */
create table tablelock
(
id int primary key auto_increment,
name varchar(20)
) engine myisam;

insert into tablelock(name) values('a1');
insert into tablelock(name) values('a2');
insert into tablelock(name) values('a3');
insert into tablelock(name) values('a4');
insert into tablelock(name) values('a5');
复制代码

9.3.一、表锁

9.3.1.一、加读/写锁

# 给表加读锁或者写锁,能够给多张表一块儿加,语法格式为
lock table1 read/writelock table2 read/write;
复制代码

9.3.1.二、查看锁

# 查看加锁的表,1表明加了锁
show open tables;
复制代码

9.3.1.三、释放锁

# 释放锁
unlock tables;
复制代码

9.3.1.四、分析表锁定的严重程度

show status like 'table%';
复制代码

    他的结果有两行数据:

  • Table_locks_immedicate:表示能够马上获取到的锁数量

  • Tbale_locks_waited:表示须要等待的表锁数,他的值越大说明锁竞争越激烈

通常建议用Table_locks_immedicate/Tbale_locks_waited的值来衡量,若是大于5000,采用InnoDB引擎,不然使用MyISAM引擎。

9.3.1.五、总结

  • 若是某一个会话对A表加了读锁,则该会话能够对A表进行读操做,可是不能够进行写操做,该会话不能够对除A表外的其余表进行任何读写操做。

  • 简单来讲,给A表加了读锁,则当前会话只能对A表进行读操做。

  • 其余会话能够对该表进行读操做,也能够进行写操做,可是在进行写操做的时候须要等带加锁的会话释放锁。

  • 对一个加写锁的会话,当前会话能够对加了写锁的表进行任何增删改查的操做,可是不能对其余表进行增删改查操做。其余会话得等当前会话释放了锁以后才能够进行增删改查操做。

9.3.二、行锁

    行锁,一次锁一行数据,所以 若是操做的是不一样数据,则不干扰。

create table linelock
(
id int(5) primary key auto_increment,
name varchar(20)
)engine=innodb;

insert into linelock(name) values('1');
insert into linelock(name) values('2');
insert into linelock(name) values('3');
insert into linelock(name) values('4');
insert into linelock(name) values('5');
复制代码

    为了研究行锁,咱们须要暂时将自动提交关闭,方便咱们手动提交。

set autocommit = 0;
复制代码

9.3.2.一、行锁总结

  1. 表锁是经过unlock tables来进行解锁,也能够经过事务解锁 ;。而行锁是经过事务(commit/rollback)解锁。
  2. 若是会话x对某条数据a进行 DML操做(研究时:关闭了自动commit的状况下),则其余会话必须等待会话x结束事务(commit/rollback)后 才能对数据a进行操做。

9.3.2.二、行锁的注意事项

若是没有索引,则行锁会转为表锁。

show index from linelock ;
alter table linelock add index idx_linelock_name(name);
# 索引未失效
# 会话0进行写操做
update linelock set name = 'ai' where name = '3' ;
# 会话1进行写操做,不一样的数据
update linelock set name = 'aiX' where name = '4' ;

# 索引失效(发生了索引类型转换)
# 会话0进行写操做
update linelock set name = 'ai' where name = 3 ;
# 会话1对不一样的数据进行写操做
update linelock set name = 'aiX' where name = 4 ;
复制代码

    能够发现,数据被阻塞了(加锁),由于索引类型发生了类型转换致使了索引失效,所以这次操做会从行锁转为表锁。

行锁存在一种极为特殊的状况

值在范围内,可是却不存在,这种称为间隙锁。好比咱们在linelock表中没有id=7的数据,当咱们写一条SQL的时候:update linelock set name ='x' where id >1 and id<9 ;,在中国where的范围内,没有id=7在这个范围内,可是没有id=7的数据,则id=7的数据成为了间隙。MySQL会自动给间隙加锁,名为间隙锁,同时他也是行锁。即MySQL会自动给id=7的数据加间隙锁(行锁)。

9.3.2.三、关闭自动提交的四种方式

  1. set autocommit =0 ;
  2. start transaction ;
  3. begin ;
  4. 在sql后加for update。

    咱们也能够在查询的时候加行锁,只需使用第四种方式。

# 经过for update对query语句进行加锁。
select * from linelock where id =2 for update ;
复制代码

9.3.2.四、行锁分析

    咱们可使用SQL语句来分析行锁。

show status like '%innodb_row_lock%' ;
复制代码

    他有四个参数:

  1. Innodb_row_lock_current_waits :当前正在等待锁的数量。
  2. Innodb_row_lock_time:等待总时长。从系统启到如今 一共等待的时间。
  3. Innodb_row_lock_time_avg :平均等待时长。从系统启到如今平均等待的时间。
  4. Innodb_row_lock_time_max :最大等待时长。从系统启到如今最大一次等待的时间。
  5. Innodb_row_lock_waits :等待次数。从系统启到如今一共等待的次数
相关文章
相关标签/搜索