1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        18000字的SQL優(yōu)化大全,收藏直接起飛!

        共 7770字,需瀏覽 16分鐘

         ·

        2022-01-09 16:02


        來源:數(shù)據(jù)分析不是個事兒

        很多朋友在做數(shù)據(jù)分析時,分析兩分鐘,跑數(shù)兩小時?

        在使用SQL過程中不僅要關(guān)注數(shù)據(jù)結(jié)果,同樣要注意SQL語句的執(zhí)行效率。

        本文涉及三部分,篇幅較長,建議收藏后翻看:

        • SQL介紹

        • SQL優(yōu)化方法

        • SQL優(yōu)化實例

        1、MySQL的基本架構(gòu)

        1)MySQL的基礎(chǔ)架構(gòu)圖


        左邊的client可以看成是客戶端,客戶端有很多,像我們經(jīng)常你使用的CMD黑窗口,像我們經(jīng)常用于學(xué)習(xí)的WorkBench,像企業(yè)經(jīng)常使用的Navicat工具,它們都是一個客戶端。右邊的這一大堆都可以看成是Server(MySQL的服務(wù)端),我們將Server在細分為sql層和存儲引擎層。

        當(dāng)查詢出數(shù)據(jù)以后,會返回給執(zhí)行器。執(zhí)行器一方面將結(jié)果寫到查詢緩存里面,當(dāng)你下次再次查詢的時候,就可以直接從查詢緩存中獲取到數(shù)據(jù)了。另一方面,直接將結(jié)果響應(yīng)回客戶端。

        2)查詢數(shù)據(jù)庫的引擎

        ① show engines;


        ② show variables like “%storage_engine%”;


        3)指定數(shù)據(jù)庫對象的存儲引擎

        create?table?tb(
        ????id?int(4)?auto_increment,
        ????name?varchar(5),
        ????dept?varchar(5),
        ????primary?key(id)
        )?engine=myISAM?auto_increment=1?default?charset=utf8;

        SQL優(yōu)化

        1)為什么需要進行SQL優(yōu)化?

        在進行多表連接查詢、子查詢等操作的時候,由于你寫出的SQL語句欠佳,導(dǎo)致的服務(wù)器執(zhí)行時間太長,我們等待結(jié)果的時間太長。基于此,我們需要學(xué)習(xí)怎么優(yōu)化SQL。

        2)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?..

        提供一個網(wǎng)站,詳細說明了mysql解析過程:

        https://www.cnblogs.com/annsshadow/p/5037667.html

        3)SQL優(yōu)化—主要就是優(yōu)化索引

        優(yōu)化SQL,最重要的就是優(yōu)化SQL索引。

        索引相當(dāng)于字典的目錄。利用字典目錄查找漢字的過程,就相當(dāng)于利用SQL索引查找某條記錄的過程。有了索引,就可以很方便快捷的定位某條記錄。

        ① 什么是索引?

        索引就是幫助MySQL高效獲取數(shù)據(jù)的一種【數(shù)據(jù)結(jié)構(gòu)】。索引是一種樹結(jié)構(gòu),MySQL中一般用的是【B+樹】。

        ② 索引圖示說明(這里用二叉樹來幫助我們理解索引)

        樹形結(jié)構(gòu)的特點是:子元素比父元素小的,放在左側(cè);子元素比父元素大的,放在右側(cè)。

        這個圖示只是為了幫我們簡單理解索引的,真實的關(guān)于【B+樹】的說明,我們會在下面進行說明。


        索引是怎么查找數(shù)據(jù)的呢?兩個字【指向】,上圖中我們給age列指定了一個索引,即類似于右側(cè)的這種樹形結(jié)構(gòu)。mysql表中的每一行記錄都有一個硬件地址,例如索引中的age=50,指向的就是源表中該行的標(biāo)識符(“硬件地址”)。也就是說,樹形索引建立了與源表中每行記錄硬件地址的映射關(guān)系,當(dāng)你指定了某個索引,這種映射關(guān)系也就建成了,這就是為什么我們可以通過索引快速定位源表中記錄的原因。

        以【select * from student where age=33】查詢語句為例。當(dāng)我們不加索引的時候,會從上到下掃描源表,當(dāng)掃描到第5行的時候,找到了我們想要找到了元素,一共是查詢了5次。當(dāng)添加了索引以后,就直接在樹形結(jié)構(gòu)中進行查找,33比50小,就從左側(cè)查詢到了23,33大于23,就又查詢到了右側(cè),這下找到了33,整個索引結(jié)束,一共進行了3次查找。是不是很方便,假如我們此時需要查找age=62,你再想想“添加索引”前后,查找次數(shù)的變化情況。

        4)索引的弊端

        1.當(dāng)數(shù)據(jù)量很大的時候,索引也會很大(當(dāng)然相比于源表來說,還是相當(dāng)小的),也需要存放在內(nèi)存/硬盤中(通常存放在硬盤中),占據(jù)一定的內(nèi)存空間/物理空間。

        2.索引并不適用于所有情況:a.少量數(shù)據(jù);b.頻繁進行改動的字段,不適合做索引;c.很少使用的字段,不需要加索引;

        3.索引會提高數(shù)據(jù)查詢效率,但是會降低“增、刪、改”的效率。當(dāng)不使用索引的時候,我們進行數(shù)據(jù)的增刪改,只需要操作源表即可,但是當(dāng)我們添加索引后,不僅需要修改源表,也需要再次修改索引,很麻煩。盡管是這樣,添加索引還是很劃算的,因為我們大多數(shù)使用的就是查詢,“查詢”對于程序的性能影響是很大的。

        5)索引的優(yōu)勢

        1.提高查詢效率(降低了IO使用率)。當(dāng)創(chuàng)建了索引后,查詢次數(shù)減少了。

        2.降低CPU使用率。比如說【…order by age desc】這樣一個操作,當(dāng)不加索引,會把源表加載到內(nèi)存中做一個排序操作,極大的消耗了資源。但是使用了索引以后,第一索引本身就小一些,第二索引本身就是排好序的,左邊數(shù)據(jù)最小,右邊數(shù)據(jù)最大。

        6)B+樹圖示說明

        MySQL中索引使用的就是B+樹結(jié)構(gòu)。


        關(guān)于B+樹的說明:

        首先,Btree一般指的都是【B+樹】,數(shù)據(jù)全部存放在葉子節(jié)點中。對于上圖來說,最下面的第3層,屬于葉子節(jié)點,真實數(shù)據(jù)部份都是存放在葉子節(jié)點當(dāng)中的。那么對于第1、2層中的數(shù)據(jù)又是干嘛的呢?答:用于分割指針塊兒的,比如說小于26的找P1,介于26-30之間的找P2,大于30的找P3。

        其次,三層【B+樹】可以存放上百萬條數(shù)據(jù)。這么多數(shù)據(jù)怎么放的呢?增加“節(jié)點數(shù)”。圖中我們只有三個節(jié)點。

        最后,【B+樹】中查詢?nèi)我鈹?shù)據(jù)的次數(shù),都是n次,n表示的是【B+樹】的高度。

        3、索引的分類與創(chuàng)建

        1)索引分類

        單值索引

        唯一索引

        復(fù)合索引

        ① 單值索引

        利用表中的某一個字段創(chuàng)建單值索引。一張表中往往有多個字段,也就是說每一列其實都可以創(chuàng)建一個索引,這個根據(jù)我們實際需求來進行創(chuàng)建。還需要注意的一點就是,一張表可以創(chuàng)建多個“單值索引”。

        假如某一張表既有age字段,又有name字段,我們可以分別對age、name創(chuàng)建一個單值索引,這樣一張表就有了兩個單值索引。

        ② 唯一索引

        也是利用表中的某一個字段創(chuàng)建單值索引,與單值索引不同的是:創(chuàng)建唯一索引的字段中的數(shù)據(jù),不能有重復(fù)值。像age肯定有很多人的年齡相同,像name肯定有些人是重名的,因此都不適合創(chuàng)建“唯一索引”。像編號id、學(xué)號sid,對于每個人都不一樣,因此可以用于創(chuàng)建唯一索引。

        ③ 復(fù)合索引

        多個列共同構(gòu)成的索引。比如說我們創(chuàng)建這樣一個“復(fù)合索引”(name,age),先利用name進行索引查詢,當(dāng)name相同的時候,我們利用age再進行一次篩選。注意:復(fù)合索引的字段并不是非要都用完,當(dāng)我們利用name字段索引出我們想要的結(jié)果以后,就不需要再使用age進行再次篩選了。

        2)創(chuàng)建索引

        ① 語法

        語法:create 索引類型 索引名 on 表(字段);

        建表語句如下:

        查詢表結(jié)構(gòu)如下:


        ② 創(chuàng)建索引的第一種方式

        Ⅰ 創(chuàng)建單值索引

        create?index?dept_index?on?tb(dept);

        Ⅱ 創(chuàng)建唯一索引:這里我們假定name字段中的值都是唯一的

        create?unique?index?name_index?on?tb(name);

        Ⅲ 創(chuàng)建復(fù)合索引

        create?index?dept_name_index?on?tb(dept,name);

        ③ 創(chuàng)建索引的第二種方式

        先刪除之前創(chuàng)建的索引以后,再進行這種創(chuàng)建索引方式的測試;

        語法:alter table 表名 add 索引類型 索引名(字段)

        Ⅰ 創(chuàng)建單值索引

        alter?table?tb?add?index?dept_index(dept);

        Ⅱ 創(chuàng)建唯一索引:這里我們假定name字段中的值都是唯一的

        alter?table?tb?add?unique?index?name_index(name);

        Ⅲ 創(chuàng)建復(fù)合索引

        alter?table?tb?add?index?dept_name_index(dept,name);

        ④ 補充說明

        如果某個字段是primary key,那么該字段默認就是主鍵索引。

        主鍵索引和唯一索引非常相似。相同點:該列中的數(shù)據(jù)都不能有相同值;不同點:主鍵索引不能有null值,但是唯一索引可以有null值。

        3)索引刪除和索引查詢

        ① 索引刪除

        語法:drop index 索引名 on 表名;

        drop?index?name_index?on?tb;

        ② 索引查詢

        語法:show index from 表名;

        show?index?from?tb;

        結(jié)果如下:


        4、SQL性能問題的探索

        人為優(yōu)化:需要我們使用explain分析SQL的執(zhí)行計劃。該執(zhí)行計劃可以模擬SQL優(yōu)化器執(zhí)行SQL語句,可以幫助我們了解到自己編寫SQL的好壞。

        SQL優(yōu)化器自動優(yōu)化:最開始講述MySQL執(zhí)行原理的時候,我們已經(jīng)知道MySQL有一個優(yōu)化器,當(dāng)你寫了一個SQL語句的時候,SQL優(yōu)化器如果認為你寫的SQL語句不夠好,就會自動寫一個好一些的等價SQL去執(zhí)行。

        SQL優(yōu)化器自動優(yōu)化功能【會干擾】我們的人為優(yōu)化功能。當(dāng)我們查看了SQL執(zhí)行計劃以后,如果寫的不好,我們會去優(yōu)化自己的SQL。當(dāng)我們以為自己優(yōu)化的很好的時候,最終的執(zhí)行計劃,并不是按照我們優(yōu)化好的SQL語句來執(zhí)行的,而是有時候?qū)⑽覀儍?yōu)化好的SQL改變了,去執(zhí)行。

        SQL優(yōu)化是一種概率問題,有時候系統(tǒng)會按照我們優(yōu)化好的SQL去執(zhí)行結(jié)果(優(yōu)化器覺得你寫的差不多,就不會動你的SQL)。有時候優(yōu)化器仍然會修改我們優(yōu)化好的SQL,然后再去執(zhí)行。

        1)查看執(zhí)行計劃

        語法:explain + SQL語句

        eg:explain select * from tb;

        2)“執(zhí)行計劃”中需要知道的幾個“關(guān)鍵字”

        id :編號

        select_type :查詢類型

        table :表

        type :類型

        possible_keys :預(yù)測用到的索引

        key :實際使用的索引

        key_len :實際使用索引的長度

        ref :表之間的引用

        rows :通過索引查詢到的數(shù)據(jù)量

        Extra :額外的信息

        建表語句和插入數(shù)據(jù):

        #?建表語句
        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)
        );

        #?插入數(shù)據(jù)
        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')?;

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

        1)id關(guān)鍵字的使用說明

        ① 案例:查詢課程編號為2 或 教師證編號為3 的老師信息:

        #?查看執(zhí)行計劃
        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í)行計劃。

        #?查看執(zhí)行計劃
        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ù)量改變而改變的原因:笛卡爾積。

        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ù),雖然是一致的。但是中間過程,有一張臨時表是6,一張臨時表是12,很明顯6 < 12,對于內(nèi)存來說,數(shù)據(jù)量越小越好,因此優(yōu)化器肯定會選擇第一種執(zhí)行順序。

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

        ② 案例:查詢教授SQL課程的老師的描述(desc)

        #?查看執(zhí)行計劃
        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)先查詢。這是由于在進行嵌套子查詢時,先查內(nèi)層,再查外層。

        ③ 針對②做一個簡單的修改

        #?查看執(zhí)行計劃
        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í)行。

        2)select_type關(guān)鍵字的使用說明:查詢類型


        ① simple:簡單查詢

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

        explain?select?*?from?teacher;

        結(jié)果如下:


        ② 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?;

        結(jié)果如下:


        ⑤ union:union之后的表稱之為union表,如上例

        ⑥ union result:告訴我們,哪些表之間使用了union查詢

        3)type關(guān)鍵字的使用說明:索引類型

        system、const只是理想狀況,實際上只能優(yōu)化到index --> range --> ref這個級別。要對type進行優(yōu)化的前提是,你得創(chuàng)建索引。


        ① system

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

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

        ② const

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

        explain?select?tid?from?test01?where?tid?=1?;

        結(jié)果如下:


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

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

        結(jié)果如下:


        ③ eq_ref

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

        此種情況常見于唯一索引和主鍵索引。

        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ù)個數(shù)和連接查詢的數(shù)據(jù)個數(shù)一致(都是3條數(shù)據(jù)),則有可能滿足eq_ref級別;否則無法滿足。條件很苛刻,很難達到。

        ④ ref

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

        準備數(shù)據(jù):


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

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

        結(jié)果如下:


        ⑤ range

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

        in有時候會失效,從而轉(zhuǎn)為無索引時候的ALL

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

        結(jié)果如下:


        ⑥ index

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

        ⑦ ALL

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


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

        4)possible_keys和key

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

        key指的是實際使用的索引。

        #?先給course表的cname字段,添加一個索引
        create?index?cname_index?on?course(cname);
        #?查看執(zhí)行計劃
        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é)果如下:


        有一點需要注意的是:如果possible_key/key是NULL,則說明沒用索引。


        5)key_len

        索引的長度,用于判斷復(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í)行計劃
        explain?select?*?from?test_kl?where?name?=''?;?


        結(jié)果如下:


        結(jié)果分析:因為我沒有設(shè)置服務(wù)端的字符集,因此默認的字符集使用的是latin1,對于latin1一個字符代表一個字節(jié),因此這列的key_len的長度是20,表示使用了name這個索引。


        ② 給test_kl表,新增name1列,該列沒有設(shè)置“not null”

        結(jié)果如下:


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

        ③ 刪除原來的索引name和name1,新增一個復(fù)合索引

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

        結(jié)果如下:


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

        ④ 再次怎加一個name2字段,并為該字段創(chuàng)建一個索引。

        不同的是:該字段數(shù)據(jù)類型是varchar

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

        結(jié)果如下:


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

        6)ref

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

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

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

        結(jié)果如下:


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

        7)rows(這個目前還是有點疑惑)

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

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

        結(jié)果如下:


        8)extra

        表示其他的一些說明,也很有用。

        ① using filesort:針對單索引的情況

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

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

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

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

        #?新建一張表,建表同時創(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í)行計劃
        explain?select?*?from?test02?where?a1?=''?order?by?a1?;
        explain?select?*?from?test02?where?a1?=''?order?by?a2?;?

        結(jié)果如下:


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

        總結(jié):對于單索引,如果排序和查找是同一個字段,則不會出現(xiàn)using filesort;如果排序和查找不是同一個字段,則會出現(xiàn)using filesort;因此where哪些字段,就order by哪些些字段。

        ② using filesort:針對復(fù)合索引的情況

        不能跨列(官方術(shù)語:最佳左前綴)

        #?刪除test02的索引
        drop?index?idx_a1?on?test02;
        drop?index?idx_a2?on?test02;
        drop?index?idx_a3?on?test02;
        #?創(chuàng)建一個復(fù)合索引
        alter?table?test02?add?index?idx_a1_a2_a3?(a1,a2,a3)?;
        #?查看下面SQL語句的執(zhí)行計劃
        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就叫做“最佳左前綴”,如果要使用后面的索引字段,必須先使用到這個a1字段。對于explain1,where后面我們使用a1字段,但是后面的排序使用了a3,直接跳過了a2,屬于跨列;對于explain2,where后面我們使用了a2字段,直接跳過了a1字段,也屬于跨列;對于explain3,where后面我們使用a1字段,后面使用的是a2字段,因此沒有出現(xiàn)【using filesort】。

        ③ using temporary

        當(dāng)出現(xiàn)了這個詞,也表示你當(dāng)前的SQL性能消耗較大。這是由于當(dāng)前SQL用到了臨時表。一般出現(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)你查詢哪個字段,就按照那個字段分組,否則就會出現(xiàn)using temporary。

        針對using temporary,我們在看一個例子:

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

        再次來看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語句的執(zhí)行計劃。

        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;

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

        ④ using index

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

        using index稱之為“索引覆蓋”。

        當(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)建一個復(fù)合索引idx_a1_a2
        create?index?idx_a1_a2?on?test02(a1,a2);
        #?查看執(zhí)行計劃
        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ù)合索引,對于第一個執(zhí)行計劃,我們卻出現(xiàn)了a3,該字段并沒有創(chuàng)建索引,因此沒有出現(xiàn)using index,而是using where,表示我們需要回表查詢。對于第二個執(zhí)行計劃,屬于完全的索引覆蓋,因此出現(xiàn)了using index。

        針對using index,我們在查看一個案例:

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

        結(jié)果如下:

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

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

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

        ⑤ using where

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

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

        結(jié)果如下:


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

        ⑥ impossible where(了解)

        當(dāng)where子句永遠為False的時候,會出現(xiàn)impossible where

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

        結(jié)果如下:


        6、優(yōu)化示例

        1)引入案例

        #?創(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)建一個復(fù)合索引
        create?index?a1_a2_a3_test03?on?test03(a1,a2,a3);
        #?查看執(zhí)行計劃
        explain?select?a3?from?test03?where?a1=1?and?a2=2?and?a3=3;

        結(jié)果如下:


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

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

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

        結(jié)果如下:


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

        最后再補充一點:對于復(fù)合索引,不要跨列使用

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

        結(jié)果如下:


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

        2)單表優(yōu)化

        #?創(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é)果如下:


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

        優(yōu)化:添加索引的時候,要根據(jù)MySQL解析順序添加索引,又回到了MySQL的解析順序,下面我們再來看看MySQL的解析順序。

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

        ① 優(yōu)化1:基于此,我們進行索引的添加,并再次查看執(zhí)行計劃。

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

        結(jié)果如下:


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

        ② 優(yōu)化2:使用了in有時候會導(dǎo)致索引失效,基于此有了如下一種優(yōu)化思路。

        將in字段放在最后面。需要注意一點:每次創(chuàng)建新的索引的時候,最好是刪除以前的廢棄索引,否則有時候會產(chǎn)生干擾(索引之間)。

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

        結(jié)果如下:


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

        總結(jié)如下:

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

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

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

        本例中同時出現(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)于沒有typeid這個索引,所以需要回原表(using where);

        例如以下沒有了In,則不會出現(xiàn)using where:

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

        結(jié)果如下:


        3)兩表優(yōu)化

        #?創(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');

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

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

        結(jié)果如下:


        ① 優(yōu)化

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

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

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

        分析:以上2個FOR循環(huán),最終都會循環(huán)3000次;但是對于雙層循環(huán)來說:一般建議,將數(shù)據(jù)小的循環(huán),放外層。數(shù)據(jù)大的循環(huán),放內(nèi)層。不用管這是為什么,這是編程語言的一個原則,對于雙重循環(huán),外層循環(huán)少,內(nèi)存循環(huán)大,程序的性能越高。

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

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

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

        #?給左表的字段加索引
        create?index?cid_teacher2?on?teacher2(cid);
        #?查看執(zhí)行計劃
        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添加一個索引。索引優(yōu)化是一個逐步的過程,需要一點點嘗試。

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

        結(jié)果如下:


        最后補充一個:Using join buffer是extra中的一個選項,表示Mysql引擎使用了“連接緩存”,即MySQL底層動了你的SQL,你寫的太差了。

        4)三表優(yōu)化

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

        • 小表驅(qū)動大表

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

        7、避免索引失效的一些原則

        ① 復(fù)合索引需要注意的點

        • 復(fù)合索引,不要跨列或無序使用(最佳左前綴)

        • 復(fù)合索引,盡量使用全索引匹配,也就是說,你建立幾個索引,就使用幾個索引

        ② 不要在索引上進行任何操作(計算、函數(shù)、類型轉(zhuǎn)換),否則索引失效

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

        結(jié)果如下:


        ③ 索引不能使用不等于(!= <>)或is null (is not null),否則自身以及右側(cè)所有全部失效(針對大多數(shù)情況)。復(fù)合索引中如果有>,則自身和右側(cè)索引全部失效。

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

        結(jié)果如下:


        再觀看下面這個案例:

        #?刪除單獨的索引
        drop?index?authorid_index?on?book;
        drop?index?typeid_index?on?book;
        #?創(chuàng)建一個復(fù)合索引
        alter?table?book?add?index?idx_book_at?(authorid,typeid);
        #?查看執(zhí)行計劃
        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)化 ,是一個大部分情況適用的結(jié)論,但由于SQL優(yōu)化器等原因 該結(jié)論不是100%正確。一般而言, 范圍查詢(> < in),之后的索引失效。

        ④ SQL優(yōu)化,是一種概率層面的優(yōu)化。至于是否實際使用了我們的優(yōu)化,需要通過explain進行推測。

        #?刪除復(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í)行計劃
        explain?select?*?from?book?where?authorid?=?1?and?typeid?=2?;

        結(jié)果如下:


        結(jié)果分析:我們創(chuàng)建了兩個索引,但是實際上只使用了一個索引。因為對于兩個單獨的索引,程序覺得只用一個索引就夠了,不需要使用兩個。

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

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

        結(jié)果如下:


        ⑤ 索引覆蓋,百分之百沒問題

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


        結(jié)果如下:


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

        ⑦ 盡量不要使用類型轉(zhuǎn)換(顯示、隱式),否則索引失效

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

        結(jié)果如下:


        ⑧ 盡量不要使用or,否則索引失效

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

        結(jié)果如下:


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

        8、一些其他的優(yōu)化方法

        1)exists和in的優(yōu)化

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

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

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

        2)order by優(yōu)化

        • IO就是訪問硬盤文件的次數(shù)

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

        • MySQL4.1之前默認使用雙路排序;雙路:掃描2次磁盤(1:從磁盤讀取排序字段,對排序字段進行排序(在buffer中進行的排序)2:掃描其他字段)

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

        • 注意:單路排序 比雙路排序 會占用更多的buffer。

        • 單路排序在使用時,如果數(shù)據(jù)大,可以考慮調(diào)大buffer的容量大?。?/span>

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

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

        ① 提高order by查詢的策略:

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

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

        • 復(fù)合索引,不要跨列使用 ,避免using filesort保證全部的排序字段,排序的一致性(都是升序或降序)

        篇幅很長,內(nèi)容較多,建議收藏。

        點擊下方卡片進行關(guān)注,獲取更多內(nèi)容
        點分享
        點收藏
        點點贊
        點在看
        瀏覽 33
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            久久日韩乱码一二三四区别 | 欧美性猛交一区二区三区精品 | 狠狠狠狠的青草 | 无码av毛片一区二区三区性色 | 天天看天天碰天天摸 | 97无码 | 中文字幕人妻AV | 大香蕉久久爱 | 欧美人与性动交a美精品 | 欧美日韩中文字幕视频 |