一、聚集索引和非聚集索引
聚集索引:類似字典的拼音目錄。表中的數據按照聚集索引的規則來存儲的。就像新華字典。整本字典是按照A-Z的順序來排列。這也是一個表只能有一個聚集索引的原因。因為這個特點,具體索引應該建在那些經常需要order by,group by,按范圍取值的列上。因為數據本身就是按照聚集索引的順序存儲的。不應該建在需要頻繁修改的列上,因為聚集索引的每次改動都以為這表中數據的物理數據的一次重新排序。就想新華字典一樣。聚集索引適合建立在大數據量但是小數目不同值的列上,就像新華字典有收錄了一兩萬的漢字,但是其拼音只有A-Z一樣。但是並不是不同值越少越好。如果一個列只有極少值,如性別只有男女,在大數據量下無論是聚集索引和非聚集索引都是不適合建立的。因為其不同值辣么少。就像要查性別為男的,那么平均有一半就符合條件。就算建立索引也用處不大。值得注意的是有些數據庫如sql server在你創建主鍵時會默認主鍵即為聚集索引,如果沒有指定主鍵數據庫本身也會創建一個不可見的索引,因為表本身總要有個排序規則是吧。主鍵作為聚集索引與大數據量但是小數目不同值適合建立聚集索引的規則是相違背的。即使這樣也需要這樣做的原因剛才說過,表總是需要一個排序規則的。如果你有更加合適的列適合做聚集索引是可以修改聚集索引的,但是聚集索引的修改一定一定一定要謹慎,因為聚集索引涉及要數據的物理存放數據。不合理的聚集索引會十分嚴重的拖累數據庫的性能。注意,雖然一般主鍵默認就是聚集索引,但是並不代表聚集索引的值具有唯一約束,主鍵不等於具體索引。這個不要弄混,剛才說過聚集索引適合大數據量但是小數目不同值的列上,聚集索引值是允許重復的,就像新華字典一樣,拼音A下面會有很多字。聚集索引流程示意圖:
非聚集索引:非聚集索引和表里面數據的物流地址順序無關。有些像新華字段的偏旁,如單人旁,他下面的兩個挨着的字可能頁數會有很大的差別。非聚集索引的查詢方式和聚集索引的查詢方式不一樣。聚集索引找到符合條件的目標即獲得該目標行的所有數據,因為直接找到是他的物理地址。非聚集索引則不一樣,如果你查詢的字段是非聚集索引的一部分。那么因為索引本身包含的就有相應數據就可以直接返回,但是如果你查的數據包含非索引數據,比如你用了select *,那么通過非聚集索引找到目標之后,目標體會有一個目標數據聚集索引的key,會通過這個key再通過聚集索引找到完整的目標數據。也就是說使用非聚集索引且要查詢的列不包含非聚集索引列本身,那么要經過二次查詢。一次查詢獲得聚集索引的key,二次通過key與聚集索引確定目標數據。有點像新華字段偏旁查詢,但是查到字沒標頁數只標了讀音,如果你僅僅查這字怎么寫或者讀什么,那么直接通過偏旁這個部分的數據即可,但是如果要查更詳細的,就要通過拼音去拼音目錄里面找,確定頁數再去指定頁獲得具體數據了。當然這只是打個比喻,因為字典里面偏旁查到的字也會表明頁數的。非聚集索引適合建立在大數據量下且有大數目不同值,即列中大部分值都互不相同的情況。非聚集索引流程示意圖:
針對MySQL5.6版本中的innodb引擎來說,比較規范的數據庫表設計(包括我們公司)都會有一條不成文的規定,那就是給每張表一個自增主鍵。那么自增主鍵除了有數據的唯一性外,還有什么所用呢?為什么要有自增主鍵?閱讀過《58到家數據庫30條軍規解讀》中解釋道:
- 主鍵遞增,數據行寫入可以提高插入性能,可以避免page分裂,減少表碎片提升空間和內存的使用。
- 主鍵要選擇較短的數據類型,Innodb引擎普通索引都會保存主鍵的值,較短的數據類型可以有效的減少索引的磁盤空間,提高索引的緩存效率。
- 無主鍵的表刪除,在row模式的主從架構,會導致備庫夯住。
第三條先不必關注,我們來看看前兩條。為什么能提高插入性能呢,避免page分頁又是怎么回事?這里就不得不說一下聚集索引了。
聚集索引(Clustered Index)一個聚集索引定義了表中數據的物理存儲順序。如何理解聚集索引呢,好比一個電話本,比如一個電話本是按照姓氏排序,並且電話號碼緊跟着后面。因為聚集索引決定了表中數據的物理存儲順序,那么一個表則有且只有一個聚集索引。一個聚集索引可以包含多個列。好比一個電話本是基於名字,姓氏同時排序。
Innodb的聚集索引
Innodb的存儲索引是基於B+tree,理所當然,聚集索引也是基於B+tree。與非聚集索引的區別則是,聚集索引既存儲了索引,也存儲了行值。當一個表有一個聚集索引,它的數據是存儲在索引的葉子頁(leaf pages)。因此innodb也能理解為基於索引的表。
Innodb如何決定那個索引作為聚集索引呢?
Innodb如何選擇一個聚集索引,對於Innodb,主鍵毫無疑問是一個聚集索引。但是當一個表沒有主鍵,或者沒有一個索引,Innodb會如何處理呢。請看如下規則
1. 如果一個主鍵被定義了,那么這個主鍵就是作為聚集索引
2. 如果沒有主鍵被定義,那么該表的第一個唯一非空索引被作為聚集索引
3. 如果沒有主鍵也沒有合適的唯一索引,那么innodb內部會生成一個隱藏的主鍵作為聚集索引,這個隱藏的主鍵是一個6個字節的列,改列的值會隨着數據的插入自增。
還有一個需要注意的是:次級索引的葉子節點並不存儲行數據的物理地址。而是存儲的該行的主鍵值。
所以:一次級索引包含了兩次查找。一次是查找次級索引自身。然后查找主鍵(聚集索引)現在應該明白了吧,建立自增主鍵的原因是:
Innodb中的每張表都會有一個聚集索引,而聚集索引又是以物理磁盤順序來存儲的,自增主鍵會把數據自動向后插入,避免了插入過程中的聚集索引排序問題。聚集索引的排序,必然會帶來大范圍的數據的物理移動,這里面帶來的磁盤IO性能損耗是非常大的。
而如果聚集索引上的值可以改動的話,那么也會觸發物理磁盤上的移動,於是就可能出現page分裂,表碎片橫生。
解讀中的第二點相信看了上面關於聚集索引的解釋后就很清楚了。
雖然遵循上面的原則也沒錯,但某些特殊的情況也是可以自己指定一些非自增主鍵為聚集索引的。如:
當數據量大,但長時間不會被更新的;
新生成的數據的索引本來就是按照自增的順序增加的等等。
首先,必須了解一些基本知識:對於一張表來說,聚集索引只能有一個,因為數據真實的物理存儲順序就是按照聚集索引存儲的。基於這個原理,現在可以用這樣的方案來測試:對一張表設置一個主鍵, 之后再建立一個聚集索引,假如聚集索引能創建成功, 表明主鍵就不是聚集索引, 如果不可以建立聚集索引,就表明主鍵是聚集索引。
--建立一張TABLE 同時設置主鍵 CREATE TABLE student ( stud_id INT IDENTITY(1,1) NOT NULL, stud_name NVARCHAR(50) NOT NULL, CONSTRAINT pk_student PRIMARY KEY(stud_id) );
接下來就嘗試對這張表建立一個聚集索引吧。
CREATE CLUSTERED INDEX index_stud_name ON student(stud_name);
執行這條語句的時候,SQLServer的消息框彈出了這樣的處理信息:“無法對 表 'student' 創建多個聚集索引。請在創建新聚集索引前刪除現有的聚集索引 'pk_student'。"
這就說明主鍵一定是聚合索引嗎?來看看下面的實驗。
再看一下關於主鍵的定義吧,主鍵是表中的一個字段或多個字段,用來唯一地標識表中的一條記錄。唯一性是主鍵最主要的特性。在查閱建立主鍵的方法的時候, 一個之前被我完全忽略的創建方式突然出現在我的眼前, 在建立主鍵的時候可以聲明為CLUETERED(聚集)或NONCLUETERED(非聚集)!也就是說主鍵也可以聲明為非聚集索引,如下:
CREATE TABLE student ( stud_id INT IDENTITY(1,1) NOT NULL, stud_name NVARCHAR(20) NOT NULL, CONSTRAINT pk_student PRIMARY KEY NONCLUSTERED (stud_id) );
在SQLServer中,主鍵的創建必須依賴於索引,默認創建的是聚集索引,這就解釋了在上面的嘗試中為什么表中已建立了聚集索引。
所以從上面看出:在SQLServer中主鍵可以是聚合索引也可以是非聚合索引。