SQLServer彙集索引鍵的選擇-性能角度談

  1. 基礎資料

1.1頁

SQL Server 中數據存儲的基本單位是頁。編號0-n,全部IO 操做在頁級執行,不管是磁盤IO或是緩存池IO(Buffer Pool).
下圖是頁的結構

注:
1 每一個頁固定大小是8K,其中標頭固定佔96字節,頁尾部行偏移信息佔36字節,剩餘空間8060字節是有效空間,存放數據以及開銷。
2 若是數據行中包含數據類型爲varchar、nvarchar、varbinary 和 sql_variant的列,致使該行超出8060字節,則該列會被移到ROW_OVERFLOW_DATA(行溢出數據) 分配單元中的頁,而在原位置生成一個24字節的指針,此後該列長度減小,又會被移到原始數據頁(IN_ROW_DATA,行內數據)。
另,varchar(max)、nvarchar(max)、varbinary(max)、ntext、text、image 或 xml列由表選項控制,直接存放在LOB類型頁(LOB_DATA)或者存儲16字節指針在原頁實際數據存放LOB類型頁。

 

開始驗證,環境sqlserver 2008 r2,版本號10.50.1600,固定長度列的狀況再也不演示,僅僅演示下變長,且溢出的狀況

1 行溢出列

CREATE TABLE test1(ID INT NOT NULL,col VARCHAR(8000),col1 CHAR(8000));
INSERT INTO test1
SELECT 1,REPLICATE('a',2000),'b'—col1是定長數據,爲IN_ROW_DATA,col是變長,且此時該行數據長度超過8K頁範圍
--查看頁數

--查看頁ID以及實際數據狀況

注:PageType 1 數據頁 2 索引頁 3 LOB頁 10 IAM頁(Index Allocation Map,每一個表或者索引均有此類頁,用來表示表或索引使用的區(extent,每8個連續頁組成一個區)信息)
這幾種頁類型是數據頁類型,其餘管理類頁此處暫不討論。
頁頭部信息

數據信息
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 8039
Memory Dump @0x000000000EDFC060
0000000000000000: 3000481f 01000000 62202020 20202020 †0.H.....b
------此處略去n行
0000000000001F50: 00000001 0000009f 150000d0 0700004f †...............O (此處的變長數據存儲區,因長度超出範圍,存儲24字節指針,下面可見)
0000000000001F60: 00000001 000000††††††††††††††††††††††.......
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
ID= 1
col = [BLOB Inline Root] Slot 0 Column 2 Offset 0x1f4f Length 24 Length (physical) 24
Level = 0 Unused = 0 UpdateSeq = 1
TimeStamp = 362741760
Link 0
Size = 2000 RowId = (1:79:0) (指針指示的該變長列位置,從第一個圖亦可看到,頁79是行溢出數據)

 

2

LOB類型列

,text,ntext,image已經被varchar(max),nvarchar(max)取代,再也不建議使用,此處以varchar(max)爲例

--查看頁數

注:能夠看到,雖然插入的數據爲4000個字符,但仍分配了一個LOB頁,由於表選項已經開啓,值爲1
--查看頁ID及實際數據

--頁頭略去
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 31
Memory Dump @0x000000000F99A060
0000000000000000: 30000800 01000000 0200e001 001f8000 †0...............
0000000000000010: 00d10700 00000072 00000001 000100††††.......r.......
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
ID = 1
col = [Textpointer] Slot 0 Column 2 Offset 0xf Length 16 Length (physical) 16 (此處是指針,16字節)
TextTimeStamp = 131137536 RowId = (1:114:1)
然而,表的這個選項默認是0,即未開啓,此時狀況同varchar類型的行溢出列

 

1.2 索引

如下圖是索引的結構 sql

注:(1) 索引是一個B樹結構,即從根節點到每個葉子節點深度相同 緩存

(2) 每個層級(根節點,中間節點,葉子節點)的頁均被連接在雙向鏈表中 sqlserver

(3) 不一樣的層級間,只能經過上級節點訪問下一級節點 性能

 

索引的root_page(根頁),first_page(首頁)能夠經過sys.system_internals_allocation_units查看,詳細內容不做介紹 ui

 

2彙集索引鍵選擇原則

2.1彙集索引所在的列或列的組合最好是惟一的

SQLServer操做數據的最小單元是頁,因此,索引佔用頁數越少,讀取索引速度越快
CREATE TABLE test4(ID INT NOT NULL ,col CHAR(200))
--建立彙集索引
CREATE CLUSTERED INDEX IXC_test4 ON test4(ID)

--插入20萬條數據,每條重複2 spa

;WITH 3d

L0 AS(SELECT 1 AS c UNION ALL SELECT 1), 指針

L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B), server

L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B), xml

L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),

L4 AS(SELECT 1 AS c FROM L3 AS A, L3 AS B),

L5 AS(SELECT 1 AS c FROM L4 AS A, L4 AS B),

Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L5)

INSERT INTO test4

SELECT n,'a'

FROM Nums CROSS JOIN (SELECT 1 AS col UNION ALL SELECT 1) t

WHERE n <= 100000
--查看頁數

--刪除數據,再次插入20萬條,不重複
TRUNCATE TABLE test4
;WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A, L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A, L4 AS B),
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L5)
INSERT INTO test4
SELECT n,'a'
FROM Nums
WHERE n <= 200000;
--再次查看

兩次插入數據同樣(int 4字節,char 200字節,都是定長),條數同樣(200000),佔用頁數不一樣,爲什麼?
--查看彙集索引有重複鍵值的狀況,某個索引頁

 

注:建立彙集索引不帶UNIQUE關鍵字,則SQLServer會添加一個附加列uniquifier,用於區分惟一性,該列佔用4字節
而使用了uniquifier後,對性能產生的影響以下:
    (1)SQL Server必須在插入或者更新時對如今數據進行判斷是否和現有的鍵重複,若是重複,則須要生成uniquifier。
    (2)由於須要對相同值的鍵添加額外的uniquifier來區分,因此鍵的大小被額外的增長了。所以不管是葉子節點和非葉子節點,都須要更多的頁進行存儲。(這就是上述現象出現的緣由)
從而還影響到了非彙集索引,使得非彙集索引的書籤列變大,從而非彙集索引也須要更多的頁進行存儲。(由於非彙集索引要引用匯集索引的鍵列,下面環節2.3有演示)

 

如下是數據片斷

 

0000000000000000: 1000d000 1d000000 61202020 20202020 †........a
--數據略去
00000000000000D0: 030000†††††††††††††††††††††††††††††††...
Slot 0 Column 0 Offset 0x0 Length 4 Length (physical) 0—-第一個值不加uniquifier
UNIQUIFIER = 0
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
ID = 29
Slot 0 Column 2 Offset 0x8 Length 200 Length (physical) 200
col = a
Slot 0 Offset 0x0 Length 0 Length (physical) 0
KeyHashValue = (48f7cc7c34f7)

 

0000000000000000: 3000d000 1d000000 61202020 20202020 †0.......a
--數據略去
00000000000000D0: 03000001 00db0001 000000†††††††††††††...........
Slot 1 Column 0 Offset 0xd7 Length 4 Length (physical) 4—-第二個值加uniquifier,由於此時有重複數據了
UNIQUIFIER = 1
Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4
ID = 29
Slot 1 Column 2 Offset 0x8 Length 200 Length (physical) 200
col = a

 

注:再次建立同樣的表,創建惟一彙集索引 CREATE UNIQUE CLUSTERED INDEX IXC_test5 ON test5(ID)
結果和上述test4插入惟一數據的狀況同樣,佔用5264個頁

 

2.2使用窄列或窄列組合做爲彙集索引列

    這個道理和上面減小頁的原理同樣,窄列使得鍵的大小變小。使得彙集索引的非葉子節點減小,而非彙集索引的書籤變小,從而葉子節點頁變得更少。最終提升了性能。

   

2.3使用值不多變更的列或列的組合做爲彙集索引列

   爲表建立彙集索引後,SQL Server按照鍵查找行。由於在B樹中,數據是有序的,因此當彙集索引鍵發生改變時,不只僅須要改變值自己,還須要改變這個鍵所在行的位置(RID,即磁盤上的位置,即上述圖示常見的slot),所以有可能使得行從一頁移動到另外一頁。 所以會帶來以下問題:
  1. 行從一頁移動到另外一頁,這個操做是須要開銷的,不只如此,這個操做還可能影響到其餘行,使得其餘行也須要移動位置,由此產生分頁。
  2. 行在頁之間的移動會產生索引碎片。
  3. 鍵的改變會影響到非彙集索引,使得非彙集索引的書籤列也須要改變。

        

        僅僅演示下非彙集索引引用匯集索引鍵列的狀況,插入數據引發的行移動甚至頁拆分暫不演示

        CREATE TABLE test5(ID INT NOT NULL PRIMARY KEY ,ID1 INT NOT NULL )--主鍵默認是彙集索引,只要建立的時候是該表不存在其餘索引,並且不加NONCLUSTERED關鍵字

--建立索引

CREATE INDEX IX_test5 ON test5(ID1)

--插入數據

;WITH

L0 AS(SELECT 1 AS c UNION ALL SELECT 1),

L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),

L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),

L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),

L4 AS(SELECT 1 AS c FROM L3 AS A, L3 AS B),

L5 AS(SELECT 1 AS c FROM L4 AS A, L4 AS B),

Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L5)

INSERT INTO test5

SELECT n,n+1000

FROM Nums

WHERE n <= 10000;

 

--查看彙集索引

DBCC IND(testdb,'test5',1)

indexID>1的爲非彙集索引,indexID=1的爲彙集索引,indexlevel>0的爲中間節點,PageType=2是索引頁

頁ID=10780是彙集索引中間節點,經過這個頁能夠查看葉子節點及起始鍵值

能夠看到,沒有uniquifier列,由於咱們建立的彙集索引是主鍵,即PRIMARY KEY CLUSTERED

再次查詢葉子節點childpageid便可看到索引頁內容,再也不演示

 

--查看非彙集索引

DBCC IND(testdb,'test5',-1)

 

indexID>1的爲非彙集索引,indexlevel>0的爲中間節點,10782

DBCC PAGE(testdb,1,10782,3)

能夠看到,咱們建立的非彙集索引鍵列是ID1,實際查到的有2個,其中一個是彙集索引的鍵列ID,正是經過這個鍵列才能找到不包含在非彙集索引中的其餘列的數據

 

2.4最好使用自增列做爲彙集索引列

    一樣推薦建立一個和數據自己無關的自增列做爲彙集索引列。如上2.3所述,若是使用自增列,新行的插入則會大大的減小分頁和碎片。

 

總結,彙集索引鍵列選擇原則,惟,窄,增

相關文章
相關標籤/搜索