网站策划和运营,最好的域名注册网站,设计网站vcg,大型企业网站建设方案来源#xff1a;捡田螺的小男孩 1、MySQL的基本架构 2、SQL优化 3、explain执行计划常用关键字详解 很多朋友在做数据分析时#xff0c;分析两分钟#xff0c;跑数两小时#xff1f;
在使用SQL过程中不仅要关注数据结果#xff0c;同样要注意SQL语句的执行效率。
本文…来源捡田螺的小男孩 1、MySQL的基本架构 2、SQL优化 3、explain执行计划常用关键字详解 很多朋友在做数据分析时分析两分钟跑数两小时
在使用SQL过程中不仅要关注数据结果同样要注意SQL语句的执行效率。
本文涉及三部分 SQL介绍 SQL优化方法 SQL优化实例
1、MySQL的基本架构
1MySQL的基础架构图 左边的client可以看成是客户端客户端有很多像我们经常你使用的CMD黑窗口像我们经常用于学习的WorkBench像企业经常使用的Navicat工具它们都是一个客户端。右边的这一大堆都可以看成是Server(MySQL的服务端)我们将Server在细分为sql层和存储引擎层。
当查询出数据以后会返回给执行器。执行器一方面将结果写到查询缓存里面当你下次再次查询的时候就可以直接从查询缓存中获取到数据了。另一方面直接将结果响应回客户端。
2查询数据库的引擎
① show engines; ② show variables like “%storage_engine%”; 3指定数据库对象的存储引擎
create table tb( id int(4) auto_increment, name varchar(5), dept varchar(5), primary key(id) ) enginemyISAM auto_increment1 default charsetutf8; 基于 Spring Boot MyBatis Plus Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能 项目地址https://github.com/YunaiV/ruoyi-vue-pro 视频教程https://doc.iocoder.cn/video/ 2、SQL优化
1为什么需要进行SQL优化
在进行多表连接查询、子查询等操作的时候由于你写出的SQL语句欠佳导致的服务器执行时间太长我们等待结果的时间太长。基于此我们需要学习怎么优化SQL。
2mysql的编写过程和解析过程
① 编写过程
select dinstinct ..from ..join ..on ..where ..group by ..having ..order by ..limit ..
② 解析过程
from .. on.. join ..where ..group by ..having ..select dinstinct ..order by ..limit ..
提供一个网站详细说明了mysql解析过程
https://www.cnblogs.com/annsshadow/p/5037667.html
3SQL优化—主要就是优化索引
优化SQL最重要的就是优化SQL索引。
索引相当于字典的目录。利用字典目录查找汉字的过程就相当于利用SQL索引查找某条记录的过程。有了索引就可以很方便快捷的定位某条记录。
① 什么是索引
索引就是帮助MySQL高效获取数据的一种【数据结构】。索引是一种树结构MySQL中一般用的是【B树】。
② 索引图示说明(这里用二叉树来帮助我们理解索引)
树形结构的特点是子元素比父元素小的放在左侧子元素比父元素大的放在右侧。
这个图示只是为了帮我们简单理解索引的真实的关于【B树】的说明我们会在下面进行说明。 索引是怎么查找数据的呢两个字【指向】上图中我们给age列指定了一个索引即类似于右侧的这种树形结构。mysql表中的每一行记录都有一个硬件地址例如索引中的age50指向的就是源表中该行的标识符(“硬件地址”)。
也就是说树形索引建立了与源表中每行记录硬件地址的映射关系当你指定了某个索引这种映射关系也就建成了这就是为什么我们可以通过索引快速定位源表中记录的原因。
以【select * from student where age33】查询语句为例。当我们不加索引的时候会从上到下扫描源表当扫描到第5行的时候找到了我们想要找到了元素一共是查询了5次。
当添加了索引以后就直接在树形结构中进行查找33比50小就从左侧查询到了2333大于23就又查询到了右侧这下找到了33整个索引结束一共进行了3次查找。是不是很方便假如我们此时需要查找age62你再想想“添加索引”前后查找次数的变化情况。
4索引的弊端
1.当数据量很大的时候索引也会很大(当然相比于源表来说还是相当小的)也需要存放在内存/硬盘中(通常存放在硬盘中)占据一定的内存空间/物理空间。
2.索引并不适用于所有情况a.少量数据b.频繁进行改动的字段不适合做索引c.很少使用的字段不需要加索引
3.索引会提高数据查询效率但是会降低“增、删、改”的效率。当不使用索引的时候我们进行数据的增删改只需要操作源表即可但是当我们添加索引后不仅需要修改源表也需要再次修改索引很麻烦。尽管是这样添加索引还是很划算的因为我们大多数使用的就是查询“查询”对于程序的性能影响是很大的。
5索引的优势
1.提高查询效率(降低了IO使用率)。当创建了索引后查询次数减少了。
2.降低CPU使用率。比如说【…order by age desc】这样一个操作当不加索引会把源表加载到内存中做一个排序操作极大的消耗了资源。但是使用了索引以后第一索引本身就小一些第二索引本身就是排好序的左边数据最小右边数据最大。
6B树图示说明
MySQL中索引使用的就是B树结构。 关于B树的说明
首先Btree一般指的都是【B树】数据全部存放在叶子节点中。对于上图来说最下面的第3层属于叶子节点真实数据部份都是存放在叶子节点当中的。
那么对于第1、2层中的数据又是干嘛的呢答用于分割指针块儿的比如说小于26的找P1介于26-30之间的找P2大于30的找P3。
其次三层【B树】可以存放上百万条数据。这么多数据怎么放的呢增加“节点数”。图中我们只有三个节点。
最后【B树】中查询任意数据的次数都是n次n表示的是【B树】的高度。
3、索引的分类与创建
1索引分类
单值索引
唯一索引
复合索引
① 单值索引
利用表中的某一个字段创建单值索引。一张表中往往有多个字段也就是说每一列其实都可以创建一个索引这个根据我们实际需求来进行创建。还需要注意的一点就是一张表可以创建多个“单值索引”。
假如某一张表既有age字段又有name字段我们可以分别对age、name创建一个单值索引这样一张表就有了两个单值索引。
② 唯一索引
也是利用表中的某一个字段创建单值索引与单值索引不同的是创建唯一索引的字段中的数据不能有重复值。像age肯定有很多人的年龄相同像name肯定有些人是重名的因此都不适合创建“唯一索引”。像编号id、学号sid对于每个人都不一样因此可以用于创建唯一索引。
③ 复合索引
多个列共同构成的索引。比如说我们创建这样一个“复合索引”(name,age)先利用name进行索引查询当name相同的时候我们利用age再进行一次筛选。注意复合索引的字段并不是非要都用完当我们利用name字段索引出我们想要的结果以后就不需要再使用age进行再次筛选了。
2创建索引
① 语法
语法create 索引类型 索引名 on 表(字段);
建表语句如下
查询表结构如下 ② 创建索引的第一种方式
Ⅰ 创建单值索引
create index dept_index on tb(dept);
Ⅱ 创建唯一索引这里我们假定name字段中的值都是唯一的
create unique index name_index on tb(name);
Ⅲ 创建复合索引
create index dept_name_index on tb(dept,name);
③ 创建索引的第二种方式
先删除之前创建的索引以后再进行这种创建索引方式的测试
语法alter table 表名 add 索引类型 索引名(字段)
Ⅰ 创建单值索引
alter table tb add index dept_index(dept);
Ⅱ 创建唯一索引这里我们假定name字段中的值都是唯一的
alter table tb add unique index name_index(name);
Ⅲ 创建复合索引
alter table tb add index dept_name_index(dept,name);
④ 补充说明
如果某个字段是primary key那么该字段默认就是主键索引。
主键索引和唯一索引非常相似。相同点该列中的数据都不能有相同值不同点主键索引不能有null值但是唯一索引可以有null值。
3索引删除和索引查询
① 索引删除
语法drop index 索引名 on 表名;
drop index name_index on tb;
② 索引查询
语法show index from 表名;
show index from tb;
结果如下 4、SQL性能问题的探索
人为优化 需要我们使用explain分析SQL的执行计划。该执行计划可以模拟SQL优化器执行SQL语句可以帮助我们了解到自己编写SQL的好坏。
SQL优化器自动优化 最开始讲述MySQL执行原理的时候我们已经知道MySQL有一个优化器当你写了一个SQL语句的时候SQL优化器如果认为你写的SQL语句不够好就会自动写一个好一些的等价SQL去执行。
SQL优化器自动优化功能【会干扰】我们的人为优化功能。当我们查看了SQL执行计划以后如果写的不好我们会去优化自己的SQL。当我们以为自己优化的很好的时候最终的执行计划并不是按照我们优化好的SQL语句来执行的而是有时候将我们优化好的SQL改变了去执行。
SQL优化是一种概率问题有时候系统会按照我们优化好的SQL去执行结果(优化器觉得你写的差不多就不会动你的SQL)。有时候优化器仍然会修改我们优化好的SQL然后再去执行。
1查看执行计划
语法explain SQL语句
egexplain select * from tb;
2“执行计划”中需要知道的几个“关键字”
id 编号
select_type 查询类型
table 表
type 类型
possible_keys 预测用到的索引
key 实际使用的索引
key_len 实际使用索引的长度
ref 表之间的引用
rows 通过索引查询到的数据量
Extra 额外的信息
建表语句和插入数据 基于 Spring Cloud Alibaba Gateway Nacos RocketMQ Vue Element 实现的后台管理系统 用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能 * 项目地址https://github.com/YunaiV/yudao-cloud * 视频教程https://doc.iocoder.cn/video/ # 建表语句 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) ;
3、explain执行计划常用关键字详解
1id关键字的使用说明
① 案例查询课程编号为2 或 教师证编号为3 的老师信息
# 查看执行计划 explain select t.* from teacher t,course c,teacherCard tc where t.tid c.tid and t.tcid tc.tcid and (c.cid 2 or tc.tcid 3);
结果如下 接着在往teacher表中增加几条数据。
insert into teacher values(4,ta,4); insert into teacher values(5,tb,5); insert into teacher values(6,tc,6);
再次查看执行计划。
# 查看执行计划 explain select t.* from teacher t,course c,teacherCard tc where t.tid c.tid and t.tcid tc.tcid and (c.cid 2 or tc.tcid 3);
结果如下 表的执行顺序 因表数量改变而改变的原因笛卡尔积。
a b c 2 3 4 最终2 * 3 * 4 6 * 4 24 c b a 4 3 2 最终4 * 3 * 2 12 * 2 24
分析最终执行的条数虽然是一致的。但是中间过程有一张临时表是6一张临时表是12很明显6 12对于内存来说数据量越小越好因此优化器肯定会选择第一种执行顺序。
结论id值相同从上往下顺序执行。表的执行顺序因表数量的改变而改变。
② 案例查询教授SQL课程的老师的描述(desc)
# 查看执行计划 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) );
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
结论id值不同id值越大越优先查询。这是由于在进行嵌套子查询时先查内层再查外层。
③ 针对②做一个简单的修改
# 查看执行计划 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值越大越优先id值相同从上往下顺序执行。
2select_type关键字的使用说明查询类型 ① simple简单查询
不包含子查询不包含union查询。
explain select * from teacher;
结果如下 ② primary包含子查询的主查询(最外层)
③ subquery包含子查询的主查询(非最外层)
④ derived衍生查询(用到了临时表)
a.在from子查询中只有一张表
b.在from子查询中如果table1 union table2则table1就是derived表
explain select cr.cname from ( select * from course where tid 1 union select * from course where tid 2 ) cr ;
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
⑤ unionunion之后的表称之为union表如上例
⑥ union result告诉我们哪些表之间使用了union查询
3type关键字的使用说明索引类型
system、const只是理想状况实际上只能优化到index -- range -- ref这个级别。要对type进行优化的前提是你得创建索引。
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
① system
源表只有一条数据(实际中基本不可能)
衍生表只有一条数据的主查询(偶尔可以达到)。
② const
仅仅能查到一条数据的SQL ,仅针对Primary key或unique索引类型有效。
explain select tid from test01 where tid 1 ;
结果如下 删除以前的主键索引后此时我们添加一个其他的普通索引
create index test01_index on test01(tid) ; # 再次查看执行计划 explain select tid from test01 where tid 1 ;
结果如下 ③ eq_ref
唯一性索引对于每个索引键的查询返回匹配唯一行数据有且只有1个不能多 、不能0并且查询结果和数据条数必须一致。
此种情况常见于唯一索引和主键索引。
delete from teacher where tcid 4; alter table teacherCard add constraint pk_tcid primary key(tcid); alter table teacher add constraint uk_tcid unique index(tcid) ; explain select t.tcid from teacher t,teacherCard tc where t.tcid tc.tcid ;
结果如下 总结以上SQL用到的索引是t.tcid即teacher表中的tcid字段如果teacher表的数据个数和连接查询的数据个数一致都是3条数据则有可能满足eq_ref级别否则无法满足。条件很苛刻很难达到。
④ ref
非唯一性索引对于每个索引键的查询返回匹配的所有行可以0可以1可以多
准备数据 创建索引并查看执行计划
# 添加索引 alter table teacher add index index_name (tname) ; # 查看执行计划 explain select * from teacher where tname tz;
结果如下 ⑤ range
检索指定范围的行 ,where后面是一个范围查询(between, , , , in)
in有时候会失效从而转为无索引时候的ALL
# 添加索引 alter table teacher add index tid_index (tid) ; # 查看执行计划以下写了一种等价SQL写法查看执行计划 explain select t.* from teacher t where t.tid in (1,2) ; explain select t.* from teacher t where t.tid 3 ;
结果如下 ⑥ index
查询全部索引中的数据(扫描整个索引)
⑦ ALL
查询全部源表中的数据(暴力扫描全表) 注意cid是索引字段因此查询索引字段只需要扫描索引表即可。但是tid不是索引字段查询非索引字段需要暴力扫描整个源表会消耗更多的资源。
4possible_keys和key
possible_keys可能用到的索引。是一种预测不准。了解一下就好。
key指的是实际使用的索引。
# 先给course表的cname字段添加一个索引 create index cname_index on course(cname); # 查看执行计划 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) ;
结果如下 有一点需要注意的是如果possible_key/key是NULL则说明没用索引。
5key_len
索引的长度用于判断复合索引是否被完全使用(a,b,c)。
① 新建一张新表用于测试
# 创建表 create table test_kl ( name char(20) not null default ); # 添加索引 alter table test_kl add index index_name(name) ; # 查看执行计划 explain select * from test_kl where name ;
结果如下 结果分析因为我没有设置服务端的字符集因此默认的字符集使用的是latin1对于latin1一个字符代表一个字节因此这列的key_len的长度是20表示使用了name这个索引。
② 给test_kl表新增name1列该列没有设置“not null”
结果如下 结果分析如果索引字段可以为null则mysql底层会使用1个字节用于标识。
③ 删除原来的索引name和name1新增一个复合索引
# 删除原来的索引name和name1 drop index index_name on test_kl ; drop index index_name1 on test_kl ; # 增加一个复合索引 create index name_name1_index on test_kl(name,name1); # 查看执行计划 explain select * from test_kl where name1 ; --121 explain select * from test_kl where name ; --60
结果如下 结果分析 对于下面这个执行计划可以看到我们只使用了复合索引的第一个索引字段name因此key_len是20这个很清楚。再看上面这个执行计划我们虽然仅仅在where后面使用了复合索引字段中的name1字段但是你要使用复合索引的第2个索引字段会默认使用了复合索引的第1个索引字段name由于name1可以是null因此key_len 20 20 1 41呀
④ 再次怎加一个name2字段并为该字段创建一个索引。
不同的是该字段数据类型是varchar
# 新增一个字段name2name2可以为null alter table test_kl add column name2 varchar(20) ; # 给name2字段设置为索引字段 alter table test_kl add index name2_index(name2) ; # 查看执行计划 explain select * from test_kl where name2 ;
结果如下 结果分析 key_len 20 1 2这个20 1我们知道这个2又代表什么呢原来varchar属于可变长度在mysql底层中用2个字节标识可变长度。
6ref
这里的ref的作用指明当前表所参照的字段。
注意与type中的ref值区分。在type中ref只是type类型的一种选项值。
# 给course表的tid字段添加一个索引 create index tid_index on course(tid); # 查看执行计划 explain select * from course c,teacher t where c.tid t.tid and t.tname tw;
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
结果分析 有两个索引c表的c.tid引用的是t表的tid字段因此可以看到显示结果为【数据库名.t.tid】t表的t.name引用的是一个常量tw因此可以看到结果显示为const表示一个常量。
7rows(这个目前还是有点疑惑)
被索引优化查询的数据个数 (实际通过索引而查询到的数据个数)
explain select * from course c,teacher t where c.tid t.tid and t.tname tz ;
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
8extra
表示其他的一些说明也很有用。
① using filesort针对单索引的情况
当出现了这个词表示你当前的SQL性能消耗较大。表示进行了一次“额外”的排序。常见于order by语句中。
Ⅰ 什么是“额外”的排序
为了讲清楚这个我们首先要知道什么是排序。我们为了给某一个字段进行排序的时候首先你得先查询到这个字段然后在将这个字段进行排序。
紧接着我们查看如下两个SQL语句的执行计划。
# 新建一张表建表同时创建索引 create table test02 ( a1 char(3), a2 char(3), a3 char(3), index idx_a1(a1), index idx_a2(a2), index idx_a3(a3) ); # 查看执行计划 explain select * from test02 where a1 order by a1 ; explain select * from test02 where a1 order by a2 ;
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
结果分析 对于第一个执行计划where后面我们先查询了a1字段然后再利用a1做了依次排序这个很轻松。但是对于第二个执行计划where后面我们查询了a1字段然而利用的却是a2字段进行排序此时myql底层会进行一次查询进行“额外”的排序。
总结对于单索引如果排序和查找是同一个字段则不会出现using filesort如果排序和查找不是同一个字段则会出现using filesort因此where哪些字段就order by哪些些字段。
② using filesort针对复合索引的情况
不能跨列(官方术语最佳左前缀)
# 删除test02的索引 drop index idx_a1 on test02; drop index idx_a2 on test02; drop index idx_a3 on test02; # 创建一个复合索引 alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ; # 查看下面SQL语句的执行计划 explain select *from test02 where a1 order by a3 ; --using filesort explain select *from test02 where a2 order by a3 ; --using filesort explain select *from test02 where a1 order by a2 ;
结果如下 结果分析 复合索引的顺序是(a1,a2,a3)可以看到a1在最左边因此a1就叫做“最佳左前缀”如果要使用后面的索引字段必须先使用到这个a1字段。对于explain1where后面我们使用a1字段但是后面的排序使用了a3直接跳过了a2属于跨列对于explain2where后面我们使用了a2字段直接跳过了a1字段也属于跨列对于explain3where后面我们使用a1字段后面使用的是a2字段因此没有出现【using filesort】。
③ using temporary
当出现了这个词也表示你当前的SQL性能消耗较大。这是由于当前SQL用到了临时表。一般出现在group by中。
explain select a1 from test02 where a1 in (1,2,3) group by a1 ; explain select a1 from test02 where a1 in (1,2,3) group by a2 ; --using temporary
结果如下 结果分析 当你查询哪个字段就按照那个字段分组否则就会出现using temporary。
针对using temporary我们在看一个例子
using temporary表示需要额外再使用一张表一般出现在group by语句中。虽然已经有表了但是不适用必须再来一张表。
再次来看mysql的编写过程和解析过程。
Ⅰ 编写过程
select dinstinct ..from ..join ..on ..where ..group by ..having ..order by ..limit ..
Ⅱ 解析过程
from .. on.. join ..where ..group by ..having ..select dinstinct ..order by ..limit ..
很显然where后是group by然后才是select。基于此我们再查看如下两个SQL语句的执行计划。
explain select * from test03 where a22 and a44 group by a2,a4; explain select * from test03 where a22 and a44 group by a3;
分析如下 对于第一个执行计划where后面是a2和a4接着我们按照a2和a4分组很明显这两张表已经有了直接在a2和a4上分组就行了。但是对于第二个执行计划where后面是a2和a4接着我们却按照a3分组很明显我们没有a3这张表因此有需要再来一张临时表a3。因此就会出现using temporary。
④ using index
当你看到这个关键词恭喜你表示你的SQL性能提升了。
using index称之为“索引覆盖”。
当出现了using index就表示不用读取源表而只利用索引获取数据不需要回源表查询。
只要使用到的列全部出现在索引中就是索引覆盖。
# 删除test02中的复合索引idx_a1_a2_a3 drop index idx_a1_a2_a3 on test02; # 重新创建一个复合索引 idx_a1_a2create index idx_a1_a2 on test02(a1,a2); # 查看执行计划 explain select a1,a3 from test02 where a1 or a3 ; explain select a1,a2 from test02 where a1 and a2 ;
结果如下 结果分析 我们创建的是a1和a2的复合索引对于第一个执行计划我们却出现了a3该字段并没有创建索引因此没有出现using index而是using where表示我们需要回表查询。对于第二个执行计划属于完全的索引覆盖因此出现了using index。
针对using index我们在查看一个案例
explain select a1,a2 from test02 where a1 or a2 ; explain select a1,a2 from test02;
结果如下
如果用到了索引覆盖(using index时)会对possible_keys和key造成影响
a.如果没有where则索引只出现在key中
b.如果有where则索引 出现在key和possible_keys中。
⑤ using where
表示需要【回表查询】表示既在索引中进行了查询又回到了源表进行了查询。
# 删除test02中的复合索引idx_a1_a2 drop index idx_a1_a2 on test02; # 将a1字段新增为一个索引 create index a1_index on test02(a1); # 查看执行计划 explain select a1,a3 from test02 where a1 and a3 ;
结果如下 结果分析 我们既使用了索引a1表示我们使用了索引进行查询。但是又对于a3字段我们并没有使用索引因此对于a3字段需要回源表查询这个时候出现了using where。
⑥ impossible where(了解)
当where子句永远为False的时候会出现impossible where
# 查看执行计划 explain select a1 from test02 where a1a and a1b ;
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
6、优化示例
1引入案例
# 创建新表 create table test03 ( a1 int(4) not null, a2 int(4) not null, a3 int(4) not null, a4 int(4) not null ); # 创建一个复合索引 create index a1_a2_a3_test03 on test03(a1,a2,a3); # 查看执行计划 explain select a3 from test03 where a11 and a22 and a33;
结果如下 推荐写法 复合索引顺序和使用顺序一致。
下面看看【不推荐写法 】复合索引顺序和使用顺序不一致。
# 查看执行计划 explain select a3 from test03 where a31 and a22 and a13;
结果如下 结果分析 虽然结果和上述结果一致但是不推荐这样写。但是这样写怎么又没有问题呢这是由于SQL优化器的功劳它帮我们调整了顺序。
最后再补充一点对于复合索引不要跨列使用
# 查看执行计划 explain select a3 from test03 where a11 and a32 group by a3;
结果如下 结果分析 a1_a2_a3是一个复合索引我们使用a1索引后直接跨列使用了a3直接跳过索引a2因此索引a3失效了当使用a3进行分组的时候就会出现using where。
2单表优化
# 创建新表 create table book ( bid int(4) primary key, name varchar(20) not null, authorid int(4) not null, publicid int(4) not null, typeid int(4) not null ); # 插入数据 insert into book values(1,tjava,1,1,2) ; insert into book values(2,tc,2,1,2) ; insert into book values(3,wx,3,2,1) ; insert into book values(4,math,4,2,3) ;
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
案例查询authorid1且typeid为2或3的bid并根据typeid降序排列。
explain select bid from book where typeid in(2,3) and authorid1 order by typeid desc ;
结果如下
’ fill‘%23FFFFFF’%3E%3Crect x‘249’ y‘126’ width‘1’ height‘1’%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
这是没有进行任何优化的SQL可以看到typ为ALL类型extra为using filesort可以想象这个SQL有多恐怖。
优化添加索引的时候要根据MySQL解析顺序添加索引又回到了MySQL的解析顺序下面我们再来看看MySQL的解析顺序。
from .. on.. join ..where ..group by ..having ..select dinstinct ..order by ..limit ..
① 优化1基于此我们进行索引的添加并再次查看执行计划。
# 添加索引 create index typeid_authorid_bid on book(typeid,authorid,bid); # 再次查看执行计划 explain select bid from book where typeid in(2,3) and authorid1 order by typeid desc ;
结果如下 结果分析 结果并不是和我们想象的一样还是出现了using where查看索引长度key_len8表示我们只使用了2个索引有一个索引失效了。
② 优化2使用了in有时候会导致索引失效基于此有了如下一种优化思路。
将in字段放在最后面。需要注意一点每次创建新的索引的时候最好是删除以前的废弃索引否则有时候会产生干扰(索引之间)。
# 删除以前的索引 drop index typeid_authorid_bid on book; # 再次创建索引 create index authorid_typeid_bid on book(authorid,typeid,bid); # 再次查看执行计划 explain select bid from book where authorid1 and typeid in(2,3) order by typeid desc ;
结果如下 结果分析 这里虽然没有变化但是这是一种优化思路。
总结如下
a.最佳做前缀保持索引的定义和使用的顺序一致性
b.索引需要逐步优化(每次创建新索引根据情况需要删除以前的废弃索引)
c.将含In的范围查询放到where条件的最后防止失效。
本例中同时出现了Using where需要回原表; Using index不需要回原表原因where authorid1 and typeid in(2,3)中authorid在索引(authorid,typeid,bid)中因此不需要回原表直接在索引表中能查到而typeid虽然也在索引(authorid,typeid,bid)中但是含in的范围查询已经使该typeid索引失效因此相当于没有typeid这个索引所以需要回原表using where
例如以下没有了In则不会出现using where
explain select bid from book where authorid1 and typeid 3 order by typeid desc ;
结果如下 3两表优化
# 创建teacher2新表 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); # 创建course2新表 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);
案例使用一个左连接查找教java课程的所有信息。
explain select * from teacher2 t left outer join course2 c on t.cidc.cid where c.cnamejava;
结果如下 ① 优化
对于两张表索引往哪里加答对于表连接小表驱动大表。索引建立在经常使用的字段上。
为什么小表驱动大表好一些呢
小表:10 大表:300 # 小表驱动大表 select ...where 小表.x10大表.x300 ; for(int i0;i小表.length10;i) { for(int j0;j大表.length300;j) { ... } } # 大表驱动小表 select ...where 大表.x300小表.x10 ; for(int i0;i大表.length300;i) { for(int j0;j小表.length10;j) { ... } }分析 以上2个FOR循环最终都会循环3000次但是对于双层循环来说一般建议将数据小的循环放外层。数据大的循环放内层。不用管这是为什么这是编程语言的一个原则对于双重循环外层循环少内存循环大程序的性能越高。
结论当编写【…on t.cidc.cid】时将数据量小的表放左边假设此时t表数据量小c表数据量大。
我们已经知道了对于两表连接需要利用小表驱动大表例如【…on t.cidc.cid】t如果是小表(10条)c如果是大表(300条)那么t每循环1次就需要循环300次即t表的t.cid字段属于经常使用的字段因此需要给cid字段添加索引。
更深入的说明 一般情况下左连接给左表加索引。右连接给右表加索引。其他表需不需要加索引我们逐步尝试。
# 给左表的字段加索引 create index cid_teacher2 on teacher2(cid); # 查看执行计划 explain select * from teacher2 t left outer join course2 c on t.cidc.cid where c.cnamejava;
结果如下 当然你可以下去接着优化给cname添加一个索引。索引优化是一个逐步的过程需要一点点尝试。
# 给cname的字段加索引 create index cname_course2 on course2(cname); # 查看执行计划 explain select t.cid,c.cname from teacher2 t left outer join course2 c on t.cidc.cid where c.cnamejava;
结果如下 最后补充一个Using join buffer是extra中的一个选项表示Mysql引擎使用了“连接缓存”即MySQL底层动了你的SQL你写的太差了。
4三表优化 大于等于张表优化原则一样 小表驱动大表 索引建立在经常查询的字段上
7、避免索引失效的一些原则
① 复合索引需要注意的点 复合索引不要跨列或无序使用(最佳左前缀) 复合索引尽量使用全索引匹配也就是说你建立几个索引就使用几个索引
② 不要在索引上进行任何操作(计算、函数、类型转换)否则索引失效
explain select * from book where authorid 1 and typeid 2; explain select * from book where authorid*2 1 and typeid 2 ;
结果如下 ③ 索引不能使用不等于! 或is null (is not null)否则自身以及右侧所有全部失效(针对大多数情况)。复合索引中如果有则自身和右侧索引全部失效。
# 针对不是复合索引的情况 explain select * from book where authorid ! 1 and typeid 2 ; explain select * from book where authorid ! 1 and typeid !2 ;
结果如下 再观看下面这个案例
# 删除单独的索引 drop index authorid_index on book; drop index typeid_index on book; # 创建一个复合索引 alter table book add index idx_book_at (authorid,typeid); # 查看执行计划 explain select * from book where authorid 1 and typeid 2 ; explain select * from book where authorid 1 and typeid 2 ;
结果如下 结论复合索引中如果有【】则自身和右侧索引全部失效。
在看看复合索引中有【】的情况 我们学习索引优化 是一个大部分情况适用的结论但由于SQL优化器等原因 该结论不是100%正确。一般而言 范围查询 in之后的索引失效。
④ SQL优化是一种概率层面的优化。至于是否实际使用了我们的优化需要通过explain进行推测。
# 删除复合索引 drop index authorid_typeid_bid on book; # 为authorid和typeid分别创建索引 create index authorid_index on book(authorid); create index typeid_index on book(typeid); # 查看执行计划 explain select * from book where authorid 1 and typeid 2 ;
结果如下 结果分析 我们创建了两个索引但是实际上只使用了一个索引。因为对于两个单独的索引程序觉得只用一个索引就够了不需要使用两个。
当我们创建一个复合索引再次执行上面的SQL
# 查看执行计划 explain select * from book where authorid 1 and typeid 2 ;
结果如下 ⑤ 索引覆盖百分之百没问题
⑥ like尽量以“常量”开头不要以’%开头否则索引失效
explain select * from teacher where tname like %x% ; explain select * from teacher where tname like x%; explain select tname from teacher where tname like %x%;
结果如下 结论如下 like尽量不要使用类似%x%情况但是可以使用x%情况。如果非使用 %x%情况需要使用索引覆盖。
⑦ 尽量不要使用类型转换显示、隐式否则索引失效
explain select * from teacher where tname abc ; explain select * from teacher where tname 123 ;
结果如下 ⑧ 尽量不要使用or否则索引失效
explain select * from teacher where tname and tcid 1 ; explain select * from teacher where tname or tcid 1 ;
结果如下 注意or很猛会让自身索引和左右两侧的索引都失效。
8、一些其他的优化方法
1exists和in的优化
如果主查询的数据集大则使用i关键字效率高。
如果子查询的数据集大则使用exist关键字,效率高。
select ..from table where exist (子查询) ; select ..from table where 字段 in (子查询) ;
2order by优化 IO就是访问硬盘文件的次数 using filesort 有两种算法双路排序、单路排序根据IO的次数 MySQL4.1之前默认使用双路排序双路扫描2次磁盘(1从磁盘读取排序字段,对排序字段进行排序(在buffer中进行的排序)2扫描其他字段) MySQL4.1之后默认使用单路排序只读取一次全部字段在buffer中进行排序。但种单路排序会有一定的隐患(不一定真的是“单路/1次IO”有可能多次IO)。原因如果数据量特别大则无法将所有字段的数据一次性读取完毕因此会进行“分片读取、多次读取”。 注意单路排序 比双路排序 会占用更多的buffer。 单路排序在使用时如果数据大可以考虑调大buffer的容量大小
# 不一定真的是“单路/1次IO”有可能多次IO set max_length_for_sort_data 1024
如果max_length_for_sort_data值太低则mysql会自动从 单路-双路(太低需要排序的列的总大小超过了max_length_for_sort_data定义的字节数)
① 提高order by查询的策略 选择使用单路、双路 调整buffer的容量大小 避免使用select * …select后面写所有字段也比写*效率高 复合索引不要跨列使用 避免using filesort保证全部的排序字段排序的一致性都是升序或降序
篇幅很长内容较多建议收藏。
转至https://mp.weixin.qq.com/s/SC3DwqE8SpAuiW66B_EXEQ