在SQL Server :理解數據頁結構我們提到每條記錄都有7 bytes的系統行開銷,那這個7 bytes行開銷到底是一個什么樣的結構,我們一起來看下。
數據記錄存儲我們具體的數據,換句話說,它存在堆表里,或者存在聚集索引的葉子節點。數據記錄結構是為了讓SQL Server更高效的管理數據。我們來看下數據記錄結構示意圖:
上圖中藍色部分是所有數據記錄部分(即系統行開銷,大小基於列個數,等於或大於7 bytes),綠色部分是表結構里取決於定長/變長列的數據記錄部分(實際存放的數據,大小基於實際數據)。
行頭系統數據:
用做狀態位1的第1字節(8位)是用來定義記錄的屬性:
- 第0位:版本信息,在SQL Server 2008里始終是0;
- 第1-3位:這3位用來定義記錄類型;
- 0 數據記錄(data record)
- 1 轉發記錄(Forwarded record)
- 2 轉發存根(a forwarding stub)
- 3 索引記錄(Index record)
- 4 二進制堆碎片或行溢出數據(blob fragment or row overflow data)
- 5 鬼影索引記錄(ghost index record)
- 6 鬼影數據記錄(ghost data record)
- 7 鬼影版本記錄(ghost version record)
- 第4位:存在空值位圖(Null bitmap )或沒有。在SQL Server 2008里沒有不為空的列也會有空值位圖(Null bitmap );
- 第5位:表示是否存在變長列;
- 第6位:表示該列包含版本信息;
- 第7位:在SQL Server里未使用;
用作狀態位2的第2字節(8位)。只有1位用來表示這條記錄是否為鬼影轉發記錄(ghost forwarded record)。
由行頭開始到定長列結尾長度:
下2個字節用來存儲行頭開始到定長列結尾長度。它包含2個狀態位,2個字節用作這個列表示在表中定長數據的實際長度。例如如果表里沒有定長列,這個列的值會是4。這和頁頭列pminlen顯示的值是一樣的。
所有定長列字段值(Fixed_Data_Size):
下n個字節用來存儲在表中的定長數據,n就是在表中所有定長列的長度。如果表里的所有列都是變長列,這一部分就沒有。
空值位圖(Null_Bitmap):
下2個字節用來存儲表里的列數。
下n個字節用作空值位圖,每個bit對應一個列,1表示對應列為空。n的值為:列數 / 8,將值取整。
Variable_Data_Size:
下2個字節用來存儲表里變長列個數。
下n個字節用來存儲每個變長列結束為止的偏移量。每個變長列需要2字節,n的值為:變長列數 * 2 。
最后n個字節用來存儲所有變長列值,n的值為所有變長列的實際長度的總長度。
我們來看一個具體的例子:
創建數據庫,並插入2條記錄
1 USE [InternalStorageFormat] 2 GO 3 4 IF EXISTS ( SELECT * 5 FROM sysobjects 6 WHERE id = OBJECT_ID(N'[dbo].[Customers]') 7 AND OBJECTPROPERTY(id, N'IsUserTable') = 1 ) 8 DROP TABLE dbo.Customers 9 10 CREATE TABLE Customers 11 ( 12 FirstName CHAR(50) NOT NULL, 13 LastName CHAR(50) NOT NULL, 14 Address CHAR(100) NOT NULL, 15 ZipCode CHAR(5) NOT NULL, 16 Rating INT NOT NULL, 17 ModifiedDate DATETIME NOT NULL, 18 ) 19 GO 20 21 22 INSERT INTO dbo.Customers 23 ( FirstName , 24 LastName , 25 Address , 26 ZipCode , 27 Rating , 28 ModifiedDate 29 ) 30 VALUES ( 'Woody' , -- FirstName - char(50) 31 'Tu' , -- LastName - char(50) 32 'ZUOQIAO YOUXI TOWN LINHAI CITY' , -- Address - char(50) 33 '0000' , -- ZipCode - char(5) 34 1 , -- Rating - int 35 '2015-05-07 10:09:51' -- ModifiedDate - datetime 36 ) 37 go 2
使用DBCC IND命令查看表對應頁列表:
1 DBCC IND('InternalStorageFormat','Customers',-1)
我們看到數據頁號為79。
使用DBCC PAGE命令查看頁信息:
1 DBCC TRACEON(3604) 2 DBCC PAGE(InternalStorageFormat,1,79,3) 3 GO
在頁頭pminlen的值是221,包括定長列的總長217 bytes(50+50+100+5+4+8),2 bytes用作狀態位(行頭系統開銷),2 byte 用作由行頭開始到定長列結尾長度。
在記錄槽提到的長度224,包括頁頭pminlen的值,1 byte用作空值位圖(6/8 取整為1)和2 bytes 的字段個數。
我們來看一個變長列的表。
創建表並插入數據后,查看表對應的頁:
1 CREATE TABLE VariableLength( 2 Title CHAR(10) NOT NULL, 3 FirstName VARCHAR(100), 4 Lastname VARCHAR(100), 5 email VARCHAR(50), 6 dob date NOT NULL, 7 phone CHAR(10), 8 Countrycode CHAR(3), 9 Designation VARCHAR(100), 10 PersonalPreference VARCHAR(100) 11 ) 12 GO 13 INSERT INTO VariableLength VALUES ('Mr','Woody','Tu','smartgz@qq.com','2015-5-7','XXXXXXXXXX','Chn','DBA','Nothing Spl') 14 GO 15 DBCC IND('InternalStorageFormat','VariableLength',-1)
我們看到數據頁號為202。
使用DBCC PAGE命令查看頁信息:
1 DBCC TRACEON(3604) 2 GO 3 DBCC PAGE('InternalStorageFormat',1,202,3)--記得根據你的實際數據庫,修改頁號202
pminlen值為30,包含:
- 1 byte 狀態位1
- 1 byte 狀態為2
- 2 bytes 存儲行頭開始到定長列結尾長度
- 26 bytes 所有定長列總長度(10+3+10+3:tittle,dob,phone,countrycode)
-
Title CHAR(10) NOT NULL
-
dob date NOT NULL
-
phone CHAR(10)
-
Countrycode CHAR(3)
-
可以用下列語句驗證下定長列總長度:
1 SELECT DATALENGTH(Title) title,DATALENGTH(dob) dob,DATALENGTH(phone) phone,DATALENGTH(Countrycode) countrycode FROM VariableLength
在槽0顯示的81長度包含:
- 1 byte 狀態位1
- 1 byte 狀態為2
- 2 bytes 存儲行頭開始到定長列結尾長度
- 26 bytes 所有定長列總長度(10+3+10+3:tittle,dob,phone,countrycode)
-
Title CHAR(10) NOT NULL
-
dob date NOT NULL
-
phone CHAR(10)
-
Countrycode CHAR(3)
-
- 2 bytes 存儲列個數
- 2 bytes 用作空值位圖,字段個數/8后取整,即 9/8 得到2
- 2 bytes 存儲變長列個數
- 10 bytes 用來存儲每個變長列結束位置的偏移量 變長列個數 * 2,即 5 * 2 得到10,5個變長列包含:
-
FirstName VARCHAR(100)
-
Lastname VARCHAR(100)
-
email VARCHAR(50)
-
Designation VARCHAR(100)
-
PersonalPreference VARCHAR(100)
-
- 35 bytes 用來存儲所有變長列的實際長度,這個可以使用下列語句得到
1 SELECT DATALENGTH(FirstName)+DATALENGTH(Lastname)+DATALENGTH(email)+ 2 DATALENGTH(Designation)+DATALENGTH(PersonalPreference) FROM VariableLength
總結下每條記錄的系統行開銷:
行頭系統數據(2 bytes)+由行頭開始到定長列結尾長度(2 bytes)+列個數(2 bytes)+空值位圖數據(取整(列個數/8) n bytes)
即 2 bytes + 2 bytes + 2 bytes + 取整(列個數/8)
當列個數小於等於8時,系統行開銷始終是7 bytes,往上沒增加8列,增加1 bytes,即系統系統行開銷始終大於等於7 bytes。
對於在SQL Server里數據記錄的存儲格式,希望你已經有了清晰的認識。