深入理解MySQL索引底層數據結構與算法 (各種索引結構優缺點)
存儲引擎作用於什么對象
存儲引擎是作用在表上的,而不是數據庫。
MyISAM和InnoDB對索引和數據的存儲在磁盤上是如何體現的
先來看下面創建的兩張表信息,role表使用的存儲引擎是MyISAM,而user使用的是InnoDB:
再來看下兩張表在磁盤中的索引文件和數據文件:
1. role表有三個文件,對應如下:
role.frm:表結構文件
role.MYD:數據文件(MyISAM Data)
role.MYI:索引文件(MyISAM Index)
2. user表有兩個文件,對應如下:
user.frm:表結構文件
user.ibd:索引和數據文件(InnoDB Data)
也由於兩種引擎對索引和數據的存儲方式的不同,我們也稱MyISAM的索引為非聚集索引,InnoDB的索引為聚集索引。
InnoDB 索引文件和數據文件是一個;MyISAM索引文件和數據文件是分開的。
MyISAM主鍵索引與輔助索引的結構
我們先列舉一部分數據出來分析,如下:
上面已經說明了MyISAM引擎的索引文件和數據文件是分離的,我們接着看一下下面兩種索引結構異同。
主鍵索引
上一篇文章已經介紹過數據庫索引是采用B+Tree存儲,並且只在葉子節點存儲數據,在MyISAM引擎中葉子結點存儲的數據其實是索引和數據的文件指針兩類。
如下圖中我們以Col1列作為主鍵建立索引,對應的葉子結點儲存形式可以看一下表格。
過索引查找數據的流程:先從索引文件中查找到索引節點,從中拿到數據的文件指針,再到數據文件中通過文件指針定位了具體的數據。
輔助(非主鍵)索引
以Col2列建立索引,得到的輔助索引結構跟上面的主鍵索引的結構是相同的。
在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復。
InnoDB主鍵索引與輔助索引的結構
雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同。
第一個重大區別是InnoDB的數據文件本身就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地 址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據 表的主鍵,因此InnoDB表數據文件本身就是主索引。
主鍵索引
我們已經知道InnoDB索引是聚集索引,它的索引和數據是存入同一個.idb文件中的,因此它的索引結構是在同一個樹節點中同時存放索引和數據,如下圖中最底層的葉子節點有三行數據,對應於數據表中的Col1、Col2、Col3數據項。
上圖是InnoDB主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因為InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形。
輔助(非主鍵)索引
第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。下圖為定義在Col3上的一個輔助索引:
在最底層的葉子結點有兩行數據,第一行的字符串是輔助索引,按照ASCII碼進行排序,第二行的整數是主鍵的值。
這里以英文字符的ASCII碼作為比較准則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
InnoDB索引結構需要注意的點
1. 數據文件本身就是索引文件
2. 表數據文件本身就是按B+Tree組織的一個索引結構文件
3. 聚集索引中葉節點包含了完整的數據記錄
4. InnoDB表必須要有主鍵,並且推薦使用整型自增主鍵
正如我們上面介紹InnoDB存儲結構,索引與數據是共同存儲的,不管是主鍵索引還是輔助索引,在查找時都是通過先查找到索引節點才能拿到相對應的數據,如果我們在設計表結構時沒有顯式指定索引列的話,MySQL會從表中選擇數據不重復的列建立索引,如果沒有符合的列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,並且這個字段長度為6個字節,類型為整型。
那為什么推薦使用整型自增主鍵而不是選擇UUID?
UUID是字符串,比整型消耗更多的存儲空間;
在B+樹中進行查找時需要跟經過的節點值比較大小,整型數據的比較運算比字符串更快速;
自增的整型索引在磁盤中會連續存儲,在讀取一頁數據時也是連續;UUID是隨機產生的,讀取的上下兩行數據存儲是分散的,不適合執行where id > 5 && id < 20的條件查詢語句。
在插入或刪除數據時,整型自增主鍵會在葉子結點的末尾建立新的葉子節點,不會破壞左側子樹的結構;UUID主鍵很容易出現這樣的情況,B+樹為了維持自身的特性,有可能會進行結構的重構,消耗更多的時間。
為什么非主鍵索引結構葉子節點存儲的是主鍵值?
保證數據一致性和節省存儲空間,可以這么理解:商城系統訂單表會存儲一個用戶ID作為關聯外鍵,而不推薦存儲完整的用戶信息,因為當我們用戶表中的信息(真是名稱、手機號、收貨地址···)修改后,不需要再次維護訂單表的用戶數據,同時也節省了存儲空間。
總結
了解不同存儲引擎的索引實現方式對於正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現后,就很容易明白為什么不建議使用過長 的字段作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作為主鍵在InnoDB中不是個好主意,因為 InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用 自增字段作為主鍵則是一個很好的選擇。