mysql优化-实战

单表优化

createtablebook
(
bidint(4)primarykey,
namevarchar(20)notnull,
authoridint(4)notnull,
publicidint(4)notnull,
typeidint(4)notnull
);

insertintobookvalues(1,'tjava',1,1,2);
insertintobookvalues(2,'tc',2,1,2);
insertintobookvalues(3,'wx',3,2,1);
insertintobookvalues(4,'math',4,2,3);
commit;

查询authorid=1且typeid为2或3的bid
explainselectbidfrombookwheretypeidin(2,3)andauthorid=1orderbytypeiddesc;

(a,b,c)
(a,b)

优化:加索引
altertablebookaddindexidx_bta(bid,typeid,authorid);

索引一旦进行升级优化,需要将之前废弃的索引删掉,防止干扰。
dropindexidx_btaonbook;

根据SQL实际解析的顺序,调整索引的顺序:
altertablebookaddindexidx_tab(typeid,authorid,bid);--虽然可以回表查询bid,但是将bid放到索引中可以提升使用usingindex;

再次优化(之前是index级别):思路。因为范围查询in有时会实现,因此交换索引的顺序,将typeidin(2,3)放到最后。
dropindexidx_tabonbook;
altertablebookaddindexidx_atb(authorid,typeid,bid);
explainselectbidfrombookwhereauthorid=1andtypeidin(2,3)orderbytypeiddesc;

--小结:a.最佳做前缀,保持索引的定义和使用的顺序一致性b.索引需要逐步优化c.将含In的范围查询放到where条件的最后,防止失效。

本例中同时出现了Usingwhere(需要回原表);Usingindex(不需要回原表):原因,whereauthorid=1andtypeidin(2,3)中authorid在索引(authorid,typeid,bid)中,因此不需要回原表(直接在索引表中能查到);而typeid虽然也在索引(authorid,typeid,bid)中,但是含in的范围查询已经使该typeid索引失效,因此相当于没有typeid这个索引,所以需要回原表(usingwhere);
例如以下没有了In,则不会出现usingwhere
explainselectbidfrombookwhereauthorid=1andtypeid=3orderbytypeiddesc;

还可以通过key_len证明In可以使索引失效。

两表优化

createtableteacher2
(
tidint(4)primarykey,
cidint(4)notnull
);

insertintoteacher2values(1,2);
insertintoteacher2values(2,1);
insertintoteacher2values(3,3);

createtablecourse2
(
cidint(4),
cnamevarchar(20)
);

insertintocourse2values(1,'java');
insertintocourse2values(2,'python');
insertintocourse2values(3,'kotlin');
commit;

左连接:
explainselect*fromteacher2tleftouterjoincourse2c
ont.cid=c.cidwherec.cname='java';

索引往哪张表加?-小表驱动大表
-索引建立经常使用的字段上(本题t.cid=c.cid可知,t.cid字段使用频繁,因此给该字段加索引)[一般情况对于左外连接,给左表加索引;右外连接,给右表加索引]
小表:10
大表:300
where小表.x10=大表.y300;--循环了几次?10

大表.y300=小表.x10--循环了300次

小表:10
大表:300

select...where小表.x10=大表.x300;
for(inti=0;i<小表.length10;i++)
{
for(intj=0;j<大表.length300;j++)
{
...
}
}

select...where大表.x300=小表.x10;
for(inti=0;i<大表.length300;i++)
{
for(intj=0;j<小表.length10;j++)
{
...
}
}

--以上2个FOR循环,最终都会循环3000次;但是对于双层循环来说:一般建议将数据小的循环放外层;数据大的循环放内存。

--当编写..ont.cid=c.cid时,将数据量小的表放左边(假设此时t表数据量小)

altertableteacher2addindexindex_teacher2_cid(cid);
altertablecourse2addindexindex_course2_cname(cname);

Usingjoinbuffer:extra中的一个选项,作用:Mysql引擎使用了连接缓存。

三张表优化

ABC
a.小表驱动大表b.索引建立在经常查询的字段上

示例:
createtabletest03
(
a1int(4)notnull,
a2int(4)notnull,
a3int(4)notnull,
a4int(4)notnull
);
altertabletest03addindexidx_a1_a2_a3_4(a1,a2,a3,a4);

explainselecta1,a2,a3,a4fromtest03wherea1=1anda2=2anda3=3anda4=4;--推荐写法,因为索引的使用顺序(where后面的顺序)和复合索引的顺序一致

explainselecta1,a2,a3,a4fromtest03wherea4=1anda3=2anda2=3anda1=4;--虽然编写的顺序和索引顺序不一致,但是sql在真正执行前经过了SQL优化器的调整,结果与上条SQL是一致的。
--以上2个SQL,使用了全部的复合索引

explainselecta1,a2,a3,a4fromtest03wherea1=1anda2=2anda4=4orderbya3;
--以上SQL用到了a1a2两个索引,该两个字段不需要回表查询usingindex;而a4因为跨列使用,造成了该索引失效,需要回表查询因此是usingwhere;以上可以通过key_len进行验证

explainselecta1,a2,a3,a4fromtest03wherea1=1anda4=4orderbya3;
--以上SQL出现了usingfilesort(文件内排序,“多了一次额外的查找/排序”):不要跨列使用(where和orderby拼起来,不要跨列使用)

explainselecta1,a2,a3,a4fromtest03wherea1=1anda4=4orderbya2,a3;--不会usingfilesort

--总结:i.如果(a,b,c,d)复合索引和使用的顺序全部一致(且不跨列使用),则复合索引全部使用。如果部分一致(且不跨列使用),则使用部分索引。
selecta,cwherea=andb=andd=
ii.where和orderby拼起来,不要跨列使用

usingtemporary:需要额外再多使用一张表.一般出现在groupby语句中;已经有表了,但不适用,必须再来一张表。
解析过程:
from..on..join..where..groupby....having...selectdinstinct..orderbylimit...
a.
explainselectfromtest03wherea2=2anda4=4groupbya2,a4;--没有usingtemporary
b.
explainselect
fromtest03wherea2=2anda4=4groupbya3;

避免索引失效的一些原则

(1)复合索引
a.复合索引,不要跨列或无序使用(最佳左前缀)
(a,b,c)
b.复合索引,尽量使用全索引匹配
(a,b,c)
(2)不要在索引上进行任何操作(计算、函数、类型转换),否则索引失效
select..whereA.x=..;--假设A.x是索引
不要:select..whereA.x3=..;
explainselect
frombookwhereauthorid=1andtypeid=2;--用到了at2个索引
explainselectfrombookwhereauthorid=1andtypeid2=2;--用到了a1个索引
explainselectfrombookwhereauthorid2=1andtypeid2=2;----用到了0个索引
explainselect
frombookwhereauthorid*2=1andtypeid=2;----用到了0个索引,原因:对于复合索引,如果左边失效,右侧全部失效。(a,b,c),例如如果b失效,则bc同时失效。

dropindexidx_atbonbook;
altertablebookaddindexidx_authroid(authorid);
altertablebookaddindexidx_typeid(typeid);
explainselectfrombookwhereauthorid2=1andtypeid=2;
(3)复合索引不能使用不等于(!=<>)或isnull(isnotnull),否则自身以及右侧所有全部失效。
复合索引中如果有>,则自身和右侧索引全部失效。

explainselect*frombookwhereauthorid=1andtypeid=2;

--SQL优化,是一种概率层面的优化。至于是否实际使用了我们的优化,需要通过explain进行推测。

explainselectfrombookwhereauthorid!=1andtypeid=2;
explainselect
frombookwhereauthorid!=1andtypeid!=2;

体验概率情况(<>=):原因是服务层中有SQL优化器,可能会影响我们的优化。
dropindexidx_typeidonbook;
dropindexidx_authroidonbook;
altertablebookaddindexidx_book_at(authorid,typeid);
explainselectfrombookwhereauthorid=1andtypeid=2;--复合索引at全部使用
explainselect
frombookwhereauthorid>1andtypeid=2;--复合索引中如果有>,则自身和右侧索引全部失效。
explainselectfrombookwhereauthorid=1andtypeid>2;--复合索引at全部使用
----明显的概率问题---
explainselect
frombookwhereauthorid<1andtypeid=2;--复合索引at只用到了1个索引
explainselect*frombookwhereauthorid<4andtypeid=2;--复合索引全部失效

--我们学习索引优化,是一个大部分情况适用的结论,但由于SQL优化器等原因该结论不是100%正确。
--一般而言,范围查询(>

(4)补救。尽量使用索引覆盖(usingindex)
(a,b,c)
selecta,b,cfromxx..wherea=..andb=..;

(5)like尽量以“常量”开头,不要以'%'开头,否则索引失效
select*fromxxwherenamelike'%x%';--name索引失效

explainselect*fromteacherwheretnamelike'%x%';--tname索引失效

explainselect*fromteacherwheretnamelike'x%';

explainselecttnamefromteacherwheretnamelike'%x%';--如果必须使用like'%x%'进行模糊查询,可以使用索引覆盖挽救一部分。

(6)尽量不要使用类型转换(显示、隐式),否则索引失效
explainselectfromteacherwheretname='abc';
explainselect
fromteacherwheretname=123;//程序底层将123->'123',即进行了类型转换,因此索引失效

(7)尽量不要使用or,否则索引失效
explainselect*fromteacherwheretname=''ortcid>1;--将or左侧的tname失效。

一些其他的优化方法

(1)
exist和in
select..fromtablewhereexist(子查询);
select..fromtablewhere字段in(子查询);

如果主查询的数据集大,则使用In,效率高。
如果子查询的数据集大,则使用exist,效率高。

exist语法:将主查询的结果,放到子查需结果中进行条件校验(看子查询是否有数据,如果有数据则校验成功),
如果复合校验,则保留数据;

selecttnamefromteacherwhereexists(select*fromteacher);
--等价于selecttnamefromteacher

selecttnamefromteacherwhereexists(select*fromteacherwheretid=9999);

in:
select..fromtablewheretidin(1,3,5);

(2)orderby优化
usingfilesort有两种算法:双路排序、单路排序(根据IO的次数)
MySQL4.1之前默认使用双路排序;双路:扫描2次磁盘(1:从磁盘读取排序字段,对排序字段进行排序(在buffer中进行的排序)2:扫描其他字段)
--IO较消耗性能
MySQL4.1之后默认使用单路排序:只读取一次(全部字段),在buffer中进行排序。但种单路排序会有一定的隐患(不一定真的是“单路|1次IO”,有可能多次IO)。原因:如果数据量特别大,则无法将所有字段的数据一次性读取完毕,因此会进行“分片读取、多次读取”。
注意:单路排序比双路排序会占用更多的buffer。
单路排序在使用时,如果数据大,可以考虑调大buffer的容量大小:setmax_length_for_sort_data=1024单位byte

如果max_length_for_sort_data值太低,则mysql会自动从单路->双路(太低:需要排序的列的总大小超过了max_length_for_sort_data定义的字节数)

提高orderby查询的策略:
a.选择使用单路、双路;调整buffer的容量大小;
b.避免select*...
c.复合索引不要跨列使用,避免usingfilesort
d.保证全部的排序字段排序的一致性(都是升序或降序)

你可能感兴趣的:(mysql优化-实战)