2 mysql索引優(yōu)化分析

個人專題目錄


1. 性能下降SQL慢 執(zhí)行時間長 等待時間長

1.1 查詢語句寫的爛

1.2 索引失效

單值

復(fù)合

1.3 關(guān)聯(lián)查詢太多join(設(shè)計缺陷或不得已的需求)

1.4 服務(wù)器調(diào)優(yōu)及各個參數(shù)設(shè)置(緩沖\線程數(shù)等)

2. 常見通用的join查詢

2.1 SQL執(zhí)行順序

手寫

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 >

機讀

FROM <left_table>
ON <join_condition>
<join_type> JOIN <right_table>
WHERE <where_condition>
GROUP BY <group_by_list>
HAVING <having_condition>
SELECT
DISTINCT <select_list>
ORDER BY <order_by_condition>
LIMIT <limit_number>

總結(jié)

clip_image044.jpg

2.2 Join圖

clip_image046.jpg

2.3 建表SQL

2.4 7種Join

3. 索引簡介

3.1 是什么

MySQL官方對索引的定義為:索引(Index)是幫助MySQL高校獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。 可以得到索引的本質(zhì):索引是數(shù)據(jù)結(jié)構(gòu)

  • 索引的目的在于提高查詢效率,可以類比字典。
  • 如果要查‘mysql’這個單詞,我們肯定需求定位到m字母,然后從上往下找到y(tǒng)字母,再找到sql.
  • 如果沒有索引,順序遍歷

你可以簡單理解為"排好序的快速查找數(shù)據(jù)結(jié)構(gòu)"。

詳解(重要)

clip_image050.jpg

結(jié)論

數(shù)據(jù)本身之外,數(shù)據(jù)庫還維護著一個滿足特定查找算法的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)以某種方式指向數(shù)據(jù), 這樣就可以在這些數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上實現(xiàn)高級查找算法,這種數(shù)據(jù)結(jié)構(gòu)就是索引。

一般來說索引本身也很大,不可能全部存儲在內(nèi)存中,因此索引往往以文件形式存儲在硬盤上

我們平時所說的索引,如果沒有特別指明,都是指B樹(多路搜索樹,并不一定是二叉樹)結(jié)構(gòu)組織的索引。其中聚集索引,次要索引,覆蓋索引, 復(fù)合索引,前綴索引,唯一索引默認(rèn)都是使用B+樹索引,統(tǒng)稱索引。當(dāng)然,除了B+樹這種類型的索引之外,還有哈希索引(hash index)等。

3.2 優(yōu)勢

類似大學(xué)圖書館建書目索引,提高數(shù)據(jù)檢索效率,降低數(shù)據(jù)庫的IO成本

通過索引列對數(shù)據(jù)進行排序,降低數(shù)據(jù)排序成本,降低了CPU的消耗

為什么要創(chuàng)建索引?這是因為,創(chuàng)建索引可以大大提高系統(tǒng)的查詢性能。

第一、通過創(chuàng)建唯一性索引,可以保證數(shù)據(jù)庫表中每一行數(shù)據(jù)的唯一性。

第二、可以大大加快 數(shù)據(jù)的檢索速度,這也是創(chuàng)建索引的最主要的原因。

第三、可以加速表和表之間的連接,特別是在實現(xiàn)數(shù)據(jù)的參考完整性方面特別有意義。

第四、在使用分組和排序子句進行數(shù)據(jù)檢索時,同樣可以顯著減少查詢中分組和排序的時間。

第五、通過使用索引,可以在查詢的過程中,使用查詢優(yōu)化器,提高系統(tǒng)的性能。

3.3 劣勢

實際上索引也是一張表,該表保存了主鍵和索引字段,并指向?qū)嶓w表的記錄,所以索引列也是要占用空間的

雖然索引大大提高了查詢速度,同時卻會降低更新表的速度,如果對表INSERT,UPDATE和DELETE。 因為更新表時,MySQL不僅要不存數(shù)據(jù),還要保存一下索引文件每次更新添加了索引列的字段, 都會調(diào)整因為更新所帶來的鍵值變化后的索引信息

索引只是提高效率的一個因素,如果你的MySQL有大數(shù)據(jù)量的表,就需要花時間研究建立優(yōu)秀的索引,或優(yōu)化查詢語句

3.4 mysql索引分類

單值索引

即一個索引只包含單個列,一個表可以有多個單列索引

建議一張表索引不要超過5個

優(yōu)先考慮復(fù)合索引

唯一索引

索引列的值必須唯一,但允許有空值

復(fù)合索引

即一個索引包含多個列

基本語法

創(chuàng)建

CREATE [UNIQUE] INDEX indexName ON mytable(columnname(length));

如果是CHAR,VARCHAR類型,length可以小于字段實際長度; 如果是BLOB和TEXT類型,必須指定length。

ALTER mytable ADD [UNIQUE] INDEX [indexName] ON(columnname(length));

刪除

DROP INDEX [indexName] ON mytable;

查看

SHOW INDEX FROM table_name\G

使用ALTER命令

clip_image052.jpg

3.5 mysql索引結(jié)構(gòu)

BTree索引

B-Tree索引,顧名思義,就是所有的索引節(jié)點都按照balance tree的數(shù)據(jù)結(jié)構(gòu)來存儲。B-tree結(jié)構(gòu)可以顯著減少定位記錄時所經(jīng)歷的中間過程,從而加快存取速度。

B-tree中,每個結(jié)點包含:

1、本結(jié)點所含關(guān)鍵字的個數(shù);

2、指向父結(jié)點的指針;

3、關(guān)鍵字;

4、指向子結(jié)點的指針;

對于一棵m階B-tree,每個結(jié)點至多可以擁有m個子結(jié)點。各結(jié)點的關(guān)鍵字和可以擁有的子結(jié)點數(shù)都有限制,規(guī)定m階B-tree中,根結(jié)點至少有2個子結(jié)點,除非根結(jié)點為葉子節(jié)點,相應(yīng)的,根結(jié)點中關(guān)鍵字的個數(shù)為1m-1;非根結(jié)點至少有[m/2]([],向上取整)個子結(jié)點,相應(yīng)的,關(guān)鍵字個數(shù)為[m/2]-1m-1。

B-tree有以下特性:

1、關(guān)鍵字集合分布在整棵樹中;

2、任何一個關(guān)鍵字出現(xiàn)且只出現(xiàn)在一個結(jié)點中;

3、搜索有可能在非葉子結(jié)點結(jié)束;

4、其搜索性能等價于在關(guān)鍵字全集內(nèi)做一次二分查找;

5、自動層次控制;

由于限制了除根結(jié)點以外的非葉子結(jié)點,至少含有M/2個兒子,確保了結(jié)點的至少利用率,其最低搜索性能為:

clip_image004.jpg

其中,M為設(shè)定的非葉子結(jié)點最多子樹個數(shù),N為關(guān)鍵字總數(shù);

所以B-樹的性能總是等價于二分查找(與M值無關(guān)),也就沒有B樹平衡的問題;

由于M/2的限制,在插入結(jié)點時,如果結(jié)點已滿,需要將結(jié)點分裂為兩個各占M/2的結(jié)點;刪除結(jié)點時,需將兩個不足M/2的兄弟結(jié)點合并。

clip_image054.jpg

初始化介紹

一顆b+樹,淺藍(lán)色的塊我們稱為一個磁盤塊,可以看到每個磁盤塊包含幾個數(shù)據(jù)項(深藍(lán)色表示)和指針(黃色表示),如磁盤塊1包含數(shù)據(jù)項17和35,包含指針P1、P2、P3.

P1表示小于17的磁盤塊,P2表示在17到35之間的磁盤塊,P3表示大于35的磁盤塊。

真實的數(shù)據(jù)存在于葉子節(jié)點,即3,5,9,10,13,15,28,29,36...

非葉子節(jié)點只不存儲真實的數(shù)據(jù),只存儲引擎搜索方向的數(shù)據(jù)項,如17、35并不是真實存于數(shù)據(jù)表中。

【查找過程】

如果要查找數(shù)據(jù)項29,那么首先會把磁盤塊1由磁盤加載到內(nèi)存,此時發(fā)生一次IO,在內(nèi)存中用二分查找確實29在17和35之間,鎖定磁盤塊1的P2指針,內(nèi)存時間因為非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內(nèi)存,發(fā)生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內(nèi)存,發(fā)生第三次IO,同時內(nèi)存中做二分查找找到29,結(jié)束查詢,總計三次IO。

Btree索引(或Balanced Tree),是一種很普遍的數(shù)據(jù)庫索引結(jié)構(gòu),oracle默認(rèn)的索引類型(本文也主要依據(jù)oracle來講)。其特點是定位高效、利用率高、自我平衡,特別適用于高基數(shù)字段,定位單條或小范圍數(shù)據(jù)非常高效。理論上,使用Btree在億條數(shù)據(jù)與100條數(shù)據(jù)中定位記錄的花銷相同。

數(shù)據(jù)結(jié)構(gòu)利用率高、定位高效

Btree索引的數(shù)據(jù)結(jié)構(gòu)如下:

clip_image058.jpg

結(jié)構(gòu)看起來Btree索引與Binary Tree相似,但在細(xì)節(jié)上有所不同,上圖中用不同顏色的標(biāo)示出了Btree索引的幾個主要特點:

樹形結(jié)構(gòu):由根節(jié)(root)、分支(branches)、葉(leaves)三級節(jié)點組成,其中分支節(jié)點可以有多層。

多分支結(jié)構(gòu):與binary tree不相同的是,btree索引中單root/branch可以有多個子節(jié)點(超過2個)。

雙向鏈表:整個葉子節(jié)點部分是一個雙向鏈表(后面會描述這個設(shè)計的作用)

單個數(shù)據(jù)塊中包括多條索引記錄

這里先把幾個特點羅列出來,后面會說到各自的作用。

結(jié)構(gòu)上Btree與Binary Tree的區(qū)別,在于binary中每節(jié)點代表一個數(shù)值,而balanced中root和Btree節(jié)點中記錄了多條”值范圍”條目(如:[60-70][70-80]),這些”值范圍”條目分別指向在其范圍內(nèi)的葉子節(jié)點。既root與branch可以有多個分支,而不一定是兩個,對數(shù)據(jù)塊的利用率更高。

在Leaf節(jié)點中,同樣也是存放了多條索引記錄,這些記錄就是具體的索引列值,和與其對應(yīng)的rowid。另外,在葉節(jié)點層上,所有的節(jié)點在組成了一個雙向鏈表。

了解基本結(jié)構(gòu)后,下圖展示定位數(shù)值82的過程:

clip_image060.jpg

演算如下:

讀取root節(jié)點,判斷82大于在0-120之間,走左邊分支。

讀取左邊branch節(jié)點,判斷82大于80且小于等于120,走右邊分支。

讀取右邊leaf節(jié)點,在該節(jié)點中找到數(shù)據(jù)82及對應(yīng)的rowid

使用rowid去物理表中讀取記錄數(shù)據(jù)塊(如果是count或者只select rowid,則最后一次讀取不需要)

在整個索引定位過程中,數(shù)據(jù)塊的讀取只有3次。既三次I/O后定位到rowid。

而由于Btree索引對結(jié)構(gòu)的利用率很高,定位高效。當(dāng)1千萬條數(shù)據(jù)時,Btree索引也是三層結(jié)構(gòu)(依稀記得億級數(shù)據(jù)才是3層與4層的分水嶺)。定位記錄仍只需要三次I/O,這便是開頭所說的,100條數(shù)據(jù)和1千萬條數(shù)據(jù)的定位,在btree索引中的花銷是一樣的。

平衡擴張

除了利用率高、定位高效外,Btree的另一個特點是能夠永遠(yuǎn)保持平衡,這與它的擴張方式有關(guān)。(unbalanced和hotspot是兩類問題,之前我一直混在一起),先描述下Btree索引的擴張方式:

新建一個索引,索引上只會有一個leaf節(jié)點,取名為Node A,不斷的向這個leaf節(jié)點中插入數(shù)據(jù)后,直到這個節(jié)點滿,這個過程如下圖(綠色表示新建/空閑狀態(tài),紅色表示節(jié)點沒有空余空間):

clip_image062.jpg

當(dāng)Node A滿之后,我們再向表中插入一條記錄,此時索引就需要做拆分處理:會新分配兩個數(shù)據(jù)塊NodeB & C,如果新插入的值,大于當(dāng)前最大值,則將Node A中的值全部插入Node B中,將新插入的值放到Node C中;否則按照5-5比例,將已有數(shù)據(jù)分別插入到NodeB與C中。

無論采用哪種分割方式,之前的leaf節(jié)點A,將變成一個root節(jié)點,保存兩個范圍條目,指向B與C,結(jié)構(gòu)如下圖(按第一種拆分形式):

clip_image064.jpg

當(dāng)Node C滿之后,此時 Node A仍有空余空間存放條目,所以不需要再拆分,而只是新分配一個數(shù)據(jù)塊Node D,將在Node A中創(chuàng)建指定到Node D的條目:

clip_image066.jpg

如果當(dāng)根節(jié)點Node A也滿了,則需要進一步拆分:新建Node E&F&G,將Node A中范圍條目拆分到E&F兩個節(jié)點中,并建立E&F到BCD節(jié)點的關(guān)聯(lián),向Node G插入索引值。此時E&F為branch節(jié)點,G為leaf節(jié)點,A為Root節(jié)點:

clip_image068.jpg

在整個擴張過程中,Btree自身總能保持平衡,Leaf節(jié)點的深度能一直保持一致。

實際應(yīng)用中的一些問題

前面說完了Btree索引的結(jié)構(gòu)與擴張邏輯,接下來講一些Btree索引在應(yīng)用中的一些問題:

單一方向擴展引起的索引競爭(Index Contention)

若索引列使用sequence或者timestamp這類只增不減的數(shù)據(jù)類型。這種情況下Btree索引的增長方向總是不變的,不斷的向右邊擴展,因為新插入的值永遠(yuǎn)是最大的。

當(dāng)一個最大值插入到leaf block中后,leaf block要向上傳播,通知上層節(jié)點更新所對應(yīng)的“值范圍”條目中的最大值,因此所有靠右邊的block(從leaf 到branch甚至root)都需要做更新操作,并且可能因為塊寫滿后執(zhí)行塊拆分。

如果并發(fā)插入多個最大值,則最右邊索引數(shù)據(jù)塊的的更新與拆分都會存在爭搶,影響效率。在AWR報告中可以通過檢測enq: TX – index contention事件的時間來評估爭搶的影響。解決此類問題可以使用Reverse Index解決,不過會帶來新的問題。

Index Browning 索引枯萎(不知道該怎么翻譯這個名詞,就是指leaves節(jié)點”死”了,樹枯萎了)

其實oracle針對這個問題有優(yōu)化機制,但優(yōu)化的不徹底,所以還是要拿出來的說。

我們知道當(dāng)表中的數(shù)據(jù)刪除后,索引上對應(yīng)的索引值是不會刪除的,特別是在一性次刪除大批量數(shù)據(jù)后,會造成大量的dead leaf掛到索引樹上??紤]以下示例,如果表100以上的數(shù)據(jù)會部被刪除了,但這些記錄仍在索引中存在,此時若對該列取max():

clip_image070.jpg

通過與之前相同演算,找到了索引樹上最大的數(shù)據(jù)塊,按照記錄最大的值應(yīng)該在這里,但發(fā)現(xiàn)這數(shù)據(jù)塊里的數(shù)據(jù)已經(jīng)被清空了,與是利用Btree索引的另一個特點:leaves節(jié)點是一個雙向列表,若數(shù)據(jù)沒有找到就去臨近的一個數(shù)據(jù)塊中看看,在這個數(shù)據(jù)塊中發(fā)現(xiàn)了最大值99。

在計算最大值的過程中,這次的定位多加載了一個數(shù)據(jù)塊,再極端的情況下,大批量的數(shù)據(jù)被刪除,就會造成大量訪問這些dead leaves。

針對這個問題的一般解決辦法是重建索引,但記住! 重建索引并不是最優(yōu)方案,詳細(xì)原因可以看看這。使用coalesce語句來整理這些dead leaves到freelist中,就可以避免這些問題。理論上oracle中這步操作是可以自動完成的,但在實際中一次性大量刪除數(shù)據(jù)后,oracle在短時間內(nèi)是反應(yīng)不過來的。

Hash索引,了解

full-text全文索引,F(xiàn)ull-text索引就是我們常說的全文索引,他的存儲結(jié)構(gòu)也是b-tree。主要是為了解決在我們需要用like查詢的低效問題。只能解決’xxx%’的like查詢。如:字段數(shù)據(jù)為ABCDE,索引建立為- A、AB、ABC、ABCD、ABCDE五個。

R-Tree索引,了解

3.6 哪些情況需要創(chuàng)建索引

1.主鍵自動建立唯一索引

2.頻繁作為查詢的條件的字段應(yīng)該創(chuàng)建索引

3.查詢中與其他表關(guān)聯(lián)的字段,外鍵關(guān)系建立索引

4.頻繁更新的字段不適合創(chuàng)建索引

因為每次更新不單單是更新了記錄還會更新索引,加重IO負(fù)擔(dān)

5.Where條件里用不到的字段不創(chuàng)建索引

6.單間/組合索引的選擇問題,who?(在高并發(fā)下傾向創(chuàng)建組合索引)

7.查詢中排序的字段,排序字段若通過索引去訪問將大大提高排序的速度

8.查詢中統(tǒng)計或者分組字段

建立索引,一般按照select的where條件來建立,比如: select的條件是where f1 and f2,那么如果我們在字段f1或字段f2上建立索引是沒有用的,只有在字段f1和f2上同時建立索引才有用等。

3.7 哪些情況不要創(chuàng)建索引

1.表記錄太少

2.經(jīng)常增刪改的表,這是因為,修改性能和檢索性能是互相矛盾的。當(dāng)增加索引時,會提高檢索性能,但是會降低修改性能。當(dāng)減少索引時,會提高修改性能,降低檢索性能。因此,當(dāng)修改性能遠(yuǎn)遠(yuǎn)大于檢索性能時,不應(yīng)該創(chuàng)建索引。

3.數(shù)據(jù)重復(fù)且分布平均的表字段,因此應(yīng)該只為經(jīng)常查詢和經(jīng)常排序的數(shù)據(jù)列建立索引。 注意,如果某個數(shù)據(jù)列包含許多重復(fù)的內(nèi)容,為它建立索引就沒有太大的實際效果。

假如一個表有10萬行記錄,有一個字段A只胡T和F兩種值,且每個值的分布概率大約為50%,那么對這種表A字段建索引一般不會提高數(shù)據(jù)庫的查詢速度。

索引的選擇性是指索引列中不同值的數(shù)據(jù)與不用跟記錄數(shù)的比。如果一個表中有2000條記錄,表索引列有1980個不同的值,那么這個索引的選擇性是1980/2000=0.99。一個索引的選擇性越接近于1,這個索引的效率就越高。

  1. 對于那些定義為text, image和bit數(shù)據(jù)類型的列不應(yīng)該增加索引。這是因為,這些列的數(shù)據(jù)量要么相當(dāng)大,要么取值很少。

3.8 索引管理

普通索引

這是最基本的索引,它沒有任何限制MyIASM中默認(rèn)的BTREE類型的索引,也是我們大多數(shù)情況下用到的索引。

創(chuàng)建索引
CREATE  INDEX  index_name  ON  table_name (column(length))
ALTER TABLE table_name ADD INDEX index_name (column(length))
CREATE TABLE table_name (id int not null auto_increment,title varchar(30) ,PRIMARY KEY(id) , INDEX  index_name (title(5)))
查看索引
SHOW  INDEX  FROM  [table_name]
SHOW  KEYS  FROM  [table_name]   # 只在MySQL中可以使用keys關(guān)鍵字。
刪除索引
DROP INDEX  index_name  ON talbe_name
ALTER TABLE  table_name  DROP INDEX  index_name
ALTER TABLE  table_name  DROP PRIMARY KEY

唯一索引

與普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值(注意和主鍵不同)。如果是組合索引,則列值的組合必須唯一,創(chuàng)建方法和普通索引類似

創(chuàng)建索引
CREATE UNIQUE INDEX index_name  ON table_name (column(length))
ALTER TABLE table_name ADD UNIQUE index_name  (column(length))
CREATE TABLE table_name (id int not null auto_increment,title varchar(30) ,PRIMARY KEY(id) ,  UNIQUE  index_name (title(length)))

全文索引(FULLTEXT)

MySQL從3.23.23版開始支持全文索引和全文檢索,F(xiàn)ULLTEXT索引僅可用于 MyISAM 表;他們可以從CHAR、VARCHAR或TEXT列中作為CREATE TABLE語句的一部分被創(chuàng)建,或是隨后使用ALTER TABLE 或CREATE INDEX被添加。

對于較大的數(shù)據(jù)集,將你的資料輸入一個沒有FULLTEXT索引的表中,然后創(chuàng)建索引,其速度比把資料輸入現(xiàn)有FULLTEXT索引的速度更為快。不過切記對于大容量的數(shù)據(jù)表,生成全文索引是一個非常消耗時間非常消耗硬盤空間的做法。

創(chuàng)建索引
CREATE FULLTEXT INDEX index_name ON table_name(column(length))
ALTER TABLE table_name ADD FULLTEXT index_name( column)
CREATE TABLE table_name (id int not null auto_increment,title varchar(30) ,PRIMARY KEY(id) ,  FULLTEXT  index_name (title))

組合索引(最左前綴)

CREATE TABLE article(id int not null, title varchar(255), time date);

平時用的SQL查詢語句一般都有比較多的限制條件,所以為了進一步榨取MySQL的效率,就要考慮建立組合索引。例如上表中針對title和time建立一個組合索引:ALTER TABLE article ADD INDEX index_title_time (title(50),time(10))。建立這樣的組合索引,其實是相當(dāng)于分別建立了下面兩組組合索引:

–title,time

–title

為什么沒有time這樣的組合索引呢?這是因為MySQL組合索引“最左前綴”的結(jié)果。簡單的理解就是只從最左面的開始組合。并不是只要包含這兩列的查詢都會用到該組合索引,如下面的幾個SQL所示:

1,使用到上面的索引

SELECT * FROM article WHERE title='測試' AND time=1234567890;

SELECT * FROM article WHERE title='測試';

2,不使用上面的索引

SELECT * FROM article WHERE time=1234567890;

創(chuàng)建索引

CREATE INDEX index_name ON table_name (column_list)

4. 性能分析

4.1 MySQL Query Optimizer

  • Mysql中有專門負(fù)責(zé)優(yōu)化的SELECT語句的優(yōu)化器模塊,主要功能:通過計算分析系統(tǒng)中收集到的統(tǒng)計信息,為客戶端請求的Query提供他認(rèn)為最優(yōu)的執(zhí)行計劃(他認(rèn)為最優(yōu)的數(shù)據(jù)檢索方式,但不見得是DBA認(rèn)為最優(yōu)的,這部分最耗費時間)
  • 當(dāng)客戶端向MYSQL請求一條Query,命令解析器模塊完成請求分類,區(qū)別出是SELECT并轉(zhuǎn)發(fā)給Mysql Query Optimizer,MySQL Query Optimizer 首先會對整條Query進行優(yōu)化,處理掉一些常量表達式的預(yù)算,直接換算成常量值。并對Query中的查詢條件進行簡化和轉(zhuǎn)換,如去掉一些無用或顯而易見的條件、結(jié)構(gòu)調(diào)等。然后分析Query中的Hint信息(如果有),看顯示Hint信息是否可以完全確定該Query的執(zhí)行計劃。如果沒有Hint或Hint信息還不足以完全確定執(zhí)行計劃,則會讀取所涉及對象的統(tǒng)計信息,根據(jù)Query進行寫相應(yīng)的計算分析,然后再得出最后的執(zhí)行計劃。

4.2 MySQL常見瓶頸

CPU:CPU在飽和的時候一般發(fā)生在數(shù)據(jù)裝入在內(nèi)存或從磁盤上讀取數(shù)據(jù)時候

IO:磁盤I/O瓶頸發(fā)生在裝入數(shù)據(jù)遠(yuǎn)大于內(nèi)存容量時

服務(wù)器硬件的性能瓶頸:top,free,iostat和vmstat來查看系統(tǒng)的性能狀態(tài)

4.3 Explain

是什么(查看執(zhí)行計劃)

使用EXPLAIN關(guān)鍵字可以模擬優(yōu)化器執(zhí)行SQL語句,從而知道MySQL是 如何處理你的SQL語句的。分析你的查詢語句或是結(jié)構(gòu)的性能瓶頸

官網(wǎng)介紹

能干嘛

表的讀取順序

數(shù)據(jù)讀取操作的操作類型

哪些索引可以使用

哪些索引被實際使用

表之間的引用

每張表有多少行被優(yōu)化器查詢

怎么玩

Explain+SQL語句

執(zhí)行計劃包含的信息

+----+-------------+-------+------+---------------+------+---------+------+------+-------
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------

各個字段解釋

id

select查詢的序列號,包含一組數(shù)字,表示查詢中執(zhí)行select子句或操作表的順序

三種情況

id相同,執(zhí)行順序由上至下

clip_image078.jpg

id不同,如果是子查詢,id的序號會遞增,id值越大優(yōu)先級越高,越先被執(zhí)行

clip_image080.jpg

id相同不同,同時存在

clip_image082.jpg

select_type:查詢的類型,主要用于區(qū)別 普通查詢、聯(lián)合查詢、子查詢等的復(fù)雜查詢

類型名稱 描述
SIMPLE 簡單的select查詢,查詢中不包含子查詢或者UNION
PRIMARY 查詢中若包含任何復(fù)雜的子部分,最外層查詢則被標(biāo)記為
SUBQUERY 在SELECT或者WHERE列表中包含了子查詢
DERIVED 在FROM列表中包含的子查詢被標(biāo)記為DERIVED(衍生) MySQL會遞歸執(zhí)行這些子查詢,把結(jié)果放在臨時表里。
UNION 若第二個SELECT出現(xiàn)在UNION之后,則被標(biāo)記為UNION; 若UNION包含在FROM子句的子查詢中,外層SELECT將被標(biāo)記為:DERIVED
UNION RESULT 從UNION表獲取結(jié)果的SELECT

table:顯示這一行的數(shù)據(jù)是關(guān)于哪張表的

type

ALL index range ref eq_ref const,system NULL

訪問類型排列

type顯示的是訪問類型,是較為重要的一個指標(biāo),結(jié)果值從最好到最壞依次是:
system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL

顯示查詢使用了何種類型 從最好到最差依次是:
system>const>eq_ref>ref>range>index>ALL
一般來說,得保證查詢至少達到range級別,最好能達到ref.

system

表只有一行記錄(等于系統(tǒng)表),這是const類型的特例,平時不會出現(xiàn),這個也可以忽略不計

const

clip_image089.jpg

表示通過索引一次就找到了,const用于比較primary key或者unique索引。因為只匹配一行數(shù)據(jù),所以很快。如將主鍵至于where列表中,MySQL就能將該查詢轉(zhuǎn)換為一個常量

eq_ref

clip_image091.jpg

唯一性索引,對于每個索引鍵,表中只有一條記錄與之匹配,常見于主鍵或唯一索引掃描

ref

clip_image093.jpg

非唯一索引掃描,返回匹配某個單獨值的所有行。 本質(zhì)上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而, 它可能會找到多個符合條件的行,所以他應(yīng)該屬于查找和掃描的混合體

range

clip_image095.jpg

只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引 一般就是在你的where語句中出現(xiàn)了between、<、>、in等的查詢 這種范圍掃描索引掃描比全表掃描要好,因為他只需要開始索引的某一點,而結(jié)束語另一點,不用掃描全部索引

index

clip_image097.jpg

Full Index Scan,index與ALL區(qū)別為index類型只遍歷索引樹。這通常比ALL快,因為索引文件通常比數(shù)據(jù)文件小。 (也就是說雖然all和index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)

all

clip_image099.jpg

FullTable Scan,將遍歷全表以找到匹配的行

備注:

一般來說,得保證查詢只是達到range級別,最好達到ref

possible_keys

顯示可能應(yīng)用在這張表中的索引,一個或多個。 查詢涉及的字段上若存在索引,則該索引將被列出,但不一定被查詢實際使用

key

實際使用的索引。如果為null則沒有使用索引

查詢中若使用了覆蓋索引,則索引和查詢的select字段重疊

參閱: 3.USING index

key_len

表示索引中使用的字節(jié)數(shù),可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好

key_len顯示的值為索引最大可能長度,并非實際使用長度,即key_len是根據(jù)表定義計算而得,不是通過表內(nèi)檢索出的

clip_image101.jpg

ref

顯示索引那一列被使用了,如果可能的話,是一個常數(shù)。那些列或常量被用于查找索引列上的值

clip_image103.jpg

rows

clip_image105.jpg

根據(jù)表統(tǒng)計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數(shù)

越少越好

Extra

包含不適合在其他列中顯示但十分重要的額外信息

1.Using filesort

clip_image107.jpg

說明mysql會對數(shù)據(jù)使用一個外部的索引排序,而不是按照表內(nèi)的索引順序進行讀取。 MySQL中無法利用索引完成排序操作成為“文件排序”

2.Using temporary

clip_image109.jpg

使用了臨時表保存中間結(jié)果,MySQL在對查詢結(jié)果排序時使用臨時表。常見于排序order by 和分組查詢 group by

3.USING index

clip_image111.jpg

表示相應(yīng)的select操作中使用了覆蓋索引(Coveing Index),避免訪問了表的數(shù)據(jù)行,效率不錯! 如果同時出現(xiàn)using where,表明索引被用來執(zhí)行索引鍵值的查找; 如果沒有同時出現(xiàn)using where,表面索引用來讀取數(shù)據(jù)而非執(zhí)行查找動作。

覆蓋索引(Covering Index)

  • 覆蓋索引(Convering Index),一說為索引覆蓋。
  • 理解方式一:就是select的數(shù)據(jù)列只用從索引中就能夠取得,不必讀取數(shù)據(jù)行,Mysql可以利用索引返回select列表中的字段,而不必根據(jù)索引再次讀取數(shù)據(jù)文件,換句話說查詢表要被所建的索引覆蓋。
  • 理解方式二:索引是高效找到行的一個方法,但是一般數(shù)據(jù)庫也能使用索引找到一個列的數(shù)據(jù),因此它不必讀取整個行。畢竟索引葉子節(jié)點存儲了它的索引的數(shù)據(jù);當(dāng)能通過讀取索引就可以得到想要的數(shù)據(jù),那就不需要讀取行了。一個索引包含了(或覆蓋了)滿足查詢結(jié)果的數(shù)據(jù)就叫覆蓋索引。
  • 注意:如果要使用覆蓋索引,一定要注意select列表中只取出需要的列,不能select *,因為如果將所有字段一起做索引會導(dǎo)致索引文件過大,查詢性能下降。

4.Using where

表面使用了where過濾

5.using join buffer

使用了連接緩存

6.impossible where

clip_image115.jpg

where子句的值總是false,不能用來獲取任何元組

7.select tables optimized away

在沒有GROUPBY子句的情況下,基于索引優(yōu)化MIN/MAX操作或者 對于MyISAM存儲引擎優(yōu)化COUNT(*)操作,不必等到執(zhí)行階段再進行計算, 查詢執(zhí)行計劃生成的階段即完成優(yōu)化。

8.distinct

優(yōu)化distinct,在找到第一匹配的元組后即停止找同樣值的工作

熱身Case

clip_image117.jpg
clip_image119.jpg

5. 索引優(yōu)化

索引不會包含有NULL值的列

只要列中包含有NULL值都將不會被包含在索引中,組合索引中只要有一列含有NULL值,那么這一列對于此組合索引就是無效的。所以我們在數(shù)據(jù)庫設(shè)計時不要讓字段的默認(rèn)值為NULL。create table table_name(c1 varchar(32) default ‘0’)

使用短索引

對串列進行索引,如果可能應(yīng)該指定一個前綴長度。例如,如果有一個CHAR(255)的列,如果在前10個或20個字符內(nèi),多數(shù)值是惟一的,那么就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節(jié)省磁盤空間和I/O操作。

CREATE INDEX index_name ON table_name (column(length))

索引列排序

MySQL查詢只使用一個索引,因此如果where子句中已經(jīng)使用了索引的話,那么order by中的列是不會使用索引的。因此數(shù)據(jù)庫默認(rèn)排序可以符合要求的情況下不要使用排序操作;盡量不要包含多個列的排序,如果需要最好給這些列創(chuàng)建復(fù)合索引。

like語句操作

一般情況下不鼓勵使用like操作,如果非使用不可,如何使用也是一個問題。like “%aaa%” 不會使用索引,而like “aaa%”可以使用索引。

不要在列上進行運算

例如:select * from users where YEAR(adddate)<2007,將在每個行上進行運算,這將導(dǎo)致索引失效而進行全表掃描,因此我們可以改成:select * from users where adddate<’2007-01-01′

最后總結(jié)一下,MySQL只對以下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些時候的like(不以通配符%或_開頭的情形)。而理論上每張表里面最多可創(chuàng)建16個索引,不過除非是數(shù)據(jù)量真的很多,否則過多的使用索引也不是那么好玩的。

建議:一個表的索引數(shù)最好不要超過6個,若太多則應(yīng)考慮一些不常使用到的列上建的索引是否有必要。

5.1 索引分析

clip_image121.jpg
clip_image123.jpg

單表

建表SQL

案例

兩表

建表SQL

案例

三表

建表SQL

案例

clip_image125.jpg
clip_image127.jpg

5.2 索引失效(應(yīng)該避免)

建表SQL

案例(索引失效)

1.全值匹配我最愛

clip_image129.jpg

2.最佳左前綴法則

如果索引了多例,要遵守最左前綴法則。指的是查詢從索引的最左前列開始并且不跳過索引中的列。

clip_image131.jpg

3.不在索引列上做任何操作(計算、函數(shù)、(自動or手動)類型轉(zhuǎn)換),會導(dǎo)致索引失效而轉(zhuǎn)向全表掃描

clip_image133.jpg

4.存儲引擎不能使用索引中范圍條件右邊的列

clip_image135.jpg

5.盡量使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致)),減少select*

clip_image137.jpg
clip_image139.jpg

6.mysql在使用不等于(!=或者<>)的時候無法使用索引會導(dǎo)致全表掃描

clip_image141.jpg

7.is null,is not null 也無法使用索引

clip_image143.jpg

8.like以通配符開頭('$abc...')mysql索引失效會變成全表掃描操作

clip_image145.jpg

問題:解決like'%字符串%'索引不被使用的方法??

1、可以使用主鍵索引

2、使用覆蓋索引,查詢字段必須是建立覆蓋索引字段

3、當(dāng)覆蓋索引指向的字段是varchar(380)及380以上的字段時,覆蓋索引會失效!

9.字符串不加單引號索引失效

clip_image147.jpg

10.少用or,用它連接時會索引失效

clip_image149.jpg

11.小總結(jié)

假設(shè)index(a,b,c)

where語句 索引是否被使用
where a=3 Y,使用到a
where a=3 and b=5 Y,使用到a,b
where a=3 and b=5 and c=4 Y,使用到a,b,c
where b=3 或者 where b=3 and c=4 或者 where c=4 N
where a=3 and c=5 使用到a,但是c不可以
where a=3 and b>4 and c=5 使用到a和b
where a=3 and b like 'kk%' and c=4 Y,使用到a,b,c
where a=3 and b like '%kk' and c=4 Y,只用到a
where a=3 and b like '%kk%' and c=4 Y,只用到a
where a=3 and b like 'k%kk%' and c=4 Y,使用到a,b,c

like KK%相當(dāng)于=常量 %KK和%KK% 相當(dāng)于范圍

【優(yōu)化總結(jié)口訣】
會值匹配我最愛,最左前綴要遵守;
帶頭大哥不能死,中間兄弟不能斷;
索引列上少計算,范圍之后會失效;
LIKE百分寫最右,覆蓋索引不寫星;
不等空值還有or,索引失敗要少用;
VAR引號不可丟,SQL高級也不難!

定值、范圍還是排序,一般order by是給個范圍

group by 基本上都需要進行排序,會有臨時表產(chǎn)生

5.3 一般性建議

對于單鍵索引,盡量選擇針對當(dāng)前query過濾性更好的索引

在選擇組合索引的時候,當(dāng)前Query中過濾性最好的字段在索引字段順序中,位置越靠前越好。

在選擇組合索引的時候,盡量選擇可以能包含當(dāng)前query中的where子句中更多字段的索引

盡可能通過分析統(tǒng)計信息和調(diào)整query的寫法來達到選擇合適索引的目的

避免全表掃描

對查詢進行優(yōu)化,應(yīng)盡量避免全表掃描,首先應(yīng)考慮在 where 及 order by 涉及的列上建立索引。

避免判斷null值

應(yīng)盡量避免在 where 子句中對字段進行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進行全表掃描,如:

select id from t where num is null

可以在num上設(shè)置默認(rèn)值0,確保表中num列沒有null值,然后這樣查詢:

select id from t where num=0

避免不等值判斷

應(yīng)盡量避免在 where 子句中使用!=或<>操作符,否則引擎將放棄使用索引而進行全表掃描。

避免使用or邏輯

應(yīng)盡量避免在 where 子句中使用 or 來連接條件,否則將導(dǎo)致引擎放棄使用索引而進行全表掃描,如:

select id from t where num=10 or num=20

可以這樣查詢:

select id from t where num=10

union all

select id from t where num=20

慎用in和not in邏輯

in 和 not in 也要慎用,否則會導(dǎo)致全表掃描,如:

select id from t1 where num in(select id from t2 where id > 10)

此時外層查詢會全表掃描,不使用索引。可以修改為:

select id from t1,(select id from t1 where id > 10)t2 where t1.id = t2.id

此時索引被使用,可以明顯提升查詢效率。

注意模糊查詢

下面的查詢也將導(dǎo)致全表掃描:

select id from t where name like '%abc%'

模糊查詢?nèi)绻潜匾獥l件時,可以使用select id from t where name like 'abc%'來實現(xiàn)模糊查詢,此時索引將被使用。如果頭匹配是必要邏輯,建議使用全文搜索引擎(Elastic search、Lucene、Solr等)。

避免查詢條件中字段計算

應(yīng)盡量避免在 where 子句中對字段進行表達式操作,這將導(dǎo)致引擎放棄使用索引而進行全表掃描。如:

select id from t where num/2=100

應(yīng)改為:

select id from t where num=100*2

避免查詢條件中對字段進行函數(shù)操作

應(yīng)盡量避免在where子句中對字段進行函數(shù)操作,這將導(dǎo)致引擎放棄使用索引而進行全表掃描。如:

select id from t where substring(name,1,3)='abc'--name以abc開頭的id

應(yīng)改為:

select id from t where name like 'abc%'

WHERE子句“=”左邊注意點

不要在 where 子句中的“=”左邊進行函數(shù)、算術(shù)運算或其他表達式運算,否則系統(tǒng)將可能無法正確使用索引。

組合索引使用

在使用索引字段作為條件時,如果該索引是復(fù)合索引,那么必須使用到該索引中的第一個字段作為條件時才能保證系統(tǒng)使用該索引,否則該索引將不會被使用,并且應(yīng)盡可能的讓字段順序與索引順序相一致。

不要定義無異議的查詢

不要寫一些沒有意義的查詢,如需要生成一個空表結(jié)構(gòu):

select col1,col2 into #t from t where 1=0

這類代碼不會返回任何結(jié)果集,但是會消耗系統(tǒng)資源的,應(yīng)改成這樣:

create table #t(...)

exists

很多時候用 exists 代替 in 是一個好的選擇:

select num from a where num in(select num from b)

用下面的語句替換:

select num from a where exists(select 1 from b where num=a.num)

索引也可能失效

并不是所有索引對查詢都有效,SQL是根據(jù)表中數(shù)據(jù)來進行查詢優(yōu)化的,當(dāng)索引列有大量數(shù)據(jù)重復(fù)時,SQL查詢可能不會去利用索引,如一表中有字段sex,male、female幾乎各一半,那么即使在sex上建了索引也對查詢效率起不了作用。

表格字段類型選擇

盡量使用數(shù)字型字段,若只含數(shù)值信息的字段盡量不要設(shè)計為字符型,這會降低查詢和連接的性能,并會增加存儲開銷。這是因為引擎在處理查詢和連接時會逐個比較字符串中每一個字符,而對于數(shù)字型而言只需要比較一次就夠了。

盡可能的使用 varchar 代替 char ,因為首先可變長度字段存儲空間小,可以節(jié)省存儲空間,其次對于查詢來說,在一個相對較小的字段內(nèi)搜索效率顯然要高些。

查詢語法中的字段

任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。

索引無關(guān)優(yōu)化

不使用*、盡量不使用union,union all等關(guān)鍵字、盡量不使用or關(guān)鍵字、盡量使用等值判斷。

表連接建議不超過5個。如果超過5個,則考慮表格的設(shè)計。(互聯(lián)網(wǎng)應(yīng)用中)

表連接方式使用外聯(lián)優(yōu)于內(nèi)聯(lián)。

外連接有基礎(chǔ)數(shù)據(jù)存在。如:A left join B,基礎(chǔ)數(shù)據(jù)是A。

A inner join B,沒有基礎(chǔ)數(shù)據(jù)的,先使用笛卡爾積完成全連接,在根據(jù)連接條件得到內(nèi)連接結(jié)果集。

大數(shù)據(jù)量級的表格做分頁查詢時,如果頁碼數(shù)量過大,則使用子查詢配合完成分頁邏輯。

Select * from table limit 1000000, 10

Select * from table where id in (select pk from table limit 100000, 10)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容