1. 程式人生 > >翻譯:Pro SQL Server Internals

翻譯:Pro SQL Server Internals

原文連結:http://www.allitebooks.com/pro-sql-server-internals-2nd-edition/

原文作者:Dmitri Korotkevitch

資料頁面和資料行

資料庫中的空間分為邏輯8KB頁面。這些頁面從零開始連續編號,可以通過指定檔案ID和頁碼來引用它們。頁面編號始終是連續的,這樣當SQL Server增長資料庫檔案時,新頁面將從檔案中的最高頁碼加1開始編號。同樣,當SQL Server收縮檔案時,它會從檔案中刪除編號最大的頁面。

SQL SERVER中的資料儲存

一般來說,SQL Server儲存和處理資料庫中的資料有三種不同的方式或技術。使用經典的基於行的儲存,資料儲存在資料行中,這些資料行將來自所有列的資料組合在一起。

SQL Server 2012引入了列儲存索引和基於列的儲存。該技術以每列而不是每行儲存資料。我們將在本書的第七部分中介紹基於列的儲存。

最後,SQL Server 2014中引入了一組記憶體技術,並在SQL Server 2016中得到了進一步改進。儘管它們將資料保留在磁碟上以實現冗餘,但它們的儲存格式與基於行和列的儲存格式非常不同。我們將在本書的第八部分討論記憶體中的技術。

本書的這一部分主要關注基於行的儲存和經典的B-Tree索引和堆。

圖1-6顯示了資料頁面的結構。

圖1-6  資料頁面結構

一個96位元組的頁首包含有關頁面的各種資訊,例如頁面所屬的物件,頁面上可用的行數和可用空間量,如果頁面是,則指向上一頁和下一頁的連結 在索引頁鏈中,依此類推。

頁首之後是儲存實際資料的區域。 其次是自由空間。 最後,有一個插槽陣列,它是一個雙位元組條目塊,指示相應資料行在頁面上開始的偏移量。

插槽陣列指示頁面上資料行的邏輯順序。 如果頁面上的資料需要按索引鍵的順序排序,則SQL Server不會對頁面上的資料行進行物理排序,而是根據索引排序順序填充插槽陣列。 插槽0(圖1-6中最右側)儲存頁面上具有最低鍵值的資料行的偏移量; 插槽1,第二低鍵值; 等等。 我們將在下一章更深入地討論索引。

SQL Server提供了一組豐富的系統資料型別,可以在邏輯上分為兩個不同的組:固定長度和可變長度。 固定長度資料型別(如int,datetime,char等)始終使用相同數量的儲存空間,無論其值如何,即使它為NULL也是如此。 例如,int列始終使用4個位元組,nchar(10)列始終使用20個位元組來儲存資訊。

相比之下,可變長度資料型別(如varchar,varbinary和其他一些資料型別)使用儲存資料所需的儲存空間,以及兩個額外位元組。 例如,nvarchar(4000)列將僅使用12個位元組來儲存五個字元的字串,在大多數情況下,使用兩個位元組來儲存NULL值。 我們將在本章後面討論可變長度列不使用儲存空間作為NULL值的情況。

讓我們看一下資料行的結構,如圖1-7所示。

圖1-7  資料行結構

該行的前兩個位元組(稱為狀態位A和狀態位B)是包含有關該行的資訊的點陣圖,例如行型別,如果該行已被邏輯刪除(幻影),並且該行具有NULL值, 可變長度列和版本控制標記。

行中接下來的兩個位元組用於儲存資料的固定長度部分的長度。 它們後面跟著固定長度的資料。

在固定長度資料部分之後,存在空點陣圖,其包括兩個不同的資料元素。 第一個雙位元組元素是行中的列數。 第二個是空點陣圖陣列。 該陣列對錶的每一列使用一位,無論它是否可為空。

即使表沒有可空列,也會在堆表或聚簇索引葉行中的資料行中始終存在空點陣圖。 但是,當索引中沒有可為空的列時,空點陣圖不存在於非葉索引行中,也不存在於非聚簇索引的葉級行中。

在空點陣圖之後,存在該行的可變長度資料部分。 它從行中的兩個位元組數量的可變長度列開始,後跟列偏移陣列。 SQL Server為行中的每個可變長度列儲存兩個偏移量值,即使該值為NULL也是如此。 接下來是資料的實際可變長度部分。 最後,在行的末尾有一個可選的14位元組版本控制標記。 此標記用於需要行版本控制的操作,例如聯機索引重建,樂觀隔離級別,觸發器和其他一些操作。

注意:我們將在第6章討論索引維護,第9章討論觸發器,第21章討論樂觀隔離級別。

讓我們建立一個表,用一些資料填充它,然後檢視實際的行資料。 程式碼如清單1-4所示。 複製功能重複作為第一個引數提供的字元十次。

清單1-4  資料行格式:表建立

create table dbo.DataRows 
( 
    ID int not null, 
    Col1 varchar(255) null, 
    Col2 varchar(255) null, 
    Col3 varchar(255) null 
); 
insert into dbo.DataRows(ID, Col1, Col3)  values (1,replicate('a',10),replicate('c',10)); 
insert into dbo.DataRows(ID, Col2) values (2,replicate('b',10)); 
dbcc ind 
( 
    'SQLServerInternals' /*Database Name*/ 
    ,'dbo.DataRows' /*Table Name*/ 
    ,-1 /*Display information for all pages of all indexes*/ 
);

未記錄但著名的DBCC IND命令返回有關表頁分配的資訊。 您可以在圖1-8中看到此命令的輸出。

圖1-8  DBCC IND輸出

有兩個屬於該表的頁面。 第一個,PageType = 10,是一種特殊型別的頁面,稱為IAM分配對映。 此頁面跟蹤屬於特定物件的頁面。 但是,現在不要關注它,因為我們將在本章後面介紹分配對映頁面。

注意:SQL Server 2012引入了另一個未記錄的資料管理功能(DMF)sys.dm_db_ database_page_allocations,可用作DBCC IND命令的替代。 與DBCC IND相比,此DMF的輸出提供了更多資訊,並且可以與其他系統DMV和/或目錄檢視結合使用。

PageType = 1的頁面是包含資料行的實際資料頁面。 PageFID和PagePID列顯示頁面的實際檔案和頁碼。 您可以使用另一個未記錄的命令DBCC PAGE來檢查其內容,如清單1-5所示。

清單1-5  資料行格式:DBCC PAGE呼叫

 -- Redirecting DBCC PAGE output to console 
 dbcc traceon(3604); 
 dbcc page 
 ( 
     'SqlServerInternals' /*Database Name*/ 
     ,1 /*File ID*/ 
     ,214643 /*Page ID*/ 
     ,3 /*Output mode: 3 - display page header and row details */ 
 );

清單1-6顯示了與第一個資料行對應的DBCC PAGE的輸出。 SQL Server以位元組交換順序儲存資料。 例如,兩位元組值0001將儲存為0100。

清單1-6  第一行的DBCC PAGE輸出

Slot 0 Offset 0x60 Length 39 
 
Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS 
Record Size = 39                    
Memory Dump @0x000000000EABA060 
 
0000000000000000:  30000800 01000000 04000403 001d001d 00270061  0................'.a 
0000000000000014:  61616161 61616161 61636363 63636363 636363    aaaaaaaaacccccccccc 
 
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4 
ID = 1                              
 
Slot 0 Column 2 Offset 0x13 Length 10 Length (physical) 10 
Col1 = aaaaaaaaaa                   
 
Slot 0 Column 3 Offset 0x0 Length 0 Length (physical) 0 
Col2 = [NULL]                       
 
Slot 0 Column 4 Offset 0x1d Length 10 Length (physical) 10 
Col3 = cccccccccc   

讓我們更詳細地看一下資料行,如圖1-9所示。

圖1-9  第一個資料行

如您所見,該行以兩個狀態位開始,後跟兩個位元組的值0800。 這是位元組交換的值0008,它是行中“列數”屬性的偏移量。 此偏移量告訴SQL Server行的固定長度資料部分結束。

接下來的四個位元組用於儲存固定長度的資料,在我們的例子中是ID列。 之後,有兩個位元組的值表明資料行有四列,後面跟著一個位元組的NULL點陣圖。 只有四列,點陣圖中的一個位元組就足夠了。 它以二進位制格式儲存04的值00000100。 它表示該行中的第三列包含NULL值。

接下來的兩個位元組儲存行中可變長度列的數量,即3(0300以位元組為單位)。 它後面是一個偏移陣列,其中每兩個位元組儲存可變長度列資料結束的偏移量。 如您所見,即使Col2為NULL,它仍然使用偏移陣列中的槽。 最後,有來自可變長度列的實際資料。

現在,讓我們看看第二個資料行。 清單1-7顯示了DBCC PAGE輸出,圖1-10顯示了行資料。

圖1-10  第二個資料行資料

清單1-7   第二行的DBCC PAGE輸出

Slot 1 Offset 0x87 Length 27 
 
Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS 
Record Size = 27                    
Memory Dump @0x000000000EABA087 
 
0000000000000000:  30000800 02000000 04000a02 0011001b 00626262  0................bbb 
0000000000000014:  62626262 626262                               bbbbbbb 
 
Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4 
ID = 2                              
 
Slot 1 Column 2 Offset 0x0 Length 0 Length (physical) 0 
Col1 = [NULL]                       
 
Slot 1 Column 3 Offset 0x11 Length 10 Length (physical) 10 
Col2 = bbbbbbbbbb                   
 
Slot 1 Column 4 Offset 0x0 Length 0 Length (physical) 0 
Col3 = [NULL]

第二行中的NULL位圖表示二進位制值00001010,表示Col1和Col3為NULL。 儘管該表具有三個可變長度列,但行中可變長度列的數量表示偏移陣列中只有兩個列/槽。 SQL Server不維護有關行中的尾隨NULL可變長度列的資訊。

提示:通過以可變長度列(通常儲存空值)定義為CREATE TABLE語句中的最後一列的方式建立表,可以減小資料行的大小。 這是CREATE TABLE語句中列的順序很重要的唯一情況。

固定長度資料和內部屬性必須適合單個數據頁面上可用的8,060位元組。 如果不是這種情況,SQL Server不允許您建立表。 例如,清單1-8中的程式碼會產生錯誤。

清單1-8  建立一個數據行大小超過8,060位元組的表

 create table dbo.BadTable 
 ( 
     Col1 char(4000), 
     Col2 char(4060) 

訊息1701,級別16,狀態1,第1行
建立或更改表'BadTable'失敗,因為最小行大小為8,067,包括7位元組的內部開銷。 這超出了允許的最大表行大小8,060位元組。