這篇接着我們的索引學習系列,這次主要來分享一些有關聚集索引的問題。上一篇SQL索引學習-索引結構主要是從一些基礎概念上給大家分享了我的理解,沒有實例,有朋友就提到了聚集索引的問題,這里列出來一下:
- 其實,我想知道的就是對於一個大數據量的表,我應該用哪種索引,各有什么優缺點。如果能帶一兩個實例,就更perfect了。
- 看過很多這樣文章,但具體還是不知道如何設計表和優化,比如:聚集和非聚集, 唯一與主鍵, 設計表事該如何取舍。應該有示例說明,這更容易理解,只是概念即使理解了也不容易消化。
上面兩位朋友的問題有一個共同特點,就是希望有示例,因為這樣容易讓他們更加容易理解。但從我的角度來講,有示例只能給你提供一個參考而已,夠不成是否容易消化的關鍵因素,最好的辦法是,通過自己的理解,自己有能力去做相應的實驗,這樣效果才是最好的,你也會發現更多的問題,每個項目都有自己的特點,所以性能優化這塊也是需要因地制宜的。
聚集索引的存儲結構
聚集索引的特點
- 索引的數據以及實際物理數據存儲於同一張表中,這與非聚集索引不相同
- 物理數據或者叫關鍵字出現在葉子結點的雙向鏈表中
- 非葉子結點不可能出現物理數據或者叫關鍵字
- 非葉子結點相當於葉子結點的索引
B+樹的結構
- 1,2,3,4,5,6,7為Key
- d1,d2,d3,d4,d5,d6,d7分別是數據
- 紅色框是鏈接下一節點的指針
它的結構完全符合聚集索引的存儲結構,所以我們說聚集索引的存儲結構為B+樹。
聚集索引的重要性
聚集索引的選擇是數據庫設計的基石,不好的聚集索引設計不光是增加查詢的執行時間,而且是一個瀑布性的影響:
- 增加硬盤空間,和選擇的數據列的大小有直接關系
- 效率低下的IO,也許會產生更多的數據頁,或者發生隨機性的數據頁切換等
- 索引碎片的增加
- ......
如何選擇表的聚集索引
一般可以優先參考如下因素:
- 列數據寬度要小或者叫窄列,比如int就只有4字節,這個寬度越小越好,因為可以在同樣的空間中存儲更多的索引數據
- 唯一性,雖然聚集索引並沒強制要求列字段是唯一的,但在系統內部會在具備有重復值的列上增加一個標識位來區分,實際內部還是唯一的,所以盡量選擇重復值很少最好是沒有重復值的列,因為SQL Sever要額外的去維護這些標識
- 靜態的,不易更改的列,很少發生變更最好是從不修改這列的值,因為它也許會引起數據的移動
- 遞增性的,用來避免索引碎片,這樣SQL Server每次在插入數據的時候都會將新記錄追加在最新一條記錄的后面,不會因此影響之前插入的數據順序。
窄列
創建如下表,為了測試只包含兩個字段,一個在類型為Int的Id上創建聚集索引,一個在類型為uniqueindentifier的Code上創建聚集索引,且為唯一聚集索引。
CREATE TABLE dbo.NarrowStudent ( Id INT IDENTITY(1, 1) , -- unique Code UNIQUEIDENTIFIER -- unique ) ; CREATE UNIQUE CLUSTERED INDEX PK_NarrowStudent_Id ON NarrowStudent(Id) CREATE TABLE dbo.Student ( Id INT IDENTITY(1, 1) , -- unique Code UNIQUEIDENTIFIER -- unique ) ; CREATE UNIQUE CLUSTERED INDEX PK_Student_Code ON Student(Code)
再分別插入一條數據:
INSERT INTO dbo.NarrowStudent ( Code ) VALUES ( NEWID() -- Code - uniqueidentifier ) INSERT INTO dbo.Student ( Code ) VALUES ( NEWID() -- Code - uniqueidentifier )
看下行大小:
注意為什么寬度為27而不是20呢(Id類型為int,占用4字節,Code為Guid占用16字節),這是SQL Sever內部為了維護可空值或者是可變長值而預留7位空間。
我們再多插入些數據來做對比,插入的腳本就貼了,然后我們看下兩表所占用的空間對比:采用了int做為主鍵的表數據占用為320K,選用Guid為主鍵的表占用為464K,明顯較int要費磁盤空間。
索引健康情況
下圖中紅線部分有一個非常重要的參數:掃描密度,明顯可以看出在連續對表進行數據插入后,int自增性為主鍵的索引密度比Guid為主鍵的索引密度要大的多。這說明前者產生的索引碎片更低。
聚集索引對非聚集索引的影響
兩者最大的區別在於聚集索引的葉級存儲了數據本身,但非聚集索引葉結點不存在數據記錄,只是一個指向聚集索引的指針,這就意味着在非聚集索引的所有級別中都包含了聚集索引的指針,聚集索引的大小會直接影響非聚集索引的大小。
為上面兩個表,增加一個AddressInfo的字段,且創建非聚集索引,這里為了測試的有效性,不要使用如下語句添加列之后做測試,因為后期表結構的變更會引起比較明顯的數據分頁情況,建議創建新表來測試,下面對比在兩個表中,字段類型以及值者相同以及表數據條數一樣的情況下非聚集索引的大小情況,結論是在其它條件都相同的情況下,誰的主鍵大誰占用的索引空間就更大。
主鍵為int的非聚集索引
主鍵為Guid的非聚集索引
唯一性
上面提到過聚集索引可以選擇具有重復值的列,但在內部會維護一個類型為uniqueifier的字段,長度為4字節,同時還會需要維護可變長列,同樣會占用4字節,所以SQL Server會使每行的大小增加8字節,數據類似如下表格:
Id | First Name | uniqueifier |
1 | Tom | NULL |
2 | Tom | 1 |
3 | Andy | Null |
關鍵字第一次出現時,uniqueifier賦值為NULL,當第二次出現時,就開始計數累加。賦值為NULL時占用0字節,可從如一圖得到結果:
再插入一條重復數據之后再查看行大小,由11字節變成19字節了,這多出來的8字節,就是當uniqueifier值不等於空之后的結果。
這里列一個場景:如果選擇一個時間字段做為主鍵的話,此時瞬間批量插入多條數據,那么時間在某一批數據中都是相同的,這時就需要頻繁維護uniqueifier。重復的數據越多,后續索引的命中就會越困難,同時由於行大小變大會降低磁盤空間的應用,從而降低IO性能。
這篇內容比較長,先講其中的窄列以及唯一性這兩原則吧,這只是從性能角度來區分,沒有涉及具體業務,所以並不是說一定要按此標准來選擇聚集索引,后面再分析其它的幾條規則。