超級(jí)重要的SQL優(yōu)化問(wèn)題(下)

      網(wǎng)友投稿 912 2022-05-29

      本文大綱

      前面我已經(jīng)帶著大家學(xué)習(xí)了本文的第1-4個(gè)部分,今天就帶大家學(xué)習(xí)這剩下的5-8個(gè)部分。MySQL優(yōu)化問(wèn)題對(duì)于新手學(xué)習(xí),一般是個(gè)難題!我的教程自認(rèn)為已經(jīng)是很通俗易懂的。如果你學(xué)習(xí)了這個(gè)教程后,仍然不太理解,可以去B站找到一個(gè)視頻瀏覽一遍,然后再回頭看我的文章。

      講解使用的數(shù)據(jù)源

      在上篇最后,我們已經(jīng)給出了本文需要使用到的數(shù)據(jù)代碼,這里我直接給出這3張表的圖示。

      5. explain執(zhí)行計(jì)劃常用關(guān)鍵字詳解

      # 查看執(zhí)行計(jì)劃 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);

      結(jié)果如下:

      接著,在往teacher表中增加幾條數(shù)據(jù)。

      insert into teacher values(4,'ta',4); insert into teacher values(5,'tb',5); insert into teacher values(6,'tc',6);

      再次查看執(zhí)行計(jì)劃。

      # 查看執(zhí)行計(jì)劃 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);

      結(jié)果如下:

      這里先記住一句話:表的執(zhí)行順序 ,因表數(shù)量改變而改變的原因:笛卡爾積。怎么回事呢?看看下面這個(gè)例子。

      # 下面舉一個(gè)例子 a b c 2 3 4 最終:2 * 3 * 4 = 6 * 4 = 24 c b a 4 3 2 最終:4 * 3 * 2 = 12 * 2 = 24

      分析:最終執(zhí)行的條數(shù),雖然是一致的。但是中間過(guò)程,有一張臨時(shí)表是6,一張臨時(shí)表是12,很明顯6 < 12,對(duì)于內(nèi)存來(lái)說(shuō),數(shù)據(jù)量越小越好,因此優(yōu)化器肯定會(huì)選擇第一種執(zhí)行順序。

      結(jié)論:id值相同,從上往下順序執(zhí)行。表的執(zhí)行順序因表數(shù)量的改變而改變,數(shù)量越小,越在前面執(zhí)行。

      # 查看執(zhí)行計(jì)劃 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') );

      結(jié)果如下:

      結(jié)論:id值不同,id值越大越優(yōu)先查詢。這是由于在進(jìn)行嵌套子查詢時(shí),先查內(nèi)層,再查外層。

      # 查看執(zhí)行計(jì)劃 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') ;

      結(jié)果如下:

      結(jié)論:id值有相同,又有不同。id值越大越優(yōu)先;id值相同,從上往下順序執(zhí)行。

      select_type關(guān)鍵字共有如下常用的6種類(lèi)型,下面我分別帶著大家梳理一下,它們各自的含義。

      不包含子查詢,不包含union查詢。

      explain select * from teacher;

      結(jié)果如下:

      關(guān)于primary和subquery,我們就拿下面的這個(gè)例子進(jìn)行演示。從代碼中可以看到這個(gè)SQL語(yǔ)句是存在子查詢的,換句話說(shuō),這個(gè)SQL語(yǔ)句包含子查詢。where內(nèi)層(非最外層)使用到的c表屬于非最外層,因此是subquery關(guān)鍵字。where外層使用到了t表 和tc表,因此是primary關(guān)鍵字。

      # 查看執(zhí)行計(jì)劃 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') ;

      結(jié)果如下:

      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 ;

      結(jié)果如下:

      type關(guān)鍵字可以很好的說(shuō)明,你寫(xiě)的SQL語(yǔ)句好不好。system、const是我們達(dá)不到的理想狀況,實(shí)際上只能優(yōu)化到index --> range --> ref這個(gè)級(jí)別,也可以看到ALL是最差的一個(gè)級(jí)別。

      對(duì)type進(jìn)行優(yōu)化的前提,是你得創(chuàng)建索引。

      源表只有一條數(shù)據(jù)(實(shí)際中,基本不可能);

      衍生表只有一條數(shù)據(jù)的主查詢(偶爾可以達(dá)到)。

      僅僅能查到一條數(shù)據(jù)的SQL ,僅針對(duì)Primary key主鍵索引或unique索引類(lèi)型有效。

      explain select tid from test01 where tid =1 ;

      超級(jí)重要的SQL優(yōu)化問(wèn)題(下)

      結(jié)果如下:

      刪除以前的主鍵索引后,此時(shí)我們添加一個(gè)其他的普通索引:

      create index test01_index on test01(tid) ; # 再次查看執(zhí)行計(jì)劃 explain select tid from test01 where tid =1 ;

      結(jié)果如下:

      可以發(fā)現(xiàn):當(dāng)tid字段去掉主鍵索引,換為普通索引后,優(yōu)化級(jí)別就不是const了。

      唯一性索引,對(duì)于每個(gè)索引鍵的查詢,返回匹配唯一行數(shù)據(jù)(有且只有1個(gè),不能多 、不能0),并且查詢結(jié)果和表中數(shù)據(jù)條數(shù)必須一致。

      此種情況常見(jiàn)于唯一索引和主鍵索引。

      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 ;

      結(jié)果如下:

      總結(jié):以上SQL,用到的索引是t.tcid,即teacher表中的tcid字段;如果teacher表的數(shù)據(jù)個(gè)數(shù)和連接查詢的數(shù)據(jù)個(gè)數(shù)一致(都是3條數(shù)據(jù)),則有可能滿足eq_ref級(jí)別;否則無(wú)法滿足。條件很苛刻,很難達(dá)到。

      非唯一性索引,對(duì)于每個(gè)索引鍵的查詢,返回匹配的所有行(可以0,可以1,可以多)

      準(zhǔn)備數(shù)據(jù):

      創(chuàng)建索引,并查看執(zhí)行計(jì)劃:

      # 添加索引 alter table teacher add index index_name (tname) ; # 查看執(zhí)行計(jì)劃 explain select * from teacher where tname = 'tz';

      結(jié)果如下:

      檢索指定范圍的行 ,where后面是一個(gè)范圍查詢(between, >, <, >=, in)

      in有時(shí)候會(huì)失效,從而轉(zhuǎn)為無(wú)索引時(shí)候的ALL

      # 添加索引 alter table teacher add index tid_index (tid) ; # 查看執(zhí)行計(jì)劃:以下寫(xiě)了一種等價(jià)SQL寫(xiě)法,查看執(zhí)行計(jì)劃 explain select t.* from teacher t where t.tid in (1,2) ; explain select t.* from teacher t where t.tid <3 ;

      結(jié)果如下:

      查詢?nèi)克饕械臄?shù)據(jù)(掃描整個(gè)索引)

      查詢?nèi)吭幢碇械臄?shù)據(jù)(暴力掃描全表)

      注意:cid是索引字段,因此查詢索引字段,只需要掃描索引表即可。但是tid不是索引字段,查詢非索引字段,需要暴力掃描整個(gè)源表,會(huì)消耗更多的資源。

      possible_keys可能用到的索引。是一種預(yù)測(cè),不準(zhǔn)。了解一下就好。

      key指的是實(shí)際使用的索引。

      # 先給course表的cname字段,添加一個(gè)索引 create index cname_index on course(cname); # 查看執(zhí)行計(jì)劃 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') ;

      結(jié)果如下:

      有一點(diǎn)需要注意的是:如果possible_key/key是NULL,則說(shuō)明沒(méi)用索引。

      索引的長(zhǎng)度:用于判斷復(fù)合索引是否被完全使用(a,b,c)。

      # 創(chuàng)建表 create table test_kl ( name char(20) not null default '' ); # 添加索引 alter table test_kl add index index_name(name) ; # 查看執(zhí)行計(jì)劃 explain select * from test_kl where name ='' ;

      結(jié)果如下:

      結(jié)果分析:因?yàn)槲覜](méi)有設(shè)置服務(wù)端的字符集,因此默認(rèn)的字符集使用的是latin1,對(duì)于latin1一個(gè)字符代表一個(gè)字節(jié),因此這列的key_len的長(zhǎng)度是20,表示使用了name這個(gè)索引。

      # 新增一個(gè)字段name1,name1可以為null alter table test_kl add column name1 char(20) ; # 給name1字段,設(shè)置為索引字段 alter table test_kl add index index_name1(name1) ; # 查看執(zhí)行計(jì)劃 explain select * from test_kl where name1 ='' ;

      結(jié)果如下:

      結(jié)果分析:如果索引字段可以為null,則mysql底層會(huì)使用1個(gè)字節(jié)用于標(biāo)識(shí)null。

      # 刪除原來(lái)的索引name和name1 drop index index_name on test_kl ; drop index index_name1 on test_kl ; # 增加一個(gè)復(fù)合索引 create index name_name1_index on test_kl(name,name1); # 查看執(zhí)行計(jì)劃 explain select * from test_kl where name1 = '' ; explain select * from test_kl where name = '' ;

      結(jié)果如下:

      結(jié)果分析:對(duì)于下面這個(gè)執(zhí)行計(jì)劃,可以看到我們只使用了復(fù)合索引的第一個(gè)索引字段name,因此key_len是20,這個(gè)很清楚。再看上面這個(gè)執(zhí)行計(jì)劃,我們雖然僅僅在where后面使用了復(fù)合索引字段中的name1字段,但是你要使用復(fù)合索引的第2個(gè)索引字段,會(huì)默認(rèn)使用了復(fù)合索引的第1個(gè)索引字段name,由于name1可以是null,因此key_len = 20 + 20 + 1 = 41呀!

      # 新增一個(gè)字段name2,name2可以為null alter table test_kl add column name2 varchar(20) ; # 給name2字段,設(shè)置為索引字段 alter table test_kl add index name2_index(name2) ; # 查看執(zhí)行計(jì)劃 explain select * from test_kl where name2 = '' ;

      結(jié)果如下:

      結(jié)果分析:key_len = 20 + 1 + 2,這個(gè)20 + 1我們知道,這個(gè)2又代表什么呢?原來(lái)varchar屬于可變長(zhǎng)度,在mysql底層中,用2個(gè)字節(jié)標(biāo)識(shí)可變長(zhǎng)度。

      這里的ref的作用,指明當(dāng)前表所參照的字段。

      注意與type中的ref值區(qū)分。在type中,ref只是type類(lèi)型的一種選項(xiàng)值。

      # 給course表的tid字段,添加一個(gè)索引 create index tid_index on course(tid); # 查看執(zhí)行計(jì)劃 explain select * from course c,teacher t where c.tid = t.tid and t.tname = 'tw';

      結(jié)果如下:

      結(jié)果分析:有兩個(gè)索引,c表的c.tid引用的是t表的tid字段,因此可以看到顯示結(jié)果為【數(shù)據(jù)庫(kù)名.t.tid】,t表的t.name引用的是一個(gè)常量"tw",因此可以看到結(jié)果顯示為const,表示一個(gè)常量。

      被索引優(yōu)化查詢的數(shù)據(jù)個(gè)數(shù) (實(shí)際通過(guò)索引而查詢到的數(shù)據(jù)個(gè)數(shù))

      explain select * from course c,teacher t where c.tid = t.tid and t.tname = 'tz' ;

      結(jié)果如下:

      表示其他的一些說(shuō)明,也非常有用,通過(guò)這個(gè)關(guān)鍵字也可以很好的說(shuō)明,你寫(xiě)的SQL語(yǔ)句到底好不好。

      當(dāng)出現(xiàn)了這個(gè)詞,表示你當(dāng)前的SQL性能消耗較大。表示進(jìn)行了一次額外的排序。常見(jiàn)于order by語(yǔ)句中。

      Ⅰ 什么是“額外”的排序?

      為了講清楚這個(gè),我們首先要知道什么是排序。我們?yōu)榱私o某一個(gè)字段進(jìn)行排序的時(shí)候,首先你得先查詢到這個(gè)字段,然后在將這個(gè)字段進(jìn)行排序。

      緊接著,我們查看如下兩個(gè)SQL語(yǔ)句的執(zhí)行計(jì)劃。

      # 新建一張表,建表同時(shí)創(chuàng)建索引 create table test02 ( a1 char(3), a2 char(3), a3 char(3), index idx_a1(a1), index idx_a2(a2), index idx_a3(a3) ); # 查看執(zhí)行計(jì)劃 explain select * from test02 where a1 ='' order by a1 ; explain select * from test02 where a1 ='' order by a2 ;

      結(jié)果如下:

      結(jié)果分析:對(duì)于第一個(gè)執(zhí)行計(jì)劃,where后面我們先查詢了a1字段,然后再利用a1做了依次排序,這個(gè)很輕松。但是對(duì)于第二個(gè)執(zhí)行計(jì)劃,where后面我們查詢了a1字段,然而利用的卻是a2字段進(jìn)行排序,此時(shí)myql底層會(huì)進(jìn)行一次查詢,進(jìn)行“額外”的排序。

      總結(jié):對(duì)于單索引,如果排序和查找是同一個(gè)字段,則不會(huì)出現(xiàn)using filesort;如果排序和查找不是同一個(gè)字段,則會(huì)出現(xiàn)using filesort;

      因此where哪些字段,就order by哪些些字段。

      不能跨列(官方術(shù)語(yǔ):最佳左前綴),這句話一定要牢記!!!

      # 刪除test02的索引 drop index idx_a1 on test02; drop index idx_a2 on test02; drop index idx_a3 on test02; # 創(chuàng)建一個(gè)復(fù)合索引 alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ; # 查看下面SQL語(yǔ)句的執(zhí)行計(jì)劃 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 ;

      結(jié)果如下:

      結(jié)果分析:復(fù)合索引的順序是(a1,a2,a3),可以看到a1在最左邊,因此a1就叫做最佳左前綴,

      如果要使用后面的索引字段,必須先使用到這個(gè)a1字段。

      對(duì)于explain1,where后面我們使用a1字段,但是后面的排序使用了a3,直接跳過(guò)了a2,屬于跨列;對(duì)于explain2,where后面我們使用了a2字段,直接跳過(guò)了a1字段,也屬于跨列;對(duì)于explain3,where后面我們使用a1字段,后面使用的是a2字段,因此沒(méi)有出現(xiàn)【using filesort】。

      當(dāng)出現(xiàn)了這個(gè)詞,也表示你當(dāng)前的SQL性能消耗較大。這是由于當(dāng)前SQL用到了臨時(shí)表,一般出現(xiàn)在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

      結(jié)果如下:

      結(jié)果分析:當(dāng)你查詢哪個(gè)字段,就按照那個(gè)字段分組,否則就會(huì)出現(xiàn)using temporary。

      ---------------------------------------------------------------------------------------------------------------

      針對(duì)using temporary,我們?cè)诳匆粋€(gè)例子:

      using temporary表示需要額外再使用一張表,一般出現(xiàn)在group by語(yǔ)句中。雖然已經(jīng)有表了,但是不適用,必須再來(lái)一張表。

      再次來(lái)看mysql的編寫(xiě)過(guò)程和解析過(guò)程。

      Ⅰ 編寫(xiě)過(guò)程

      select dinstinct ..from ..join ..on ..where ..group by ..having ..order by ..limit ..

      Ⅱ 解析過(guò)程

      from .. on.. join ..where ..group by ..having ..select dinstinct ..order by ..limit ..

      很顯然,where后是group by,然后才是select。基于此,我們?cè)俨榭慈缦聝蓚€(gè)SQL語(yǔ)句的執(zhí)行計(jì)劃。

      explain select * from test03 where a2=2 and a4=4 group by a2,a4; explain select * from test03 where a2=2 and a4=4 group by a3;

      分析如下:對(duì)于第一個(gè)執(zhí)行計(jì)劃,where后面是a2和a4,接著我們按照a2和a4分組,很明顯這兩張表已經(jīng)有了,直接在a2和a4上分組就行了。但是對(duì)于第二個(gè)執(zhí)行計(jì)劃,where后面是a2和a4,接著我們卻按照a3分組,很明顯我們沒(méi)有a3這張表,因此有需要再來(lái)一張臨時(shí)表a3,因此就會(huì)出現(xiàn)using temporary。

      當(dāng)你看到這個(gè)關(guān)鍵詞,恭喜你,表示你的SQL性能提升了。

      using index稱(chēng)之為

      索引覆蓋。

      當(dāng)出現(xiàn)了using index,就表示不用讀取源表,而只利用索引獲取數(shù)據(jù),不需要回源表查詢。

      只要使用到的列,全部出現(xiàn)在索引中,就是索引覆蓋。

      # 刪除test02中的復(fù)合索引idx_a1_a2_a3 drop index idx_a1_a2_a3 on test02; # 重新創(chuàng)建一個(gè)復(fù)合索引idx_a1_a2 create index idx_a1_a2 on test02(a1,a2); # 查看執(zhí)行計(jì)劃 explain select a1,a3 from test02 where a1='' or a3= '' ; explain select a1,a2 from test02 where a1='' and a2= '' ;

      結(jié)果如下:

      結(jié)果分析:這里我們創(chuàng)建的是a1和a2的復(fù)合索引,對(duì)于第一個(gè)執(zhí)行計(jì)劃,我們卻出現(xiàn)了a3,該字段并沒(méi)有創(chuàng)建索引,因此沒(méi)有出現(xiàn)using index,而是using where,表示我們需要回表查詢。對(duì)于第二個(gè)執(zhí)行計(jì)劃,屬于完全的索引覆蓋,因此出現(xiàn)了using index。

      ----------------------------------------------------------------------------------------------------------------

      針對(duì)using index,我們?cè)诓榭匆粋€(gè)案例:

      explain select a1,a2 from test02 where a1='' or a2= '' ; explain select a1,a2 from test02 ;

      結(jié)果如下:

      如果用到了索引覆蓋(using index時(shí)),會(huì)對(duì)possible_keys和key造成影響:

      a.如果沒(méi)有where,則索引只出現(xiàn)在key中;

      b.如果有where,則索引 出現(xiàn)在key和possible_keys中。

      表示需要【回表查詢】,表示既在索引中進(jìn)行了查詢,又回到了源表進(jìn)行了查詢。

      # 刪除test02中的復(fù)合索引idx_a1_a2 drop index idx_a1_a2 on test02; # 將a1字段,新增為一個(gè)索引 create index a1_index on test02(a1); # 查看執(zhí)行計(jì)劃 explain select a1,a3 from test02 where a1="" and a3="" ;

      結(jié)果如下:

      結(jié)果分析:我們使用了索引a1,表示我們使用了索引進(jìn)行查詢。但是又對(duì)于a3字段,我們并沒(méi)有使用索引,因此對(duì)于a3字段,需要回源表查詢,這個(gè)時(shí)候出現(xiàn)了using where。

      當(dāng)where子句永遠(yuǎn)為False的時(shí)候,會(huì)出現(xiàn)impossible where。

      # 查看執(zhí)行計(jì)劃 explain select a1 from test02 where a1="a" and a1="b" ;

      結(jié)果如下:

      # 創(chuàng)建新表 create table test03 ( a1 int(4) not null, a2 int(4) not null, a3 int(4) not null, a4 int(4) not null ); # 創(chuàng)建一個(gè)復(fù)合索引 create index a1_a2_a3_test03 on test03(a1,a2,a3); # 查看執(zhí)行計(jì)劃 explain select a3 from test03 where a1=1 and a2=2 and a3=3;

      結(jié)果如下:

      【推薦寫(xiě)法】:復(fù)合索引順序和使用順序一致。

      【不推薦寫(xiě)法】:復(fù)合索引順序和使用順序不一致。

      # 查看執(zhí)行計(jì)劃 explain select a3 from test03 where a3=1 and a2=2 and a1=3;

      結(jié)果如下:

      結(jié)果分析:雖然結(jié)果和上述結(jié)果一致,但是不推薦這樣寫(xiě)。但是這樣寫(xiě)怎么又沒(méi)有問(wèn)題呢?這是由于SQL優(yōu)化器的功勞,它幫我們調(diào)整了順序。

      最后再補(bǔ)充一點(diǎn):對(duì)于復(fù)合索引,不要跨列使用。

      # 查看執(zhí)行計(jì)劃 explain select a3 from test03 where a1=1 and a3=2 group by a3;

      結(jié)果如下:

      結(jié)果分析:a1_a2_a3是一個(gè)復(fù)合索引,我們使用a1索引后,直接跨列使用了a3,直接跳過(guò)索引a2,因此索引a3失效了。當(dāng)再使用a3進(jìn)行分組的時(shí)候,就會(huì)出現(xiàn)using where。

      # 創(chuàng)建新表 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 ); # 插入數(shù)據(jù) 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) ;

      結(jié)果如下:

      案例:查詢authorid=1且typeid為2或3的bid,并根據(jù)typeid降序排列。

      explain select bid from book where typeid in(2,3) and authorid=1 order by typeid desc ;

      結(jié)果如下:

      這是沒(méi)有進(jìn)行任何優(yōu)化的SQL,可以看到typ為ALL類(lèi)型,extra為using filesort,可以想象這個(gè)SQL有多恐怖。

      優(yōu)化:添加索引的時(shí)候,要根據(jù)MySQL解析順序添加索引,又回到了MySQL的解析順序,下面我們?cè)賮?lái)看看MySQL的解析順序。

      from .. on.. join ..where ..group by ..having ..select dinstinct ..order by ..limit ..

      # 添加索引 create index typeid_authorid_bid on book(typeid,authorid,bid); # 再次查看執(zhí)行計(jì)劃 explain select bid from book where typeid in(2,3) and authorid=1 order by typeid desc ;

      結(jié)果如下:

      結(jié)果分析:結(jié)果并不是和我們想象的一樣,還是出現(xiàn)了using where,查看索引長(zhǎng)度key_len=8,表示我們只使用了2個(gè)索引,有一個(gè)索引失效了。

      將in字段放在最后面。

      需要注意一點(diǎn):每次創(chuàng)建新的索引的時(shí)候,最好是刪除以前的廢棄索引,否則有時(shí)候會(huì)產(chǎn)生干擾(索引之間)。

      # 刪除以前的索引 drop index typeid_authorid_bid on book; # 再次創(chuàng)建索引 create index authorid_typeid_bid on book(authorid,typeid,bid); # 再次查看執(zhí)行計(jì)劃 explain select bid from book where authorid=1 and typeid in(2,3) order by typeid desc ;

      結(jié)果如下:

      結(jié)果分析:這里雖然沒(méi)有變化,但是這是一種優(yōu)化思路。

      總結(jié)如下:

      a.最佳做前綴,保持索引的定義和使用的順序一致性。

      b.索引需要逐步優(yōu)化(每次創(chuàng)建新索引,根據(jù)情況需要?jiǎng)h除以前的廢棄索引)。

      c.將含in的范圍查詢,放到where條件的最后,防止失效。

      本例中同時(shí)出現(xiàn)了Using where(需要回原表); Using index(不需要回原表):原因,where authorid=1 and typeid in(2,3)中authorid在索引(authorid,typeid,bid)中,因此不需要回原表(直接在索引表中能查到);而typeid雖然也在索引(authorid,typeid,bid)中,但是含in的范圍查詢已經(jīng)使該typeid索引失效,因此相當(dāng)于沒(méi)有typeid這個(gè)索引,所以需要回原表(using where);

      下面這個(gè)例子,沒(méi)有了in,則不會(huì)出現(xiàn)using where:

      explain select bid from book where authorid=1 and typeid =3 order by typeid desc ;

      結(jié)果如下:

      # 創(chuàng)建teacher2新表 create table teacher2 ( tid int(4) primary key, cid int(4) not null ); # 插入數(shù)據(jù) insert into teacher2 values(1,2); insert into teacher2 values(2,1); insert into teacher2 values(3,3); # 創(chuàng)建course2新表 create table course2 ( cid int(4) , cname varchar(20) ); # 插入數(shù)據(jù) insert into course2 values(1,'java'); insert into course2 values(2,'python'); insert into course2 values(3,'kotlin');

      結(jié)果如下:

      案例:使用一個(gè)左連接,查找教java課程的所有信息。

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

      結(jié)果如下:

      對(duì)于兩張表,索引往哪里加?答:對(duì)于表連接,小表驅(qū)動(dòng)大表。索引建立在經(jīng)常使用的字段上。

      為什么小表驅(qū)動(dòng)大表好一些呢?

      小表:10 大表:300 # 小表驅(qū)動(dòng)大表 select ...where 小表.x10=大表.x300 ; for(int i=0;i<小表.length10;i++) { for(int j=0;j<大表.length300;j++) { ... } } # 大表驅(qū)動(dòng)小表 select ...where 大表.x300=小表.x10 ; for(int i=0;i<大表.length300;i++) { for(int j=0;j<小表.length10;j++) { ... } }

      分析:以上2個(gè)FOR循環(huán),最終都會(huì)循環(huán)3000次;但是對(duì)于雙層循環(huán)來(lái)說(shuō):一般建議,將數(shù)據(jù)小的循環(huán),放外層。數(shù)據(jù)大的循環(huán),放內(nèi)層。

      不用管這是為什么,這是編程語(yǔ)言的一個(gè)原則,對(duì)于雙重循環(huán),外層循環(huán)少,內(nèi)存循環(huán)大,程序的性能越高。

      結(jié)論:當(dāng)編寫(xiě)【…on t.cid=c.cid】時(shí),將數(shù)據(jù)量小的表放左邊(假設(shè)此時(shí)t表數(shù)據(jù)量小,c表數(shù)據(jù)量大。)

      我們已經(jīng)知道了,對(duì)于兩表連接,需要利用小表驅(qū)動(dòng)大表。例如【…on t.cid=c.cid】,t如果是小表(10條),c如果是大表(300條),那么t每循環(huán)1次,就需要循環(huán)300次,即t表的t.cid字段屬于經(jīng)常使用的字段,因此需要給cid字段添加索引。

      更深入的說(shuō)明:一般情況下,左連接給左表加索引。右連接給右表加索引。其他表需不需要加索引,我們逐步嘗試。

      # 給左表的字段加索引 create index cid_teacher2 on teacher2(cid); # 查看執(zhí)行計(jì)劃 explain select * from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';

      結(jié)果如下:

      當(dāng)然你可以下去接著優(yōu)化,給cname添加一個(gè)索引。

      索引優(yōu)化是一個(gè)逐步的過(guò)程,需要一點(diǎn)點(diǎn)嘗試。

      # 給cname的字段加索引 create index cname_course2 on course2(cname); # 查看執(zhí)行計(jì)劃 explain select t.cid,c.cname from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';

      結(jié)果如下:

      最后補(bǔ)充一個(gè):Using join buffer是extra中的一個(gè)選項(xiàng),表示Mysql引擎使用了連接緩存,即MySQL底層動(dòng)了你的SQL,你寫(xiě)的太差了。

      大于等于2張表,優(yōu)化原則一樣;

      小表驅(qū)動(dòng)大表 ;

      索引建立在經(jīng)常查詢的字段上;

      a.復(fù)合索引,不要跨列或無(wú)序使用(最佳左前綴);

      b.復(fù)合索引,盡量使用全索引匹配,也就是說(shuō),你建立幾個(gè)索引,就使用幾個(gè)索引;

      explain select * from book where authorid = 1 and typeid = 2; explain select * from book where authorid*2 = 1 and typeid = 2 ;

      結(jié)果如下:

      # 針對(duì)不是復(fù)合索引的情況 explain select * from book where authorid != 1 and typeid =2 ; explain select * from book where authorid != 1 and typeid !=2 ;

      結(jié)果如下:

      再觀看下面這個(gè)案例:

      # 刪除單獨(dú)的索引 drop index authorid_index on book; drop index typeid_index on book; # 創(chuàng)建一個(gè)復(fù)合索引 alter table book add index idx_book_at (authorid,typeid); # 查看執(zhí)行計(jì)劃 explain select * from book where authorid > 1 and typeid = 2 ; explain select * from book where authorid = 1 and typeid > 2 ;

      結(jié)果如下:

      結(jié)論:復(fù)合索引中如果有【>】,則自身和右側(cè)索引全部失效。

      在看看復(fù)合索引中有【<】的情況:

      我們學(xué)習(xí)索引優(yōu)化 ,是一個(gè)大部分情況適用的結(jié)論,但由于SQL優(yōu)化器等原因 該結(jié)論不是100%正確。一般而言, 范圍查詢(> < in)之后的索引失效。

      # 刪除復(fù)合索引 drop index authorid_typeid_bid on book; # 為authorid和typeid,分別創(chuàng)建索引 create index authorid_index on book(authorid); create index typeid_index on book(typeid); # 查看執(zhí)行計(jì)劃 explain select * from book where authorid = 1 and typeid =2 ;

      結(jié)果如下:

      結(jié)果分析:我們創(chuàng)建了兩個(gè)索引,但是實(shí)際上只使用了一個(gè)索引。因?yàn)閷?duì)于兩個(gè)單獨(dú)的索引,程序覺(jué)得只用一個(gè)索引就夠了,不需要使用兩個(gè)。

      當(dāng)我們創(chuàng)建一個(gè)復(fù)合索引,再次執(zhí)行上面的SQL:

      # 查看執(zhí)行計(jì)劃 explain select * from book where authorid = 1 and typeid =2 ;

      結(jié)果如下:

      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%';

      結(jié)果如下:

      結(jié)論如下:like盡量不要使用類(lèi)似"%x%"情況,但是可以使用"x%"情況。如果非使用 "%x%"情況,需要使用索引覆蓋。

      explain select * from teacher where tname = 'abc' ; explain select * from teacher where tname = 123 ;

      結(jié)果如下:

      explain select * from teacher where tname ='' and tcid >1 ; explain select * from teacher where tname ='' or tcid >1 ;

      結(jié)果如下:

      注意:or很猛,會(huì)讓自身索引和左右兩側(cè)的索引都失效。

      如果主查詢的數(shù)據(jù)集大,則使用i關(guān)鍵字,效率高。

      如果子查詢的數(shù)據(jù)集大,則使用exist關(guān)鍵字,效率高。

      select ..from table where exist (子查詢) ; select ..from table where 字段 in (子查詢) ;

      IO就是訪問(wèn)硬盤(pán)文件的次數(shù)。

      using filesort 有兩種算法:雙路排序、單路排序(根據(jù)IO的次數(shù))

      MySQL4.1之前默認(rèn)使用雙路排序;雙路:掃描2次磁盤(pán)(1:從磁盤(pán)讀取排序字段

      ,對(duì)排序字段進(jìn)行排序(在buffer中進(jìn)行的排序)2:掃描其他字段)

      MySQL4.1之后默認(rèn)使用單路排序:只讀取一次(全部字段),在buffer中進(jìn)行排序。但種單路排序會(huì)有一定的隱患(不一定真的是“單路/1次IO”,有可能多次IO)。原因:如果數(shù)據(jù)量特別大,則無(wú)法將所有字段的數(shù)據(jù)一次性讀取完畢,因此會(huì)進(jìn)行“分片讀取、多次讀取”。

      注意:?jiǎn)温放判?比雙路排序 會(huì)占用更多的buffer。

      單路排序在使用時(shí),如果數(shù)據(jù)大,可以考慮調(diào)大buffer的容量大小:

      # 不一定真的是“單路/1次IO”,有可能多次IO set max_length_for_sort_data = 1024

      如果max_length_for_sort_data值太低,則mysql會(huì)自動(dòng)從 單路->雙路(太低:需要排序的列的總大小超過(guò)了max_length_for_sort_data定義的字節(jié)數(shù))

      a.選擇使用單路、雙路 ;調(diào)整buffer的容量大小;

      b.避免使用select * …(select后面寫(xiě)所有字段,也比寫(xiě)*效率高)

      c.復(fù)合索引,不要跨列使用 ,避免using filesort

      d.保證全部的排序字段,排序的一致性(都是升序或降序)

      MySQL SQL 數(shù)據(jù)庫(kù)

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:機(jī)動(dòng)車(chē)排放檢測(cè)信息系統(tǒng)建設(shè)目標(biāo)及規(guī)范
      下一篇:Qt核心知識(shí)歸類(lèi)及相關(guān)資料
      相關(guān)文章
      亚洲人成欧美中文字幕| 亚洲精品无码专区| 精品亚洲456在线播放| 国产乱辈通伦影片在线播放亚洲| 亚洲一区二区三区丝袜| 亚洲国产精品乱码在线观看97 | 久久亚洲精品无码网站| 亚洲欧洲无卡二区视頻| www.亚洲成在线| 午夜在线a亚洲v天堂网2019| 亚洲五月丁香综合视频| 亚洲一级在线观看| 精品国产日韩久久亚洲| 亚洲欧美日韩中文二区| 亚洲av日韩精品久久久久久a| 一本色道久久88亚洲精品综合| 亚洲宅男精品一区在线观看| 亚洲一区欧洲一区| 亚洲欧美国产欧美色欲| 亚洲熟妇无码AV| 亚洲爆乳无码专区www| 蜜臀亚洲AV无码精品国产午夜.| www.亚洲精品.com| 亚洲高清无码在线观看| 爱情岛论坛网亚洲品质自拍| 亚洲日韩aⅴ在线视频| 久久精品亚洲综合| 亚洲精品成人网站在线播放| 亚洲人成在线免费观看| 亚洲熟妇丰满xxxxx| 亚洲成aⅴ人片久青草影院按摩| 亚洲Av无码乱码在线观看性色| 亚洲人成无码久久电影网站| 在线观看亚洲av每日更新| 亚洲成AV人片在线播放无码| 久久精品亚洲精品国产色婷| 麻豆亚洲av熟女国产一区二| 激情亚洲一区国产精品| 在线观看亚洲专区| 亚洲精品午夜无码专区| 亚洲电影在线免费观看|