Mysql索引及優化

MySQL索引原理及SQL優化

 

索引(Index)

MySQL官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構。索引的創建對於MySQL的高效運行是很重要的,索引能夠大大提升MySQL的檢索速度。mysql

拿漢語字典的目錄頁(索引)打比方,咱們能夠按拼音、筆畫、偏旁部首等排序的目錄(索引)快速查找到須要的字。sql

索引分單列索引和組合索引。單列索引,即一個索引只包含單個列,一個表能夠有多個單列索引,但這不是組合索引。組合索引,即一個索引包含多個列。數據庫

建立索引時,你須要確保該索引是應用在 SQL 查詢語句的條件(通常做爲 WHERE 子句的條件)。緩存

實際上,索引也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄。markdown

索引的原理

索引用於快速查找具備特定列值的行。若是沒有索引,MySQL必須從第一行開始,而後讀取整個表以查找相關行。表越大,成本越高。若是表中有相關列的索引,MySQL能夠快速肯定要在數據文件中間尋找的位置,而無需查看全部數據。這比按順序讀取每一行要快得多。數據結構

MySQL經常使用的是B+ Tree索引,下面詳細介紹。函數

b+樹

b+樹

如上圖,是一顆b+樹,淺藍色的塊咱們稱之爲一個磁盤塊,能夠看到每一個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊1包含數據項17和35,包含指針P一、P二、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。非葉子節點不存儲真實的數據,只存儲指引搜索方向的數據項,如1七、35並不真實存在於數據表中。post

上圖中,若是要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找肯定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間由於很是短(相比磁盤的IO)能夠忽略不計,經過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,經過指針加載磁盤塊8到內存,發生第三次IO,同時內存中作二分查找找到29,結束查詢,總計三次IO。真實的狀況是,3層的b+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要三次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高。性能

經過上面的分析,咱們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每一個磁盤塊的數據項的數量是m,則有

公式

當數據量N必定的狀況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,若是數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲何每一個數據項,即索引字段要儘可能的小,好比int佔4字節,要比bigint8字節少一半。

當b+樹的數據項是複合的數據結構,常見的就是組合索引,好比咱們給某個表添加個組合索引,包括姓名、年齡和性別三列(name,age,sex),b+數是按照從左到右的順序來創建搜索樹的,好比查詢(where name=‘馬雲’ and age=18 and sex=1),b+樹會優先比較name來肯定下一步的檢索方向,若是name相同再依次比較age和sex,最後獲得檢索的數據;但若是咱們查詢(where age=18 and sex= 1),此時索引是不生效的,由於創建搜索樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。好比當查詢(where name='張三' and sex=2)的時候,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是2的數據了, 這個是很是重要的性質,即索引的最左匹配特性。

MySQL如何使用索引

MySQL使用索引進行這些操做:

  • WHERE快速 查找與子句匹配的行。

  • 若是在多個索引之間有選擇,MySQL一般使用找到最小行數的索引。

  • 若是表具備多列索引,即組合索引,則優化程序可使用索引的任何最左前綴來查找行。例如,若是你有一個三列索引上(col1, col2, col3),你有索引的搜索功能(col1)(col1, col2)以及(col1, col2, col3)。

  • 在執行鏈接時從其餘表中檢索行。若是聲明它們的類型和大小相同,MySQL能夠更有效地使用列上的索引。在這種狀況下, VARCHAR與 CHAR被認爲是相同的,若是它們被聲明爲相同的大小。例如, VARCHAR(10)和 CHAR(10)大小相同,但 VARCHAR(10)與 CHAR(15)不是。

    對於非二進制字符串列之間的比較,兩列應使用相同的字符集。例如,將utf8列與 latin1列進行比較會排除使用索引。

    不類似列的比較(例如,將字符串列與時間或數字列進行比較)可能會在沒有轉換的狀況下沒法直接比較值時阻止使用索引。對於給定的值,如1 在數值列,它可能比較等於在字符串列,例如任何數量的值 '1'' 1', '00001',或'01.e1'。這排除了對字符串列的任何索引的使用。

  • 查找特定索引列的值Min()或 Max()[`值key_col。這是由預處理器優化的,該預處理器檢查您是否正在使用 索引以前出現的全部關鍵部分。在這種狀況下,MySQL對每一個或 表達式執行單個鍵查找,並用常量替換它。

  • 對指定索引列進行排序或者分組,ORDER BY或者 GROUP BY

  • 在某些狀況下,能夠優化查詢在不查詢整行數據的狀況下檢索值。(爲查詢提供全部必要結果的索引稱爲 [覆蓋索引])若是查詢僅使用表中包含某些索引的列,則能夠從索引樹中檢索所選值以得到更快的速度:好比

    SELECT key_part3 FROM tbl_name WHERE key_part1 = 1 

對於小型表或報表查詢處理大多數或全部行的大型表的查詢,索引不過重要。當查詢須要訪問大多數行時,順序讀取比經過索引更快。順序讀取能夠最大限度地減小磁盤搜索,即便查詢不須要全部行也是如此。

如何優化

  1. 主鍵優化

    表的主鍵表示您在最重要的查詢中使用的列或列集。它具備關聯的索引,以實現快速查詢性能。查詢性能受益於NOT NULL優化,由於它不能包含任何NULL值。使用InnoDB存儲引擎,表數據在物理上進行組織,以根據主鍵或列進行超快速查找和排序。

    若是您的表很大且很重要,但沒有明顯的列或列集用做主鍵,則能夠建立一個單獨的列,其中包含自動增量值以用做主鍵。使用外鍵鏈接表時,這些惟一ID可用做指向其餘表中相應行的指針。

  2. 外鍵優化

    若是一個表有不少列,而且您查詢了許多不一樣的列組合,那麼將頻率較低的數據拆分爲每一個都有幾列的單獨表可能會頗有效,並經過外鍵將它們與主表關聯起來。這樣每一個小表均可以有一個主鍵來快速查找其數據,您可使用鏈接操做查詢所需的列集。根據數據的分佈方式,查詢可能會執行較少的I / O並佔用較少的高速緩存。(爲了最大限度地提升性能,查詢嘗試從磁盤中讀取儘量少的數據塊)。

  3. 列索引

    最多見的索引類型涉及單個列,在數據結構中存儲該列的值的副本,容許快速查找具備相應列值的行。B樹數據結構可讓索引快速查找特定值,一組值,或值的範圍,例如where條件中=, >, BETWEENIN等。

    每一個存儲引擎定義每一個表的最大索引數和最大索引長度。全部存儲引擎支持每一個表至少16個索引,總索引長度至少爲256個字節。

    • 索引前綴

      使用 字符串列的索引規範中的語法,能夠建立僅使用列的前幾個字符的索引 。以這種方式僅索引列值的前綴可使索引文件更小。索引 或 列時, 必須爲索引指定前綴長度。

      若是搜索項超過索引前綴長度,則索引用於排除不匹配的行,並檢查剩餘的行以查找可能的匹配項。

    • FULLTEXT索引

      FULLTEXT索引用於全文搜索。只有InnoDB和 MyISAM存儲引擎支持 FULLTEXT索引和僅適用於CHAR,VARCHAR和TEXT類型的列。索引始終發生在整個列上,而且不支持列前綴索引。

    • 空間索引(Spatial Index)

      您能夠在空間數據類型上建立索引。 MyISAM和InnoDB 支持空間類型的R樹索引。其餘存儲引擎使用B樹來索引空間類型(除了 ARCHIVE)。

  4. 多列索引

    MySQL能夠建立複合索引(即多列索引)。索引最多可包含16列。對於某些數據類型,您能夠索引列的前綴。

    MySQL能夠對測試索引中全部列的查詢使用多列索引,或者只測試第一列,前兩列,前三列等的查詢。若是在索引定義中以正確的順序指定列,則單個複合索引能夠加速同一表上的多種查詢。

    假設一個表具備如下規範:

    CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name,first_name) );

    在last_namefirst_name列建立了一個組合索引,它既能夠查詢last_namefirst_name`組合的值,也能夠僅查詢last_name,由於該列是索引的最左前綴。所以,下面這些查詢是能夠用到該索引的:

    //只查詢last_name
    SELECT * FROM test WHERE last_name='Jones'; //同時查 SELECT * FROM test WHERE last_name='Jones' AND first_name='John'; SELECT * FROM test WHERE last_name='Jones' AND (first_name='John' OR first_name='Jon'); SELECT * FROM test WHERE last_name='Jones' AND first_name >='M' AND first_name < 'N';

    可是,該索引 不能用於如下查詢中的查找:

    SELECT * FROM test WHERE first_name='John'; SELECT * FROM test WHERE last_name='Jones' OR first_name='John';

    假設您寫了如何SQL語句:

    SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;

    若是col1和col2存在組合索引,那麼能夠直接獲取相應的行。若是col1和col2每列都存在單列索引,那麼MySQL會優化合並索引,或者嘗試經過肯定哪一個索引會排除更多的行來查找限制性最強的索引。

    若是表具備多列索引,則優化程序可使用索引的最左前綴來查找行。例如,若是你有一個三列索引上(col1, col2, col3),你有索引的搜索功能 (col1)(col1, col2)以及 (col1, col2, col3)

    若是SQL語句不適用索引的最左前綴,則MySQL沒法使用索引執行查找。例如如下查詢語句:

    //使用索引
    SELECT * FROM tbl_name WHERE col1=val1; //使用索引 SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2; //不使用索引 SELECT * FROM tbl_name WHERE col2=val2; //不使用索引 SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;

    若是存在索引(col1, col2, col3),則只有前兩個查詢使用索引。第三和第四個查詢確實包括索引的列,但不使用索引來進行查找,由於(col2)和 (col2, col3)不是的最左邊的前綴 (col1, col2, col3)

索引雖好,不可濫用

儘管爲查詢中使用的每一個可能列建立索引頗有誘惑力,但沒必要要的索引會浪費空間並浪費時間讓MySQL肯定要使用哪些索引。索引還會增長插入,更新和刪除的成本,由於必須更新每一個索引。您必須找到適當的平衡,以使用最佳索引集實現快速查詢。

如何驗證索引使用狀況?

咱們建立了索引,可是咱們如何肯定mysql使用了索引? 答案是 使用explain語句。

下面會詳細介紹Explain,以及如何優化SQL。

SQL優化

explain查詢執行計劃

舉個例子,最基礎的主鍵查詢

EXPLAIN SELECT * FROM `subject` WHERE id = 1

執行結果以下:

再舉個關聯查詢的例子

EXPLAIN SELECT a.* FROM `subject` a LEFT JOIN subject_role_0 b ON a.id = b.subject_id WHERE a.id < 3

執行結果以下:

屬性 說明
id 查詢的序列號
select_type 查詢的類型
table 輸出結果集的表
rows 掃描的行數
type 鏈接類型,all表示採用全表掃描的方式。
possible_keys 可能使用的索引
key 實際使用的索引
key_len 索引字段的長度
ref 列與索引的比較
Extra 額外信息,好比使用了where語句,使用了join buffer等

id

id是sql執行順序的標識,按id從大到小的順序執行,在id相同時,執行順序是由上至下

select_type

select 查詢的類型,主要是用於區別普通查詢,聯合查詢,嵌套的複雜查詢

類型 說明
simple 簡單的select 查詢,查詢中不包含子查詢或者union
primary 查詢中若包含任何複雜的子查詢,最外層查詢則被標記爲primary
subquery 在select或where 列表中包含了子查詢
derived 在from列表中包含的子查詢被標記爲derived(衍生)MySQL會遞歸執行這些子查詢,把結果放在臨時表裏。
union 若第二個select出如今union以後,則被標記爲union,若union包含在from子句的子查詢中,外層select將被標記爲:derived
union result 從union表獲取結果的select

table

查詢的數據庫的表的名稱,若是沒有給表指定別名,那麼table值爲表的名稱;不然table值爲你指定的別名

type

表示MySQL在表中找到所需行的方式,這是一個很是重要的參數,常見的有:all , index , range , ref , eq_ref , const , system , null 八個級別。

經常使用的類型有: all、index、range、 ref、eq_ref、const、system、null(從左到右,性能從差到好)

類型 說明
all 全表掃描找到匹配的行,性能最差
index 全索引掃描,從索引樹找數據,比all性能好
range 只掃描指定範圍的行,使用索引來匹配行,常見使用between,in,>,<等關鍵字
ref 非惟一性索引掃描,本質上也是一種索引訪問,返回全部匹配某個單獨值的行。
eq_ref 惟一性索引掃描,對於每一個索引鍵,表中有一條記錄與之匹配。
const 表示經過索引一次就能夠找到,const用於比較primary key 或者unique索引。由於只匹配一行數據,因此很快
system 表只有一條記錄(等於系統表),這是const類型的特列,平時不會出現
null MySQL在優化過程當中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列裏選取最小值能夠經過單獨索引查找完成。

possible_keys

指出MySQL能使用哪一個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引將被列出,但不必定被查詢使用(該查詢能夠利用的索引,若是沒有任何索引顯示 null)

key

key列顯示MySQL實際決定使用的索引,必然包含在possible_keys中

key_len

顯示索引中使用的字節數,可經過key_len計算查詢中使用的索引長度。在不損失精確性的狀況下索引長度越短越好。key_len 顯示的值爲索引字段的最可能長度,並不是實際使用長度,即key_len是根據表定義計算而得,並非經過表內檢索出的。

ref

顯示索引的哪一列或常量被用於查找索引列上的值。

rows

很重要的一個參數,根據表統計信息及索引選用狀況,大體估算出找到所需的記錄所須要讀取的行數,值越大說明掃描的行數越多,性能越差

Extra

該列包含MySQL解決查詢的詳細信息,有如下幾種狀況:

說明
Using where 不用讀取表中全部信息,僅經過索引就能夠獲取所需數據
Using temporary 表示須要使用臨時表來存儲結果集,常見於排序和分組查詢,如group by ,order by
Using filesort 當查詢中包含 order by 操做,且沒法利用索引完成的排序操做稱爲「文件排序」
Using join buffer 在獲取鏈接條件時沒有使用索引,而且須要鏈接緩衝區來存儲中間結果。若是出現了這個值,那應該注意,根據查詢的具體狀況可能須要添加索引來改進能
Impossible where 強調了where語句會致使沒有符合條件的行
Select tables optimized away 這個值意味着僅經過使用索引,優化器可能僅從聚合函數結果中返回一行
No tables used Query語句中使用from dual 或不含任何from子句

優化數據庫結構

優化數據大小

以最小佔用磁盤空間來設計表,這樣能夠減小磁盤寫入和讀取來實現性能的提高。較小的表一般須要較少的主內存,同時索引也比較小,便於更快的處理。

經過使用此處列出的技術,您能夠得到更好的表性能並最大限度地減小存儲空間:

表列

  • 儘量使用最有效(最小)的數據類型。MySQL有許多專門的類型能夠節省磁盤空間和內存。例如,若是可能,請使用較小的整數類型。mediumint一般是一個更好的選擇,它比int使用的列空間減小25%。
  • 儘可能使用NOT NULL列,它經過更好地使用索引並避免測試每一個值是否爲NULL來獲取更快的速度。

索引

  • 表的主索引應儘量短。這使得每行的識別變得簡單而有效。
  • 僅建立提升查詢性能所需的索引。索引適用於檢索,但會下降插入和更新操做的速度。若是您主要經過搜索列的組合來訪問表,請在它們上建立單個複合索引,而不是爲每列建立單獨的索引。索引的第一部分應該是最經常使用的列。若是從表中選擇時老是使用多列,則索引中的第一列應該是具備最多重複的列,以得到更好的索引壓縮。
  • 若是長字符串列極可能在第一個字符數上有惟一的前綴,那麼最好只使用MySQL支持在列的最左邊部分建立索引來索引此前綴,較短的索引更快,不只由於它們須要更少的磁盤空間,並且由於它們還會在索引緩存中爲您提供更多的命中,從而減小磁盤搜索次數。

Join

  • 在某些狀況下,分紅兩個常常掃描的表多是有益的,若是它是動態格式表,則尤爲如此,而且可使用較小的靜態格式表,該表可用於在掃描表時查找相關行。
  • 在具備相同數據類型的不一樣表中聲明具備相同信息的列,能夠加速鏈接。
  • 保持列名簡單,以便您能夠在不一樣的表中使用相同的名稱並簡化鏈接查詢。例如表customer,使用列名name而不是customer_name。

正常化

  • 一般,儘可能保持全部數據不冗餘(第三範式),儘可能經過引用join子句中的ID來鏈接查詢中的表。
  • 若是速度比磁盤空間更重要,而且保留多個數據副本,那麼能夠建立彙總表到得到更快的速度。

優化數據類型

對於惟一的id,首選使用數字列,而不是字符串,這是由於數字比字符串佔用更少的字節,傳輸和比較速度更快,佔用的內存更少。

優化字符和字符串類型

對於字符和字符串列,請遵循如下準則:

  • 比較來自不一樣列的值時,請儘量聲明具備相同字符集和排序規則的列,以免在運行查詢時進行字符串轉換。
  • 對於小於8KB的列值,請使用binary VARCHAR而不是 BLOB
  • 若是表包含字符串列(如名稱和地址),但許多查詢不檢索這些列,請考慮將字符串列拆分爲單獨的表,並在必要時使用帶有外鍵的鏈接查詢。能夠減小了常見查詢的磁盤I / O和內存使用。

優化BLOB類型

  • 存儲包含文本數據的大blob時,請考慮先壓縮它。
  • 對於具備多個列的表,要減小使用BLOB列的查詢,請考慮將BLOB列拆分爲單獨的表,並在須要時使用鏈接查詢引用它。
相關文章
相關標籤/搜索