關於數據庫索引,隨便Google一個Oracle index,Mysql index總有大量的結果出來,其中不乏某某索引之n條經典建議。筆者認爲,較之借鑑,在搞清楚了本身的需求的基礎上,對備選方案的原理有個儘量深刻全面的瞭解會更有利於咱們的選擇和決策。由於某種方案或者技術呈現出某種優點(包括可能沒有被介紹到但必定存在的限制),不是定義出來的,而是由於其實現機制決定的。就像LinkedList和ArrayList分別適用於什麼應用不是Document裏面定義的,是由其自己的結構決定的。數據庫的索引也是同樣,不是廠商的白皮書這樣規定,而是其原理決定的。html
本文只是重點介紹數據結構中經典的樹(B樹)結構在數據庫索引中的經典應用,也會涉及到幾種數據庫中對此支持的細微不一樣,以期比較完整的描述實現原理。最終會發現這幾種被不一樣數據庫廠商冠以不一樣名字東西原理上其實差很少,理論上實際上是一個東西。文中只是略微空洞的介紹其實現原理,不涉及應用上具體的使用建議。node
數據庫索引在維基中的定義:A database index is a data structure that improves the speed of data retrieval operations on a database table at the cost of additional writes and the use of more storage space to maintain the extra copy of data. Indexes are used to quickly locate data without having to search every row in a database table every time a database table is accessed. Indexes can be created using one or more columns of a database table, providing the basis for both rapid random lookups and efficient access of ordered records.mysql
這個定義看上去挺長,簡單講就是爲了對一個相對較大的數據結構訪問快速方便,另外的存儲了一個小的數據結構,依照待檢索屬性進行排序而且記錄了該屬性的記錄在大的數據結構中的位置,以便快速的在大的數據結構中檢索定位。若是Index被翻譯成目錄可能更能體現出其本質的做用。和其餘不少計算機科學中的概念同樣,Index也是現實事物中的一種常見結構。由目錄最容易聯想到的是圖書館的書籍管理,若是沒有個目錄,很難想象要從圖書館的那麼多書架上找到一本書是多麼困難的事情。
固然映射的最好的是小時候厚厚的新華字典前面的目錄,通常好像有兩種,一種是拼音的,一種是筆畫仍是所謂的四角號碼的。就是對於字典中數據根據兩種不一樣屬性不一樣進行索引。字典前面和後面多出來的那麼幾十頁紙(額外的存儲)的用處就是幫助檢索者快速定位到字典中某個詞條的完整記錄。若是沒有這個Index,要查找字典的某個字就只有來挨着翻頁了(對應數據庫索引的全表掃描full table scan)。web
數據庫中比較經常使用的索引結構有B樹、位圖等幾種。其中B樹是幾乎全部數據庫的默認索引結構,也是用的最多的索引結構。sql
索引的基本做用是用於查找。數據結構的查找算法中最基本的是順序查找,即從列表上逐個匹配關鍵字,其時間複雜度是O(n),當n比較大的時候這個效率是不能承受的。因而計算機科學嘗試能不能在存儲上作些文章發明效率更高的算法,而後就有了數據結構中咱們熟悉的基於排序樹的查找。B樹(實際上是B+樹)是一種樹的結構,一般用於數據庫和操做系統的文件系統中。特色是可以保持數據穩定有序,其插入與修改擁有較穩定的對數時間複雜度。B+樹的創造者Rudolf Bayer沒有解釋B表明什麼。最多見的觀點是B表明平衡(balanced),由於全部的葉子節點在樹中都在相同的級別上,B也可能表明Bayer,或者是波音(Boeing),由於他曾經工做于波音科學研究實驗室。下圖是一件簡單的B樹的例子。數據庫
A simple B+ tree example linking the keys 1–7 to data values d1-d7. The linked list (red) allows rapid in-order traversalapi
B樹是一棵平衡樹,採用樹的結構是由於其O(logN)的查找複雜度,而平衡樹是計算機科學中改進的二叉查找樹。對一棵查找樹(search tree)進行查詢/新增/刪除等動做,所花的時間與樹的高度h成比例,並不與樹的容量n成比例。在B樹上無論查找成功與否,每次查找都是走了一條從根到葉子結點的路徑。一個度爲d的B樹,設節點爲數N,則其樹高h的上限爲logd((N+1)/2),檢索一個值,其查找節點個數的時間複雜度爲O(logdN)。這樣使得在B樹中檢索一個節點最多須要h個節點,而數據庫系統中通常將一個節點的大小設定爲一個頁,每一個節點一次IO。使B樹的根節點常駐內存,則一次檢索最多須要h-1次的I/O便可。關於數據結構中的樹,二叉樹、平衡樹的結構,遍歷方式、節點查找方式、節點的刪除、添加等都是很典型的內容,不在此作介紹。B樹檢索的僞代碼以下:數據結構
關於B樹的一個性質,在集中數據庫中採用的B樹結構的索引,除了上面平衡樹的公共特徵外,結合數據庫索引使用的須要,都有以下的結構要求。oracle
一般在B樹上有兩個頭指針,一個指向根節點,另外一個指向關鍵字最小的葉子節點。所以能夠對B樹進行兩種查找運算:一種是從最小關鍵字起順序查找,另外一種是從根節點開始,進行隨機查找。
結合數據庫實現對B樹結構的不一樣應用,主要是葉子節點存儲的內容不一樣,我把B樹其爲兩種:一種是葉節點存完整的行數據,一種是葉節點只是存一個指向實際數據行的指針。根據表中數據存儲格式不一樣,指針又分爲物理指針和邏輯指針。這樣B樹的結構被分紅了三類:
由於幾種數據庫各自對這幾種特徵的索引的術語不一樣,暫且這樣統一命名,雖然聽着都不高大上。爲了討論方便,且這樣分了。
這是最普通的一種索引結構。數據插入時存儲位置是隨機的,主要是數據庫內部存儲的空閒狀況決定。這種表數據的存儲結構稱爲堆表(heap table)(原本heap這個概念就是生成時候分配空間的)。在堆表(heap table)中記錄是無序的,插入速度會比較快。可是查找一個數據會比較麻煩,須要掃描整個堆表才能夠。以下圖表T是示意的一個簡單表,表上有三列。前面的十六進制數字僅僅是示意這一行的存儲位置。
想一想咱們須要在表中找出C2=43的行,咱們須要從第一行開始,逐行的檢查每一行上C2的取值。直到找到第三行找到了。但仍是須要掃描接下來的行,由於你不能保證在你掃描的前方還有沒有另一個或者多個C2=43的行存在。即要進行全表的掃描,查找一條記錄的時間複雜度是O(N),N爲記錄行數。對一個數據量比較大的表,這樣的方式幾乎是不能夠接受的。
因而乎就有了索引的概念,即另外開闢一個存儲結構,按照某個列進行排序,並記錄每行的在該列上取值的以及該行在表中的對應位置。這恐怕是索引本質的意思了吧。回到咱們字典的類比上,想咱們的字典能根據目錄某個筆畫找到那個詞條,靠的是在目錄中存的頁碼這樣一個指針。
由於前面提到的B樹的優勢,幾乎全部的這類索引都採用B樹結構。在葉節點上,葉節點的key是索引列在每行上的值,而對應的data域保存了該行的一個引用,也能夠理解爲指向實際存儲數據的指針。如圖中在C2上創建索引,記錄按照C2的屬性構建B樹,在每一個葉節點上和索引鍵對應的都有一個指針記錄該行數據的存儲位置。儘管右下角的表上的數據是無序的。一樣要找到C2=43的記錄行,從索引樹上只要通過三個節點便可以找到葉節點存儲的指針,並經過指針找到對應的行。?
由於幾種數據庫中最典型的索引,結構也就基本相同。Oracle 中直接根據存儲結構把這種索引稱爲B樹索引,索引葉節點存儲(key: rowid),其中rowid標識了該行的物理存儲位置。
引用來自Oracle ?Database Concepts 對B-Tree Indexes的描述:
The leaf blocks contain every indexed data value and a corresponding rowid used to locate the actual row. Each entry is sorted by (key, rowid). Within a leaf block, a key and rowid is linked to its left and right sibling entries. The leaf blocks themselves are also doubly linked.
對於Mssql來講,這種索引稱爲非彙集索引。當沒有建立彙集索引的時候,即表示表是以堆的形式(heap structure)存儲。一樣葉節點也是存儲(key: RID),其中RID指定數據存儲物理位置的行和頁。
引用msdn.microsoft中Nonclustered Index Structures的描述
If the table is a heap, which means it does not have a clustered index, the row locator is a pointer to the row. The pointer is built from the file identifier (ID), page number, and number of the row on the page. The whole pointer is known as a Row ID (RID).
在Mysql 中索引結構和表的存儲方式都是和存儲引擎相關,不一樣的存儲引擎實現不一樣。兩種比較經常使用的存儲引擎中,Myisam表上的數據老是按照堆的結果存儲的,在Myisam上的索引也都是採用和上圖相似的索引結構。詳細點說Myisam上的主鍵索引、惟一索引、輔助索引都是這種結構。不一樣的是,主鍵索引要求選擇的索引列是表的主鍵,惟一索引要求索引列取值的惟一性約束,而輔助索引沒有這些要求。
B樹構造的另一種索引,與其說是一種索引方式,倒不如說是以一種表數據的存儲方式(Oracle 中就稱之爲索引組織表(Index-Organized Tables))。這中結構的一個特色是B樹的葉節點中和索引鍵對應存儲的是實際的數據行。即(Key: Row)的結構。即在葉節點上完整的保存了數據行。如圖,在C3上構建索引,則整個表中的數據按照C3的順序來存儲。第一個葉節點上存儲了C3=5和C3=25的完整的行,同時整個表按照C3取值的順序存儲。即整個表的數據按照C3列在彙集(難怪在Mssql中這種結構被稱爲彙集索引呢)。
在Oracle 中,並不認爲該種方式的存儲是索引,而是更形象的稱爲索引組織表(Index-Organized Tables);在Mssql中,這種結構正是其所謂的彙集索引(Clustered Index);在Mysql 中,由於索引屬於存儲引擎級別的概念,在經常使用的Innodb和Myisam存儲引擎中,只有Innodb是支持這種結構的,稱之爲(clustered index)。即使三種數據庫分別支持這種索引結構,其相互之間仍是有些比較tricky的差異,這正是想對照着強調的。
在Oracle 的索引組織表(Index-Organized Tables)根據主鍵排序後的順序進行排列的,即索引的列必須是表的主鍵列,在建表的同時要指定主鍵約束,能夠是單字段主鍵,也能夠是複合主鍵約束。建立索引組織表時,必需要設定主鍵,不然報錯。
引用來自Oracle ?Database Concepts 對Index-Organized Tables的描述:
An index-organized table is a table stored in a variation of a B-tree index structure. In a heap-organized table, rows are inserted where they fit. In an index-organized table, rows are stored in an index defined on the primary key for the table. Each index entry in the B-tree also stores the non-key column values.
在Mysql的Innodb的存儲引擎中,Innodb的數據文件自己要按主鍵彙集,按主鍵順序存儲。因此Innodb要求表必須有主鍵,若是沒有顯式指定,Mysql系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則Mysql自動爲Innodb表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。
引用來自Mysql Manual關於clustered index的描述
而在Mssql中,關於該索引列的要求就沒有那麼高,並未要求改索引列必須是主鍵,也不要求該列上必須有惟一性約束。若是表上沒有建彙集索引,當在表上建立主鍵的時候,Mssql會自動在該主鍵列上建立一個彙集索引。當在沒有惟一約束的列上建立彙集索引是,Mssql會自動的在重複的鍵值上添加一個4 byte的uniqueifier使得該值惟一,這個對用戶是透明的。
來自technet.microsoft 的Create Clustered Indexes
When you create a PRIMARY KEY constraint, a unique clustered index on the column or columns is automatically created if a clustered index on the table does not already exist and you do not specify a unique nonclustered index. The primary key column cannot allow NULL values.
根據前面的描述,當表中數據按照傳統堆結構組織的時候,構造索引(非彙集)的B樹的葉節點上上存儲(key: rowid)這樣的結構,即關聯到數據行的物理指針。但當數據自己是按照B樹存儲的時候,數據庫認爲有了邏輯標識一個行的標籤,葉節點存儲的對指針會稍有不一樣。不在存儲一個物理指針,而是存儲該邏輯指針。即葉節點上存儲(key: clusterKey)這樣的結構,即關聯到對應的彙集索引鍵,彙集索引鍵扮演了一個邏輯指針。如圖,前面在C3上建立了彙集索引,C1上建立一個非彙集的索引。則在C1構造的索引樹上葉節點處存儲了每行C3取值做爲彙集索引的鍵。如第三個葉子節點,C1對應的值爲Inter,而對應的彙集索引在該行的值爲C3=151.即經過151這個cluster key來關聯到實際數據行。由於在C3上建立了彙集索引,數據行在另一個按C3列構造的B樹上存儲。
由於幾種數據庫對於彙集索引的要求有細微差異,在存在彙集索引狀況下的非彙集索引也相應的有所不一樣。在Oracle 中,該索引稱爲輔助索引(Secondary Indexes on Index-Organized Tables)。由於Oracle 的索引組織表(Index-Organized Tables)的索引鍵必須是主鍵,則該輔助索引相應管理的是一個表明了主鍵的邏輯rowid。
引用Oracle ?Database Concepts的描述
As explained in 「Rowid Data Types」, Oracle ?Database uses row identifiers called logical rowids for index-organized tables. A logical rowid is a base64-encoded representation of the table primary key. The logical rowid length depends on the primary key length.
在Mysql的Innodb中,和Oracle 幾乎徹底相同,這種索引也稱爲輔助索引(secondary indexes)。由於其彙集索引列也是要求必須是主鍵,相應輔助索引關聯的也是對應的主鍵。
引用Mysql Manual關於clustered index的描述
All indexes other than the clustered index are known as secondary indexes. In Innodb, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. Innodb uses this primary key value to search for the row in the clustered index.
在Mssql中,這種索引稱爲非彙集索引(Nonclustered Index)。在B樹的頁節點上存儲索引列和彙集索引對應彙集索引鍵(clustered index key)。上面討論彙集索引的時候說到過,Mssql的彙集索引的列不要求惟一性,也不要求是主鍵。可是爲了非彙集索引能經過彙集索引鍵惟必定位到一行數據,在重複的彙集索引鍵上會添加一個惟一標示來使得其惟一,這個操做對用戶是透明的。
如上圖在C3上有重複的值,按照Mysql和Oracle 的要求,在該列上是不能建立彙集索引的,可是在Mssql中,在該列上能夠建彙集索引。圖示在C1列上的非彙集索引和C3列有重複值的彙集索引的狀況下,C1上建立的非彙集索引的每一行數據都能經過彙集索引key惟一關聯到實際的數據行上。
來自msdn的no-clustered index
If the table has a clustered index, or the index is on an indexed view, the row locator is the clustered index key for the row. If the clustered index is not a unique index, SQL Server makes any duplicate keys unique by adding an internally generated value called a uniqueifier. This four-byte value is not visible to users. It is only added when required to make the clustered key unique for use in nonclustered indexes. SQL Server retrieves the data row by searching the clustered index using the clustered index key stored in the leaf row of the nonclustered index.
爲了更清晰的對照,整理出一個對照列表。發現大部分都是相同的,除了術語上,SQL語法上,或者某些約定限制的程度上。由於原理是同樣的。一樣由於結構相同,形成使用也是徹底相同。如:
數據庫(存儲引擎)/項目 |
Oracle |
Mssql |
Mysql(Innodb) |
Mysql(Myisam) |
|
---|---|---|---|---|---|
表數據B樹結構存儲(即建立了彙集索引) |
支持表數據B樹存儲 |
支持 |
支持 |
支持 |
不支持 |
術語 |
索引組織表(Index-Organized Tables) |
彙集索引(主鍵索引) Clustered Index |
不支持 |
||
彙集索引鍵要求 |
必須是主鍵 |
沒有主鍵要求,也沒有惟一性要求 |
必須是主鍵 |
不支持 |
|
B樹葉節點結構 |
(Key: ROW)索引key和整行數據 |
(Key: ROW)索引key和整行數據 |
(Key: ROW)索引key和整行數據 |
不支持 |
|
根據彙集索引訪問數據行 |
彙集索引上檢索彙集索引鍵,找到索引葉節點即訪問到整行數據 |
彙集索引上檢索彙集索引鍵,找到索引葉節點即訪問到整行數據 |
彙集索引上檢索彙集索引鍵,找到索引葉節點即訪問到整行數據 |
不支持 |
|
索引(非彙集)名稱 |
輔助索引 |
非彙集索引 |
輔助索引 |
不支持 |
|
索引(非彙集)B樹葉節點結構 |
(Key:ClusterKey)索引(非彙集)鍵和彙集索引鍵的對應關係。 |
(Key:ClusterKey)索引(非彙集)鍵和,彙集索引鍵的對應關係。 |
(Key:ClusterKey)索引(非彙集)鍵和,彙集索引鍵的對應關係。 |
不支持 |
|
根據索引(非彙集)訪問數據行 |
二次檢索: 1.檢索索引(非彙集),定位到索引行所在葉節點,獲得索引鍵對應的彙集索引鍵; 2.在彙集索引上檢索彙集索引鍵,即訪問到數據行。 |
二次檢索: 1.檢索索引(非彙集),定位到索引行所在葉節點,獲得索引鍵對應的彙集索引鍵; 2.在彙集索引上檢索彙集索引鍵,即訪問到數據行。 |
二次檢索: 1.檢索索引(非彙集),定位到索引行所在葉節點,獲得索引鍵對應的彙集索引鍵; 2.在彙集索引上檢索彙集索引鍵,即訪問到數據行。 |
不支持 |
|
表數據堆存儲方式heap structure (彙集索引不存在) |
索引(非彙集)名稱 |
B樹索引 |
非彙集索引 |
不支持 |
主鍵索引、惟一索引、輔助索引 |
索引(非彙集)B樹葉節點結構 |
(Key:ROWID) 索引(非彙集)鍵和行存儲物理位置 |
(Key:ROWID) 索引(非彙集)鍵和行存儲物理位置 |
不支持 |
(Key:ROWID) 索引(非彙集)鍵和行存儲物理位置 |
|
根據索引(非彙集)訪問數據行 |
1.從索引(非彙集)定位到索引行所在葉節點,即獲得數據行的物理存儲位置; 2.直接根據物理存儲位置從堆上訪問數據行。 |
1.從索引(非彙集)定位到索引行所在葉節點,即獲得數據行的物理存儲位置; 2.直接根據物理存儲位置從堆上訪問數據行。 |
不支持 |
1.從索引(非彙集)定位到索引行所在葉節點,即獲得數據行的物理存儲位置; 2.直接根據物理存儲位置從堆上訪問數據行。 |
再根據原理多分析一點,不是使用建議,只是這種結構提示給咱們的信息。只說it is ,不說you should。
如知道了彙集索引實現原理後,應該能理解爲何不大建議在長字段上面建彙集索引,由於全部輔助索引都引用主索引,過長的主索引會令輔助索引變得過大;也能理爲何說在彙集索引列上的查找,包括範圍查找會比較高效,由於彙集索引按照某個列在組織;也能理解建了彙集索引後寫入性能會怎樣下降,由於數據組織有了約束,寫入性能降低,插入/刪除/更新彙集鍵值等,會致使記錄的物理移動、頁拆分等額外的磁盤操做;也不難理解非彙集的索引讀數據時候,若是不能從索引上包含所有的查詢列,須要關聯表來查詢,則會有兩次查詢,一次是從非彙集索引上定位到彙集索引鍵,而後再從彙集索引鍵查到數據。
較之非彙集的索引,數據存儲方式只有一種,彙集索引也就只能有一個,也就顯得相對珍貴些。通常選擇會要比較慎重些。知道了這些原理後,對於到底要不要建彙集索引,根據業務特徵在哪一個列上建立,要不要建立非彙集索引,在哪一個上建立。這些也就不難回答。
固然即便理解了原理,在使用中參考使用場景的實驗結果更能幫助咱們作出知足要求的選擇。有點像咱們測試中的黑盒測試之於白盒測試的關係。
一樣對於其餘數據庫方面的技術,經過Database System Concepts 中數據庫通常理論的稍微抽象的觀點了解、看待其在咱們開發中的應用,可使咱們對這些技術的理解更系統,更深入。當項目須要遊走於多個數據庫之間的時候,不至於都是拿着manual,拿着tuning的手冊來徹底的從零開始被指導。