聚集索引的葉子頁存儲的就是表的數據。因此,表行物理上按照聚集索引列排序,因為表數據只能有一種物理順序,所以一個表只能有一個聚集索引。
當我們創建主鍵約束時,如果不存在聚集索引並且該索引沒有被明確指定為非聚集索引,SQL Server會自動將其創建為唯一的聚集索引,這並不是說主鍵列就一定是聚集索引,這只是默認行為而已。
示例,建表時通過指定主鍵為非聚集索引使主鍵列不為聚集列:
CREATE TABLE MyTableKeyExample { Column1 int IDENTITY PRIMARY KEY NONCLUSTERED, Column2 int }
一、堆表與聚集表
沒有聚集索引的表稱為堆表。堆表的數據列沒有任何特別的順序,連接到表的相鄰頁面。與訪問大的聚集表相比,對標這種無組織的結構通常增大了訪問大的堆表的開銷。
有聚集索引的表稱為聚集表,聚集表是B樹結構,數據量大時,能夠大幅減少讀次數。
二、與非聚集索引的關系
SQL Server中聚集索引和非聚集索引之間有一個有趣的關系,非聚集索引的一個索引列包含指向表的對應數據行的指針。這個指針被稱為行定位器。行定位器的值取決於數據表是堆表還是聚集表。當時堆表時,行定位器是指向堆中數據行的RID指針。對於具有聚集索引的表,行定位器是聚集索引鍵值。
下面用一個表格來說明這種關系
假設有一個2列的表:
RID(這不是實際列) | 列1 | 列2 |
1 | A1 | A2 |
2 | B1 | B2 |
堆表:
索引列(列1) | 行定位器 |
A1 | RID = 1 指向表中第一行數據 |
B1 | RID = 2 指向表中第二行數據 |
聚集表,假設我們將列2設為聚集索引列:
索引列(列1) | 行定位器 |
A1 | A2 指向聚集鍵 |
B1 | B2 指向聚集鍵 |
由此可見,通過非聚集索引列查找一行數據,還需要多一步-通過RID獲得實際數據。這個RID在堆表是行指針,在聚集表是聚集鍵值。
三、聚集索引的建議
1、首先創建聚集索引
對於聚集表而言,因為所有非聚集索引在其索引行上都保存一個聚集索引鍵值,所以非聚集索引和聚集索引創建的順序非常重要。如果非聚集索引先於聚集索引創建,那么非聚集索引的行定位器將包含指向堆表的RID的指針。然后再創建聚集索引時,會將所有非聚集索引的RID指針改為聚集鍵,這實際上相當於重新建立了非聚集索引。
為了最好的性能,最好在創建任何非聚集索引之前創建聚集索引。這將使得非聚集索引在創建的時候將他們的行定位器直接設置為聚集索引值。這對最終的性能沒有太大影響,但是SQL Server工作量少很多,速度快很多。如果你是在線上運行着的系統進行維護操作,這尤其有用。
2、保持窄索引
因為所有的非聚集索引將聚集索引鍵作為行定位器,為了最佳的性能,應使聚集索引的總體長度盡可能小。
試想,假如創建了個寬的聚集索引,如CHAR(500),這將在每個非聚集索引中添加一個500字節的值。就算非聚集索引什么都不放,光聚集索引鍵值占用的空間,它一頁的數據頁僅僅能存放16個數據行左右。
保持窄聚集索引能有效減少邏輯讀操作與磁盤I/O。
3、一步重建聚集索引
因為聚集索引上有非聚集索引的依賴性,用單獨的DROP INDEX 和 CREATE INDEX語句重建聚集索引將導致所有非聚集索引被重建兩次(DROP,行定位器指向堆表數據行指針,CREATE行定位器指向新的聚集鍵值)。為了避免這種情況,使用CREATE INDEX語句的DROP_EXISTING子句來在一個單獨的原子步驟中重建聚集索引。相似地,也可以在非聚集索引上使用DROP_EXISTING子句。
CREATE CLUSTERED INDEX index1 ON PersonTenThousand(Id) WITH (DROP_EXISTING = ON)
4、何時使用聚集索引
在某些情況下,使用聚集索引是非常有幫助的。
1、檢索一定范圍的數據
因為聚集索引的葉子頁面就是表的實際數據,聚集索引列的順序就是表中數據行的物理順序。如果數據行的物理順序與查詢請求的數據順序相同,磁盤刺頭可以順序地讀取所有行,而不需要太多的磁頭移動。
假設我聚集索引建立在ID列,我需要讀取ID BETWEEN 1 AND 100或ID > 100的數據,那么所有數據行在磁盤上排列在一起。這使磁頭可以移動到磁盤上第一行的位置,然后用最少的磁頭移動順序讀出所有數據。另一方面,如果行在磁盤上沒有以正確的物理順序排列,磁頭必須隨機地從一個位置移動到另一個位置來讀取所有相關的行。磁頭的物理移動是磁盤操作開銷的最主要部分,將行以合適的物理順序在磁盤上排序(使用聚集索引)優化了I/O開銷。
2、讀取預先排序的數據
聚集索引在數據讀取需要排序時特別有效,如果在可能需要排序的一列或多列上創建一個聚集索引,那么行將被按該順序物理排序,這消除了數據讀取之后排序的開銷。
在沒有聚集索引的情況下,檢索范圍排序的數據:
在有聚集索引的情況下,檢索范圍排序的數據:
從中看到,有聚集索引的范圍排序返回數據非常快速,因為對於聚集列,本身就是已經排好順序存放於數據庫中的。
5、何時不使用聚集索引
在某些情況下,最好不使用聚集索引。
1、頻繁更新的列
如果聚集索引列頻繁更新,將導致所有非聚集索引行的行定位器相應更新,從而顯著地增加相關操作查詢的開銷。還將阻塞這段時間引用相同部分和非聚集索引的其他查詢,從而影響數據庫的並行性。因此,應該避免在大量更新的列上創建聚集索引。
2、寬的關鍵字
因為所有非聚集索引將聚集鍵作為其行定位器,所以為了性能,應該避免在非常寬或太多列上創建聚集索引。上面紅色加粗字體特別說明了原因。
3、太多並行的順序插入
如果希望並發地添加許多新行,那么對於性能來講,將他們分布到表的各個數據頁面更好一些。但是,如果將所有行按照與聚集索引相同的順序添加,那么所有的插入操作都在表的最后一個頁面上進行。這可能在磁盤的對應山區造成一個巨大的“熱點”,為了避免磁盤熱點,不應該將數據行按照物理位置相同的順序排列。可以通過創建另一列上的索引(該索引不會將行按照新航相同的順序來排列)來插入操作隨機地分布到整個表。這個問題只在大量同時插入時發生。
允許在表的尾部插入,能夠避免需要容納新行時發生的頁拆分。如果並行插入數據降低,那么按照新行的順序來排列數據行(使用聚集索引)將避免頁拆分。但是,如果磁盤熱點成為性能瓶頸,那么新行可以通過降低表的填充因子來容納到中間頁面。另外,“熱”的頁面將在內存中,這也有利於性能。
最后附上一個設置非主鍵為聚集索引列的方法:
1. 查看所有的索引,默認情況下主鍵上都會建立聚集索引
查看索引:
sp_helpindex person
查看約束:
sp_helpconstraint person
2. --刪除主鍵約束,把【1】中查詢出的主鍵上的索引約束【如:PK__person__117F9D94】去除掉。去掉主鍵字段上面的主鍵約束,此時該字段不是主鍵了。
alter table person drop constraint PK_Person
3.--創建聚集索引到其它列
create clustered index test_index on person(Name)
4.—修改原來的主鍵字段還是為主鍵,此時會自動建立非聚集索引【因為已經有了聚集索引】
sp_helpindex person sp_helpconstraint person alter table person drop constraint PK_Person create clustered index test_index on person(Name) alter table person add primary key (id)
alter table person add primary key (id)