我們都很清楚SQL Server用8KB 的頁來存儲數據,并且在SQL Server里磁盤 I/O 操作在頁級執行。也就是說,SQL Server 讀取或寫入所有數據頁。頁有不同的類型,像數據頁,GAM,SGAM等。在這文章里,讓我們一起來理解下數據頁結構。
SQL Server把數據記錄存在數據頁(Data Page)里。數據記錄是堆表里、聚集索引里葉子節點的行。
數據頁由3個部分組成。頁頭(標頭),數據區(數據行和可用空間)及行偏移數組。
在我們討論在SQL Server里,數據頁內部結構具體是什么樣之前,我們來創建一個表并插入一些記錄。
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 Customers11 (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 GO20 21 22 INSERT INTO dbo.Customers23 ( FirstName ,24 LastName ,25 Address ,26 ZipCode ,27 Rating ,28 ModifiedDate29 )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 - int35 '2015-05-07 10:09:51' -- ModifiedDate - datetime36 )37 go 2
現在我們要找出SQL Server給這個表分配的頁有哪些,這個就要用到非文檔的命令DBCC IND。它的語法如下:
DBCC IND 命令用于查詢一個存儲對象的內部存儲結構信息,該命令有4個參數, 前3個參數必須指定。語法如下:DBCC IND ( { 'dbname' | dbid }, { 'objname' | objid },{ nonclustered indid | 1 | 0 | -1 | -2 } [, partition_number] )第一個參數是數據庫名或數據庫ID。第二個參數是數據庫中的對象名或對象ID,對象可以是表或者索引視圖。第三個參數是一個非聚集索引ID或者 1, 0, 1, or 2. 值的含義:0: 只顯示對象的in-row data頁和 in-row IAM 頁。1: 顯示對象的全部頁, 包含IAM 頁, in-row數據頁, LOB 數據頁row-overflow 數據頁 . 如果請求的對象含有聚集所以則索引頁也包括。-1: 顯示全部IAM頁,數據頁, 索引頁 也包括 LOB 和row-overflow 數據頁。-2: 顯示全部IAM頁。Nonclustered index ID:顯示索引的全部 IAM頁, data頁和索引頁,包含LOB和 row-overflow數據頁。為了兼容sql server 2000,第四個參數是可選的,該參數用于指定一個分區號.如果不給定值或者給定0, 則顯示全部分區數據。和DBCC PAGE不同的是, SQL Server運行DBCC IND不需要開啟3604跟蹤標志.
我們來執行下列的命令:
1 DBCC IND('InternalStorageFormat','Customers',-1)
SQL Server會給我們如下的輸出結果:
可以看到有2條記錄,一條記錄為頁面類型(PageType)為10的頁和一條記錄為頁面類型(PageType)為1的頁。頁面類型(PageType)10是IAM頁,頁面類型(PageType)1是數據頁,它的頁ID是79.
關于數據庫頁類型如下所示:
2 Index page聚集索引的非葉子節點和非聚集索引的所有索引記錄
3 Text mixed pageA text page that holds small chunks of LOB values plus internal parts of text tree. These can be shared between LOB values in the same partition of an index or heap.
4 Text tree pageA text page that holds large chunks of LOB values from a single column value.
7 Sort page排序時所用到的臨時頁,排序中間操作存儲數據用的。
8 GAM page 全局分配映射(Global Allocation Map,GAM)頁面 這些頁面記錄了哪些區已經被分配并用作何種用途。
9 SGAM page共享全局分配映射(Shared Global Allocation Map,GAM)頁面 這些頁面記錄了哪些區當前被用作混合類型的區,并且這些區需含有至少一個未使用的頁面。
10 IAM page 有關每個分配單元中表或索引所使用的區的信息
11 PFS page 有關頁分配和頁的可用空間的信息
13 boot page 記錄了關于數據庫的信息,僅存于每個數據庫的第9頁
15 file header page記錄了關于數據庫文件的信息,存于每個數據庫文件的第0頁
16 DCM page記錄自從上次全備以來的數據改變的頁面,以備差異備份
17 BCM page有關每個分配單元中自最后一條 BACKUP LOG 語句之后的大容量操作所修改的區的信息
現在我們來看看79號類型為1的數據頁里存放的數據,這個就要用到DBCC PAGE命令,它的語法如下:
dbcc page 命令讀取數據頁結構的命令DBCC Page。該命令為非文檔化的命令,具體如下: DBCC Page ({dbid|dbname},filenum,pagenum[,printopt]) 具體參數描述如下: dbid 包含頁面的數據庫ID dbname 包含頁面的數據庫的名稱 filenum 包含頁面的文件編號 pagenum 文件內的頁面 printopt 可選的輸出選項;選用其中一個值: 0:默認值,輸出緩沖區的標題和頁面標題 1:輸出緩沖區的標題、頁面標題(分別輸出每一行),以及行偏移量表 2:輸出緩沖區的標題、頁面標題(整體輸出頁面),以及行偏移量表 3:輸出緩沖區的標題、頁面標題(分別輸出每一行),以及行偏移量表;每一行 后跟分別列出的它的列值 要想看到這些輸出的結果,還需要設置DBCC TRACEON(3604)。
我們來執行下列的命令:
1 DBCC TRACEON(3604)2 DBCC PAGE(InternalStorageFormat,1,79,3)3 GO
SQL Server會給我們包含4個部分的輸出。第1部分是BUFFER,里面是一些內存分配信息,對此我們沒多少興趣。下一部分是固定96 bytes大小的頁頭(page header),頁頭(page header)會類似如下顯示:
頁頭相關字段的含義:
再來看下頁面相關分配情況:
接下來就是用于存放實際數據的槽(slot),每條記錄存放一個槽(slot)里。0號槽在頁里擁有第1條數據,1號槽擁有第2條數據,以此類推。通過下面的圖片,你可以看到我們記錄大小是224 bytes,217 bytes(50+50+100+5+4+8) 的定長和7 bytes 的系統行開銷。
頁的最后一部分是行偏移數組表,我們可以用參數為1的DBCC PAGE命令來,在輸出信息的底部獲得。
執行如下的命令:
1 DBCC TRACEON(3604)2 DBCC PAGE(InternalStorageFormat,1,79,3)3 GO
SQL Server在輸出信息的底部,給我們如下的信息:
這個行偏移表,應該從下往上讀。每條槽條目是一個2 bytes長的指針指向頁里槽偏移量。這里我們插入了2條記錄,所以表里有2個槽條目。第1條記錄指向第96 bytes,剛好在頁頭后。這個行偏移表可以幫助我們管理頁面的記錄。在頁里的行偏移表里,每條記錄需要2 bytes的大小來存儲。于此類似,在堆表上建立的非聚集索引,每個非聚集索引行里都包含一個物理指針映射回堆表里的行記錄。這個物理指針是[文件號:頁號:槽號](file:page:solt)的結構,因此在讀取頁的時候,可以找到堆表里的對應行,再通過行偏移表里槽號里的偏移量,就可以在頁里讀取到對應的行記錄。如果我們要修改頁中間的記錄,我們并不一定需要重組整個頁,我們只要修改偏移表里偏移量即可。
在頁頭我們看到當前頁面還有7644 bytes可以用,我們一起來驗證下。
(8 * 1024) - 96 - (217 * 2)-(7 * 2)-(2 * 2)=7644 bytes
8 * 1024 = 頁的總大小,8K
96= 頁頭大小 96 bytes
217 * 2 = 每條記錄的總長 * 記錄數
7 * 2 = 每條記錄的系統行開銷 * 記錄數
2 * 2 = 行偏移表里每槽占用字節數 * 記錄數
現在我們已經知道了頁的結構,我們一起來小結下。
頁是 8KB 的大小,即 8192 bytes,固定 96 bytes的大小給頁頭使用,接下來是具體的數據以槽的方式存儲。數據記錄的最大長度是 8060 bytes(包括 7 bytes的系統行開銷),因此一條記錄中你擁有的最大字節數是 8053 bytes。下列的表創建語句會失敗。
1 CREATE TABLE Maxsize(2 id CHAR(8000) NOT NULL,3 id1 CHAR(54) NOT NULL4 )
剩下的 36 bytes (8192-96-8060)保留給槽數組(Slot array)或者任何轉發行返回指針(forwarding row back pointer)(每條10 bytes)。這就意味一個頁不一定就能保存18(36/2)條記錄。槽數組(Slot array)根據你的記錄數從下往上增長。如果記錄長度小,頁里就可以存儲更多的記錄,偏移表也會自下而上占用更多的空間。
新聞熱點
疑難解答