MySQL连接查询详解、explain分析、性能优化

一.Join连接查询

7种join连接查询情况
假设创建员工表(employee)和部门表(dept)

// 创建部门表
create table dept(id int primary key auto_increment, name varchar(30))// 创建员工表
create table employee (
	id int primary key auto_increment, 
	name varchar(20), 
	dept_id int, 
	foreign key(dept_id) references dept(id)
	);
// 插入部门信息
insert into dept(name) values('测试部');
insert into dept(name) values('研发部');
insert into dept(name) values('人力部');
insert into dept(name) values('管理部');
// 插入员工信息
insert into employee(name,dept_id) values('zhangsan',1);
insert into employee(name,dept_id) values('zhangzan',1);
insert into employee(name,dept_id) values('zhangwan',1);
insert into employee(name,dept_id) values('lisi',2);
insert into employee(name,dept_id) values('lizi',2);
insert into employee(name,dept_id) values('lezi',2);
insert into employee(name,dept_id) values('fezi',2);
insert into employee(name,dept_id) values('wangwu',3);
insert into employee(name,dept_id) values('wangqu',3);
insert into employee(name,dept_id) values('wangcu',3);
insert into employee(name,dept_id) values('wangzu',3);
insert into employee(name,dept_id) values('ziwei',null);
insert into employee(name,dept_id) values('erkang',null);

表信息如下:

1.内连接(inner join)

内连接
使用 inner join 就可以查询出两表的共同部分
查询SQL:

select * from employee inner join dept on employee.dept_id = dept.id;

查询结果:
在这里插入图片描述
可以发现在员工表中的两个员工(ziwei、erkang)未查出,因为没有在两表的共同部分

2.左连接(left join)


查询两表共同部分包括左表
查询SQL:

select * from employee left join dept on employee.dept_id = dept.id;

查询结果:

可以发现这次出现了两个员工数据

3.右连接(right join)


和左连接类似,查询两表共同部分以及右表全部
查询SQL:

select * from employee right join dept on employee.dept_id = dept.id;

在索引优化的时候可能会遇到两表索引创建不同,这个时候就可能需要改变左右查询的顺序,后面会提到
查询结果:

4.左连接不包含两表共同部分


思路很简单就是添加where条件就可以将共同部分去除
(即连接右表外键为null 或 右表查出结果id(主键)为null)
查询SQL:

// 判断为空是: is null
select * from employee left join dept on employee.dept_id = dept.id where dept.id is null;

查询结果:

5.右连接不包含两表共同部分


和上面思路相同,只是改变判断条件
查询SQL:

// 判断为空是: is null
select * from employee right join dept on employee.dept_id = dept.id where employee.id is null;

查询结果:

6.左右表的共同内容


这个时候可能会想到 union 这个关键字:
union 连接两表自动去除重复部分、union all 连接两表不会去除重复部分

思路:将左连接查到的和右连接查到的union
查询SQL:

select * from employee left join dept on employee.dept_id = dept.id 
union 
select * from employee right join dept on employee.dept_id = dept.id;

查询结果:

7.左表部分和右表部分


这个就和上面思路一样了
将A表部分和B表部分 union
查询SQL:

select * from employee left join dept on employee.dept_id = dept.id where dept.id is null 
union 
select * from employee right join dept on employee.dept_id = dept.id where employee.id is null;

查询结果:

二.explain分析

如果不懂索引的先跳到下一部分学习了解一下索引,在回头来看。

1.explain简介

使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析查询语句或是表结构的性能瓶颈 (在5.6以及以后的版本中,除过select,其他比如insert,update和delete均可以使用explain查看执行计划)

通过explain,可以分析出以下结果:

  • 标的读取顺序(id)
  • 数据读取操作的操作类型(type)
  • 哪些索引可以使用(possible_keys)
  • 哪些索引被实际使用(key)
  • 表之间的引用(table)
  • 每张表有多少行被优化器查询(rows)

2.explain用法

explain + SQL语句

explain select * from dept;

执行计划包含信息
在这里插入图片描述

3.执行计划各字段含义

➤ id

查询的序号,包含一组数字,表示查询中执行select子句或操作表的顺序

三种情况:
① id相同:执行顺序从上往下
② id不同:id值越大,优先级越高,越先执行
③ id相同不同:存在相同的id和不同的id(如下图)
在这里插入图片描述
在id为1时,table显示的是 ,这里指的是指向id为2的表,即t3表的衍生表。

➤ select_type *

查询类型,主要用于区别普通查询,联合查询,子查询等的复杂查询。
在这里插入图片描述
① simple
简单的select查询,查询中不包含子查询或者UNION
② primary
查询中若包含任何复杂的子部分,最外层查询被标记primary
③ subquery
在select或where列表中包含了子查询
④ derived
在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放到临时表
⑤ union
如果第二个select出现在UNION之后,则被标记为UNION,如果union包含在from子句的子查询中,外层select被标记为derived
⑥ union result
从UNION表获取结果的SELECT

➤ table *

当前执行的表

➤ type *

显示联结类型,显示查询使用了何种类型,按照从最佳到最坏类型排序
(这个信息比较重要,一般查看SQL语句好坏就需要看这个属性)
在这里插入图片描述
从最佳到最坏类型排序:
system > const > eq_ref > ref > range > index > all
注意:一般保证查询至少达到range级别,最好能达到ref。

system
表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计。

const
表示通过索引一次就找到,const用于比较primary key或者unique索引。(因为只匹配一行数据,所以如果将主键置于where列表中,mysql能将该查询转换为一个常量)
在这里插入图片描述
从id可以看出执行子查询,子查询t1表条件id=1(常量),所以type为const,得到一个结果存放在临时表d1,因为d1只有一条数据,所以type为system。

eq_ref
唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。
(常见于唯一索引或者主键扫描)

ref
非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,可能会找多个符合条件的行,属于查找和扫描的混合体。
在这里插入图片描述
range
只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是where语句中出现了between,in等范围的查询
(这种范围扫描索引扫描比全表扫描要好,因为它开始于索引的某一个点,而结束另一个点,不用全表扫描)
在这里插入图片描述
index
Full Index Scan,Index与All区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。
(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘读取的)
在这里插入图片描述
all
Full Table Scan 将遍历全表以找到匹配的行
(查询中未使用索引键,直接全表扫描,这种情况是必须优化SQL语句的)

➤ possible_keys 和 key *

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

key

  • 实际使用的索引,如果为NULL,则没有使用索引。
    (可能原因包括没有建立索引或索引失效)
    在这里插入图片描述
  • 查询中若使用了覆盖索引(select 后要查询的字段刚好被创建的索引字段所包含 - 符合左前缀原则),则该索引仅出现在key列表中。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    但是单独查看dept_id时,就会出现key为null的情况。

➤ key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。(一般精度越高,长度越长)
在这里插入图片描述

➤ ref

显示索引的哪一列被使用了,如果有可能是一个常数,哪些列或常量被用于查询索引列上的值。
在这里插入图片描述

➤ rows

根据表统计信息以及索引选用情况,大致估算出找到所需的记录所需要读取的行数。(越少越好)
在这里插入图片描述

➤ Extra

包含不适合在其他列中显式但十分重要的额外信息
Using filesort(九死一生)
说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。
在这里插入图片描述
Using temporary(十死无生)
使用了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。
在这里插入图片描述
Using index(SQL语句还不错)
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
在这里插入图片描述
在这里插入图片描述
后面几个不太重要,了解即可。
Using where
表明使用了where过滤
Using join buffer
表明使用了连接缓存,比如说在查询的时候,多表join的次数非常多,那么将配置文件中的缓冲区的join buffer调大一些。
impossible where
where子句的值总是false,不能用来获取任何元组
select tables optimized away
在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
distinct
优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作

拓展:MySQL逻辑架构和SQL语句执行顺序

1.MySQL逻辑架构

和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其他的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
在这里插入图片描述
如上图MySQL逻辑架构可以分为下面四层:
1.连接层
最上层是一些客户端和连接服务,包括本地sock通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似与连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2.服务层
该层主要完成大多的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
3.引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。
4.存储层
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。

2.SQL语句执行顺序

下面SQL语句为手写:

select distinct 
        <select_list>
from
    <left_table><join_type>
join <right_table> on <join_condition>
where
    <where_condition>
group by
    <group_by_list>
having
    <having_condition>
order by
    <order_by_condition>
limit <limit number>

机器读取顺序:

1from <left_table><join_type>
2on <join_condition>
3<join_type> join <right_table>
4where <where_condition>
5group by <group_by_list>
6having <having_condition>
7select
8distinct <select_list>
9order by <order_by_condition>
10limit <limit_number>

总结:先获取数据(from、join),筛选数据(on、where),然后选择数据(having、select),排序数据(order by、group by),最后在限制几条数据(limit)。
在这里插入图片描述

三.性能优化

0.慢SQL优化步骤:

1.慢查询的开启并捕获
2.explain+慢SQL分析
3.show profile查询SQL在MySQL服务器里面的执行细节和生命周期
4.SQL数据库服务器的参数调优

1.索引优化

1).索引简介

定义
索引(Index)是帮助MySQL高效获取数据的数据结构。
索引的本质就是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址,在数据十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据
可以简单理解为排好序的快速查找的数据结构

2).MySQL中索引的语法

创建索引

在创建表时添加索引

CREATE TABLE mytable(  
    ID INT NOT NULL,   
    username VARCHAR(16) NOT NULL,  
    INDEX [索引名称] (字段名1, 字段名2, ..)  
); 

在创建表以后添加索引

ALTER TABLE my_table ADD [UNIQUE] INDEX index_name(字段名1, 字段名2, ..);
或者
CREATE INDEX index_name ON my_table(字段名1, 字段名2, ..);

注意:
1、索引需要占用磁盘空间,因此在创建索引时要考虑到磁盘空间是否足够
2、创建索引时需要对表加锁,因此实际操作中需要在业务空闲期间进行

删除索引

DROP INDEX my_index ON tablename;
或者
ALTER TABLE table_name DROP INDEX index_name;

查看表中的索引

SHOW INDEX FROM tablename

查看查询语句使用索引的情况

//explain 加查询语句
explain SELECT * FROM table_name WHERE column_1='123';

点击进一步了解索引

3).索引优化

学习视频老师讲的口诀总结很到位:

全值匹配我最爱,最左前缀要遵守; 带头大哥不能死,中间兄弟不能断; 索引列上少计算,范围之后全失效; Like百分写最右,覆盖索引不写星; 不等空值还有or,索引失效要少用; VAR引号不可丢,SQL高级也不难!

一一讲解每句话的意思:
全值匹配我最爱,最左前缀要遵守
在使用索引查询时,尽量多的使用索引字段,如果全部使用的是索引字段查询,那么效率会非常的高。
在这里插入图片描述
最佳左前缀:比如有复合索引(c1,c2,c3),查询时可以使用c1,c2,c3、c1,c2、c1这样的字段,只有当c1存在时索引才会有效。
带头大哥不能死
如果使用索引字段时,第一个创建的索引没有被使用,那么这样就失效了。(火车头可以单独跑,车厢不能单独跑)
在这里插入图片描述
中间兄弟不能断
在这里插入图片描述
在这里插入图片描述
两个SQL语句都是用到了索引,但观察key_len可以发现实际用到的索引字段是不同的。
第二句SQL语句因为中间索引字段并未使用,导致了从name字段后的索引字段失效,这种情况是部分索引失效。

索引列上少计算
不要在索引列上做任何操作(计算、函数、(自动或手动)类型转换),会导致索引失效进而转向全表扫描!
在这里插入图片描述
范围之后全失效
范围只能用到一部分排序,而不能用到查找,之后的索引字段也会全部失效
在这里插入图片描述
Like百分写最右
在这里插入图片描述
覆盖索引不写*
select之后尽量使用索引字段,减少select *
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
不等空值还有or
在使用 !=、<>、is null、is not null、or ,会导致索引失效,全表扫描。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
字符串不加单引号索引失效
在这里插入图片描述
非要使用"%条件%"时,可以使用覆盖索引
在这里插入图片描述
不可以的SQL语句中,因为email不是索引字段,不符合覆盖索引的条件。

2.查询优化

1).小表驱动大表

在这里插入图片描述

  • exists
    select … from table where exists(subquery)
    含义:将主查询的数据,放到子查询中做条件验证,根据验证结果(true或false)来决定主查询的数据结果是否得以保留。

2).order by关键字排序优化

尽量将排序方式转为using index 排序方式,避免使用using filesort。
在这里插入图片描述
filesort有两种算法:
① 双路排序:在MySQL4.1之前使用,需要两次扫描磁盘。
② 单路排序:按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,他的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,每一行都保存在内存中。

3).group by关键字优化

  • group by实质是先排序后分组,遵照索引的最佳左前缀
  • where高于having,能写在where限定的条件就不要去having限定
  • 其余条件与order by一样
发布了120 篇原创文章 · 获赞 16 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43327091/article/details/102685468