1. 程式人生 > >【轉】MySQL—1、資料庫索引的實現原理及查詢優化

【轉】MySQL—1、資料庫索引的實現原理及查詢優化

MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取資料的資料結構。

使用索引的目的在於提高查詢效率,這篇文章梳理一下索引的實現原理和應用。

不同的儲存引擎索引實現的資料結構不同

MySQL支援諸多儲存引擎,而各種儲存引擎對索引的支援也各不相同,因此MySQL資料庫支援多種索引型別,如B-Tree索引,雜湊索引,全文索引等,主要儲存引擎有MyISAM、InnoDB、MEMORY和MERGE等,在建立表到時候通過engine=type=來指定所要使用到引擎,show table status from DBname來檢視指定表的引擎。

  • MyISAM
    不支援事務,也不支援外來鍵,訪問速度快,對事務完整性沒有要求或者以SELECT、INSERT為主的應用可以使用這個引擎來建立表.
  • memory
    資料表使用雜湊索引,
  • InnoDB儲存引擎
    則提供了具有提交、回滾和崩潰恢復能力的事務安全。但是對比MyISAM的儲存引擎,InnoDB寫的處理效率差一些並且會佔用更多的磁碟空間以保留資料和索引。

這裡只關注應用最廣泛的InnoDB的B-Tree索引。

索引實現的B-Tree結構

  資料庫索引是通過B-Tree實現的,但是為什麼使用B-Tree而不是使用紅黑樹或者其他的查詢資料結構呢,通過對樹結構的回顧分析一下。

  • 紅黑樹(Red-Black Tree)
      是二叉搜尋樹(Binary Search Tree)的一種改進。我們知道二叉搜尋樹在最壞的情況下可能會變成一個連結串列(當所有節點按從小到大的順序依次插入後)。而紅黑樹在每一次插入或刪除節點之後都會花O(log N)的時間來對樹的結構作修改,以保持樹的平衡。也就是說,紅黑樹的查詢方法與二叉搜尋樹完全一樣;插入和刪除節點的的方法前半部分節與二叉搜尋樹完全一樣,而後半部分添加了一些修改樹的結構的操作。
      紅黑樹的每個節點上的屬性除了有一個key、3個指標:parent、lchild、rchild以外,還多了一個屬性:color。它只能是兩種顏色:紅或黑。而紅黑樹除了具有二叉搜尋樹的所有性質之外,還具有以下4點性質:
  1. 根節點是黑色的。
  2. 空節點是黑色的(紅黑樹中,根節點的parent以及所有葉節點lchild、rchild都不指向NULL,而是指向一個定義好的空節點)。
  3. 紅色節點的父、左子、右子節點都是黑色。
  4. 在任何一棵子樹中,每一條從根節點向下走到空節點的路徑上包含的黑色節點數量都相同。
  • B-Tree又叫平衡多路查詢樹
      B-樹是為了磁碟或其它儲存裝置而設計的一種多叉平衡查詢樹,相對於二叉,B樹每個內結點有多個分支,即多叉。與紅黑樹很相似,但在降低磁碟I/0操作方面要更好一些。
      為什麼使用B-Tree而不是使用紅黑樹或者其他的查詢資料結構,紅黑樹多用在內部排序,即全部在記憶體中的,C++的STL中map和set的內部實現就是紅黑樹,Java集合框架裡HashSet和HashTree也是使用紅黑樹實現,B樹多用在記憶體裡放不下,大部分資料儲存在外存上時
    。因為B樹層數少,因此可以確保每次操作,讀取磁碟的次數儘可能的少。

索引建立的幾個原則

(1)適合索引的列是出現在WHERE 子句中的列
最適合索引的列是出現在WHERE 子句中的列,或連線子句中指定的列,而不是出現在SELECT 關鍵字後的選擇列表中的列。
(2)使用惟一索引
考慮某列中值的分佈。對於惟一值的列,索引的效果最好,而具有多個重複值的列,其索引效果最差。例如,存放年齡的列具有不同值,很容易區分 各行。而用來記錄性別的列,只含有“ M”和“F”,則對此列進行索引沒有多大用處。
(3)使用短索引
如果對字串列進行索引,應該指定一個字首長度,只要有可能就應該這樣做。
例如,如果有一個CHAR(200) 列,如果在前10 個或20 個字元內,多數值是惟一的,那麼就不要對整個列進行索引。對前10 個或20 個字元進行索引能夠節省大量索引空間,也可能會使查詢更快。較小的索引涉及的磁碟I/O 較少,較短的值比較起來更快。更為重要的是,對於較短的鍵值,索引快取記憶體中的塊能容納更多的鍵值,因此,MySQL也可以在記憶體中容納更多的值。這增加 了找到行而不用讀取索引中較多塊的可能性。(當然,應該利用一些常識。如僅用列值的第一個字元進行索引是不可能有多大好處的,因為這個索引中不會有許多不 同的值。)
(4)利用最左字首
在建立一個n 列的索引時,實際是建立了MySQL可利用的n 個索引。多列索引可起幾個索引的作用,因為可利用索引中最左邊的列集來匹配行。這樣的列集稱為最左字首。(這與索引一個列的字首不同,索引一個列的字首是利用該的前n 個字元作為索引值。)
(5)不要過度索引
不要以為索引“越多越好”,什麼東西都用索引是錯的。**每個額外的索引都要佔用額外的磁碟空間,並降低寫操作的效能,這一點我們前面已經介紹 過。在修改表的內容時,索引必須進行更新,有時可能需要重構,因此,索引越多,所花的時間越長。如果有一個索引很少利用或從不使用,那麼會不必要地減緩表 的修改速度。此外,MySQL在生成一個執行計劃時,要考慮各個索引,這也要費時間。建立多餘的索引給查詢優化帶來了更多的工作。索引太多,也可能會使 MySQL選擇不到所要使用的最好索引。只保持所需的索引有利於查詢優化。如果想給已索引的表增加索引,應該考慮所要增加的索引是否是現有多列索引的最左 索引。**如果是,則就不要費力去增加這個索引了,因為已經有了。
(6)考慮在列上進行的比較型別
索引可用於“ <”、“ < = ”、“ = ”、“ > =”、“ > ”和BETWEEN 運算。在模式具有一個直接量字首時,索引也用於LIKE 運算。如果只將某個列用於其他型別的運算時(如STRCMP( )),對其進行索引沒有價值
索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有必要

建立索引的時機

(1)什麼時候建立索引
較頻繁地作為查詢條件的欄位,也就是說最適合索引的列是出現在WHERE 子句中的列,或連線子句中指定的列,而不是出現在SELECT 關鍵字後的選擇列表中的列。
(2)什麼時候不建立索引

  1. 表記錄太少:
    如果一個表只有5條記錄,採用索引去訪問記錄的話,那首先需訪問索引表,再通過索引表訪問資料表,一般索引表與資料表不在同一個資料塊,這種情況下ORACLE至少要往返讀取資料塊兩次。而不用索引的情況下ORACLE會將所有的資料一次讀出,處理速度顯然會比用索引快。
  2. 唯一性太差的欄位:
    如狀態欄位、型別欄位。那些只儲存固定幾個值的欄位,例如使用者登入狀態、訊息的status等。如狀態欄位、型別欄位。那些只儲存固定幾個值的欄位,例如使用者登入狀態、訊息的status等。
    這個涉及到了索引掃描的特性。例如:通過索引查詢鍵值為A和B的某些資料,通過A找到某條相符合的資料,這條資料在X頁上面,然後繼續掃描,又發現符合A的資料出現在了Y頁上面,那麼儲存引擎就會丟棄X頁面的資料,然後儲存Y頁面上的資料,一直到查詢完所有對應A的資料,然後查詢B欄位,發現X頁面上面又有對應B欄位的資料,那麼他就會再次掃描X頁面,等於X頁面就會被掃描2次甚至多次。以此類推,所以同一個資料頁可能會被多次重複的讀取,丟棄,在讀取,這無疑給儲存引擎極大地增加了IO的負擔。
  3. 更新太頻繁地欄位不適合建立索引:
    當你為這個欄位建立索引時候,當你再次更新這個欄位資料時,資料庫會自動更新他的索引,所以當這個欄位更新太頻繁地時候那麼就是不斷的更新索引。
    如果一個欄位同一個時間段內被更新多次,那麼不能為他建立索引。

索引失效的幾種情況

(1)儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描
如:
select id from t where num is null
可以在num上設定預設值0,確保表中num列沒有null值,然後這樣查詢:
select id from t where num=0
(2)儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。
(3)儘量避免在 where 子句中使用 or 來連線條件,否則將導致引擎放棄使用索引而進行全表掃描,
selectid fromt where num=10 or num=20
可以這樣查詢:

select id fromt where num=10 
unionall
select id fromt where num=20

(4)in 和 not in 也要慎用,否則會導致全表掃描,如:
selectid fromt wherenum in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
下面的查詢也將導致全表掃描:
select id from t where name like '%abc%'
若要提高效率,可以考慮全文檢索。

(5)儘量避免在where子句中對欄位進行函式操作,這將導致引擎放棄使用索引而進行全表掃描。如:

select id from t where substring(name,1,3) = 'abc'    --name以abc開頭的id 
select id from t where datediff(day,createdate,'2005-11-30') = 0    --‘2005-11-30’生成的id

應改為:

select id from t where name like   'abc%'
select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'

索引失效的情況還有很多,其他的還有使用<>或者單獨的<單獨的>;當變數採用的是times變數,而表的欄位採用的是date變數時等

使用explains優化慢查詢

MySQL的Explain命令用於檢視執行效果,顯示了mysql如何使用索引來處理select語句以及連線表
可以幫助選擇更好的索引和寫出更優化的查詢語句。
explain的語法如下,在select語句前加上explain就可以:
explain selectxx fromtb wherexx;

EXPLAIN列的解釋:
table:顯示這一行的資料是關於哪張表的
type:這是重要的列,顯示連線使用了何種型別。從最好到最差的連線型別為const、eq_reg、ref、range、index和ALL
possible_keys:顯示可能應用在這張表中的索引。如果為空,沒有可能的索引。可以為相關的域從WHERE語句中選擇一個合適的語句
key: 實際使用的索引。如果為NULL,則沒有使用索引。很少的情況下,MYSQL會選擇優化不足的索引。這種情況下,可以在SELECT語句中使用USE INDEX(indexname)來強制使用一個索引或者用IGNORE INDEX(indexname)來強制MYSQL忽略索引
key_len:使用的索引的長度。在不損失精確性的情況下,長度越短越好
ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數
rows:MYSQL認為必須檢查的用來返回請求資料的行數
Extra:關於MYSQL如何解析查詢的額外資訊。

好文書籤
MySQL索引及查詢優化
SQL優化避免索引失效