[SQL Server] Let's clear to storage index of (n)varchar in sql server

讓我們一起來瞧瞧,當我在(n)varchar類型欄位建立非叢集索引,SQL Server如何儲存

SQL Server2016開始,針對非叢集索引鍵長度限制已經從900 bytes放寬至1700 bytes,

當我們新增或更新資料時,由於會影響索引,所以也會觸發該機制,

但請不要忘記Disk table針對每筆資料有8060 bytes限制,

這兩種機制誰先誰後,我先簡單驗證一下

--該資料表有兩個可變長度欄位,長度分別為8000,2000
CREATE TABLE [dbo].[testvarchar](
	[c1] [int] IDENTITY(1,1) NOT NULL,
	[c2] [nvarchar](2000) NULL,
	[c3] [varchar](8000) NULL,
	[c4] [datetime] NULL
)

建立該表過程,SQL Server沒有任何警告,但當我們建立索引,我們會看到1700 bytes限制警告

create index idx1 on [testvarchar](c3,c2)

假設我新增一筆資料,我們會收到8060 長度限制錯誤

insert into [testvarchar](c2,c3,c4)
values(replicate('r',2000),replicate('r',8000),GETDATE())

或許你想和我一樣嘗試忽悠SQL Server,把長度改短,這

時我們就會收到索引鍵長度限制錯誤,如下

insert into [testvarchar](c2,c3,c4)
values(replicate('r',1000),replicate('r',6000),GETDATE())

我繼續嘗試insert then update

--insert資料,避開8060 byte和1700 bytes限制
insert into [testvarchar](c2,c3,c4)
values(replicate('r',10),replicate('r',1000),GETDATE())

當我要執行update,我依然無法突破SQL Server的防衛

update 	 [testvarchar] set c2= replicate('r',1000)

我在更深入來驗證該假設是合理又正確的

-- Retrieve physical information about the clustered index
SELECT * FROM sys.dm_db_index_physical_stats
(
    DB_ID('AdventureWorks2012_Data'),
    OBJECT_ID('testvarchar'),
    NULL,
    NULL,
    'DETAILED'
)

透過dm_db_index_physical_stats的min_record_size和max_record_size我們可以知道,index page中,

叢集和非叢集索引鍵是使用可變動長度來儲存。

 

我更進一步使用DBCC PAGE來看看更詳細資料

DBCC IND(AdventureWorks2012_Data, testvarchar, 2)

DBCC traceon(3604)
DBCC PAGE(AdventureWorks2012_Data, 1, 304768, 1)

可以看到每一筆Index Record長度都不同,另外,針對可變長度欄位上建立索引,

要了解會有額外的儲存成本2 bytes來存放可變長度的偏移量(offset),

當我們在設計並計算一個page可存放多少index record時,也要留意這些成本,

以免無法建立良好索引來提高查詢效能。