SQLSERVER中NULL位圖的作用


SQLSERVER中NULL位圖的作用

首先感謝宋沄劍提供的文章和sqlskill網站:www.sqlskills.com,看下面文章之前請先看一下下面兩篇文章

SQL Server誤區30日談-Day6-有關NULL位圖的三個誤區

char nchar varchar nvarchar的區別

在SQLSERVER內部有很多地方都使用到了位圖技術,包括執行計划數據庫系統頁面,復制,還有這篇文章說到的數據行中的NULL位圖

執行計划中有位圖運算符

數據庫系統頁面有:DCM頁面、BCM頁面,詳細請看:SQL Server 2008 存儲結構之DCM、BCM

復制:Replication的犄角旮旯(三)--聊聊@bitmap(@bitmap 是binary類型,即二進制串;簡單來說,它是用來表示所操作的字段位置的參數,通過@bitmap,分發代理從distribution.dbo.msrepl_commands中讀取命令時(update操作),才會知道哪些列進行了更新;)

而這些位圖技術的作用無疑都是為了 標志位

標志哪些地方發生了變化,發生了變化的就標記為1,沒有發生變化的就標記為0

 


建立環境

建表

1 USE [pratice]
2 GO
3 
4 
5 --允許空,varchar類型
6 CREATE TABLE testnullvarchar(id INT ,NAME VARCHAR(20) NULL)
7 GO

插入數據

1 --插入數據
2 INSERT INTO [dbo].[testnullvarchar] ( [id],[Name] )
3 SELECT 1 ,NULL UNION ALL
4 SELECT 2,''
5 GO

查看

1 SELECT * FROM testnullvarchar

建立DBCCResult表

 1 CREATE TABLE DBCCResult (
 2 PageFID NVARCHAR(200),
 3 PagePID NVARCHAR(200),
 4 IAMFID NVARCHAR(200),
 5 IAMPID NVARCHAR(200),
 6 ObjectID NVARCHAR(200),
 7 IndexID NVARCHAR(200),
 8 PartitionNumber NVARCHAR(200),
 9 PartitionID NVARCHAR(200),
10 iam_chain_type NVARCHAR(200),
11 PageType NVARCHAR(200),
12 IndexLevel NVARCHAR(200),
13 NextPageFID NVARCHAR(200),
14 NextPagePID NVARCHAR(200),
15 PrevPageFID NVARCHAR(200),
16 PrevPagePID NVARCHAR(200)
17 )
18 GO
View Code

查看數據頁

1 --TRUNCATE TABLE DBCCResult
2 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,testnullvarchar,-1) ')
3 
4 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
View Code


數據頁內容

 1 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
 2 
 3 PAGE: (1:8370)
 4 
 5 
 6 BUFFER:
 7 
 8 
 9 BUF @0x03CF4E64
10 
11 bpage = 0x16F16000                   bhash = 0x00000000                   bpageno = (1:8370)
12 bdbid = 5                            breferences = 0                      bUse1 = 17294
13 bstat = 0x2c0000b                    blog = 0x32159bb                     bnext = 0x00000000
14 
15 PAGE HEADER:
16 
17 
18 Page @0x16F16000
19 
20 m_pageId = (1:8370)                  m_headerVersion = 1                  m_type = 1
21 m_typeFlagBits = 0x4                 m_level = 0                          m_flagBits = 0x8000
22 m_objId (AllocUnitId.idObj) = 521    m_indexId (AllocUnitId.idInd) = 256  
23 Metadata: AllocUnitId = 72057594072072192                                 
24 Metadata: PartitionId = 72057594059882496                                 Metadata: IndexId = 0
25 Metadata: ObjectId = 1207675350      m_prevPage = (0:0)                   m_nextPage = (0:0)
26 pminlen = 8                          m_slotCnt = 2                        m_freeCnt = 8064
27 m_freeData = 124                     m_reservedCnt = 0                    m_lsn = (3045:22651:20)
28 m_xactReserved = 0                   m_xdesId = (0:0)                     m_ghostRecCnt = 0
29 m_tornBits = 0                       
30 
31 Allocation Status
32 
33 GAM (1:2) = ALLOCATED                SGAM (1:3) = ALLOCATED               
34 PFS (1:8088) = 0x61 MIXED_EXT ALLOCATED  50_PCT_FULL                      DIFF (1:6) = CHANGED
35 ML (1:7) = NOT MIN_LOGGED            
36 
37 Slot 0 Offset 0x60 Length 11
38 
39 Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP     
40 Memory Dump @0x0A1EC060
41 
42 00000000:   10000800 01000000 0200fe†††††††††††††...........              
43 
44 Slot 0 Column 0 Offset 0x4 Length 4
45 
46 id = 1                               
47 NAME = [NULL]                        
48 
49 Slot 1 Offset 0x6b Length 17
50 
51 Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS
52 
53 Memory Dump @0x0A1EC06B
54 
55 00000000:   30000800 02000000 0200fc01 001100c4 †0...............         
56 00000010:   e3†††††††††††††††††††††††††††††††††††.                        
57 
58 Slot 1 Column 0 Offset 0x4 Length 4
59 
60 id = 2                               
61 
62 Slot 1 Column 1 Offset 0xf Length 2
63 
64 NAME =65 
66 
67 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
View Code
1 SELECT LEN(name) FROM testnullvarchar WHERE [id]=1


我們看到第一行的長度是11,第二行的長度是17

 

我們看第一行記錄長度11怎麽得出來的

SQL Server頁中行物理存儲里對數據行的各段進行了解釋

2個字節行標頭存儲了狀態A和狀態B的信息(2 bytes row header)

2個字節存儲固定長度大小,因為一行記錄了有varchar這些不固定長度的數據類型(2 bytes for length of fixed length columns)

SQLSERVER需要知道int、datetime、decimal這些固定長度數據類型的大小

2個字節的列數,用來存儲這個表一共有多少列(2 bytes for number of columns in the table)

1個字節的null bitmap,(1 byte for null bitmap)

4個字節存儲int型數據(4 bytes for int (1st column))

2+2+2+1+4=11

我們看第二行記錄長度17怎麽得出來的

2個字節行標頭存儲了狀態A和狀態B的信息(2 bytes row header)

2個字節存儲固定長度大小,因為一行記錄了有varchar這些不固定長度的數據類型(2 bytes for length of fixed length columns)

SQLSERVER需要知道int、datetime、decimal這些固定長度數據類型的大小

2個字節的列數,用來存儲這個表一共有多少列(2 bytes for number of columns in the table)

1個字節的null bitmap,(1 byte for null bitmap)

4個字節存儲int型數據(4 bytes for int (1st column))

2個字節存儲數據行中的可變長度列數量,統計數據行中一共有多少列是nvarchar ,varchar類型的列( 2 bytes for number of variable length columns in the table)

2個字節存儲可變長度偏移陣列,可變長度偏移陣列的公式

2*表格中可變長度數據類型的列數量,這個表只有一列varchar,所以2*1=2,為什麽要有可變長度偏移陣列?我估計是因為可變長度的數據類型

存儲的數據是不固定的,所以要預留一些位置,當update varchar值的時候有足夠的位置(2 bytes for name column offset)

2個字節存儲name列的值,為什麽用兩個字節大家可以看一下char nchar varchar nvarchar的區別  2 bytes for name (你)

2+2+2+1+4+2+2+2=17


我們這里不討論數據占用空間的問題,這里要說的是null bitmap

無論數據行中是否有null值,都會有一個字節用來存儲null bitmap

而這個null bitmap是不是總是只有一個字節的長度,他的原理是什么??

我畫了一下草圖

他在每一行記錄里都存在,並且標記了哪一列是NULL,哪一列不是NULL

這樣在使用 len函數或者select 出表中的數據時候,先掃描NULL 位圖,遇到某一位為1就跳過不select出來,從而大大加快select的速度

len函數也是一樣,遇到某一位為1就返回null

1 SELECT LEN(name) FROM testnullvarchar WHERE [id]=1

但是一個字節只有8位,也就是只能標記表中8個列,如果一張表有10個列呢??

我們建立另外一張表

 1 USE [pratice]
 2 GO
 3 
 4 
 5 CREATE TABLE testnull10varchar(
 6 id INT ,
 7 NAME1 VARCHAR(2) NULL,
 8 NAME2 VARCHAR(2) ,
 9 NAME3 VARCHAR(2) ,
10 NAME4 VARCHAR(2) ,
11 NAME5 VARCHAR(2) ,
12 NAME6 VARCHAR(2) ,
13 NAME7 VARCHAR(2) ,
14 NAME8 VARCHAR(2) ,
15 NAME9 VARCHAR(2) ,
16 NAME10 VARCHAR(2) 
17 )
18 GO
19 
20 --插入數據
21 INSERT INTO [dbo].[testnull10varchar] ( [id],[Name1],[NAME2],[NAME3],[NAME4],[NAME5],[NAME6],[NAME7],[NAME8],[NAME9],[NAME10] )
22 SELECT 1 ,NULL,'2','2','2','2','2','2','2','2','2' 
23 GO
24 
25 SELECT * FROM testnull10varchar
View Code

看一下他的數據頁

 1 --TRUNCATE TABLE DBCCResult
 2 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,testnull10varchar,-1) ')
 3 
 4 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
 5 
 6 
 7 DBCC TRACEON(3604,-1)
 8 GO
 9 DBCC PAGE([pratice],1,8354,3)
10 GO
View Code

數據頁內容

 1 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
 2 
 3 PAGE: (1:8354)
 4 
 5 
 6 BUFFER:
 7 
 8 
 9 BUF @0x03CF691C
10 
11 bpage = 0x16FAA000                   bhash = 0x00000000                   bpageno = (1:8354)
12 bdbid = 5                            breferences = 0                      bUse1 = 23632
13 bstat = 0x2c0000b                    blog = 0x2159bbbb                    bnext = 0x00000000
14 
15 PAGE HEADER:
16 
17 
18 Page @0x16FAA000
19 
20 m_pageId = (1:8354)                  m_headerVersion = 1                  m_type = 1
21 m_typeFlagBits = 0x4                 m_level = 0                          m_flagBits = 0x8000
22 m_objId (AllocUnitId.idObj) = 527    m_indexId (AllocUnitId.idInd) = 256  
23 Metadata: AllocUnitId = 72057594072465408                                 
24 Metadata: PartitionId = 72057594060275712                                 Metadata: IndexId = 0
25 Metadata: ObjectId = 1303675692      m_prevPage = (0:0)                   m_nextPage = (0:0)
26 pminlen = 8                          m_slotCnt = 1                        m_freeCnt = 8051
27 m_freeData = 139                     m_reservedCnt = 0                    m_lsn = (3045:22809:18)
28 m_xactReserved = 0                   m_xdesId = (0:0)                     m_ghostRecCnt = 0
29 m_tornBits = 0                       
30 
31 Allocation Status
32 
33 GAM (1:2) = ALLOCATED                SGAM (1:3) = ALLOCATED               
34 PFS (1:8088) = 0x61 MIXED_EXT ALLOCATED  50_PCT_FULL                      DIFF (1:6) = CHANGED
35 ML (1:7) = NOT MIN_LOGGED            
36 
37 Slot 0 Offset 0x60 Length 43
38 
39 Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS
40 
41 Memory Dump @0x0855C060
42 
43 00000000:   30000800 01000000 0b0002f8 0a002200 †0.............".         
44 00000010:   23002400 25002600 27002800 29002a00 †#.$.%.&.'.(.).*.         
45 00000020:   2b003232 32323232 323232†††††††††††††+.222222222              
46 
47 Slot 0 Column 0 Offset 0x4 Length 4
48 
49 id = 1                               
50 NAME1 = [NULL]                       
51 
52 Slot 0 Column 2 Offset 0x22 Length 1
53 
54 NAME2 = 2                            
55 
56 Slot 0 Column 3 Offset 0x23 Length 1
57 
58 NAME3 = 2                            
59 
60 Slot 0 Column 4 Offset 0x24 Length 1
61 
62 NAME4 = 2                            
63 
64 Slot 0 Column 5 Offset 0x25 Length 1
65 
66 NAME5 = 2                            
67 
68 Slot 0 Column 6 Offset 0x26 Length 1
69 
70 NAME6 = 2                            
71 
72 Slot 0 Column 7 Offset 0x27 Length 1
73 
74 NAME7 = 2                            
75 
76 Slot 0 Column 8 Offset 0x28 Length 1
77 
78 NAME8 = 2                            
79 
80 Slot 0 Column 9 Offset 0x29 Length 1
81 
82 NAME9 = 2                            
83 
84 Slot 0 Column 10 Offset 0x2a Length 1
85 
86 NAME10 = 2                           
87 
88 
89 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
View Code

 

2個字節行標頭存儲了狀態A和狀態B的信息

2個字節存儲固定長度大小,因為一行記錄了有varchar這些不固定長度的數據類型

2個字節的列數,用來存儲這個表一共有多少列

4個字節存儲int型數據

2個字節存儲數據行中的可變長度列數量,統計數據行中一共有多少列是nvarchar ,varchar類型的列

20個字節存儲可變長度偏移陣列,可變長度偏移陣列的公式2*10=20,因為有10個varchar類型列

9個字節存儲name1~name10列的值,因為name1值為null,所以實際字節長度為9個字節即統計name2~name10的值,詳細可以再看一下

char nchar varchar nvarchar的區別

2個字節的null bitmap

2+2+2+4+2+20+9+2=43

下面用草圖演示

 

 所以這里NULL位圖的長度是根據你當前表中有多少列來決定的

testnull10varchar表有11個列相當於需要2個字節的NULL 位圖了


關於行記錄屬性

在上面的實驗中testnullvarchar表第一行記錄和第二行記錄的行記錄屬性都不一樣,實際上這個行記錄屬性只是指明

testnullvarchar表第二行記錄有可變長度類型數據,並且不為NULL,這並不意味着SQLSERVER掃描到第一行記錄的時候,

當發現record attributes(行記錄屬性)=NULL_BITMAP 就跳過第一行,不去讀取第一行記錄里的數據

掃描到第二行記錄的時候,當發現record attributes(行記錄屬性)=NULL_BITMAP VARIABLE_COLUMNS知道第二行記錄有

可變長度數據並且不為NULL就去讀取第二行記錄里的數據

 

論壇里的rmiao大俠給出了下面解釋:

Null_bitmap in record attributes means this record contains null_bitmap field.

Likewise,'null_bitmap variable_columns' means this record contains null_bitmap field with variable length columns.

 

不管數據行中是否有NULL值,建表的時候是否允許NULL,數據頁中的行都會有record attributes = NULL_BITMAP

而record attributes =NULL_BITMAP VARIABLE_COLUMNS 只是說明了數據行有可變長度數據類型並且不為NULL

所以SQLSERVER在任何情況下都會去掃描這個NULL 位圖的,除了Record Attributes = No null bitmap

詳細可以看一下SQL Server誤區30日談-Day6-有關NULL位圖的三個誤區

 


證明

建立測試環境

 1 USE [pratice]
 2 GO
 3 
 4 
 5 --允許空,char類型
 6 CREATE TABLE testnullchar(id INT,NAME CHAR(20) NULL)
 7 GO
 8 --不允許空,varchar類型
 9 CREATE TABLE testnotnullvarchar(id INT ,NAME VARCHAR(20) NOT NULL)
10 GO
11 --不允許空,char類型
12 CREATE TABLE testnotnullchar(id INT ,NAME CHAR(20) NOT NULL)
13 GO
14 
15 INSERT INTO [dbo].[testnullchar] ( [id],[Name] )
16 SELECT 1,NULL UNION ALL
17 SELECT 2,''
18 GO
19 
20 INSERT INTO [dbo].[testnotnullchar] ( [id],[NAME] )
21 SELECT 1,'' UNION ALL
22 SELECT 2,''
23 GO
24 
25 INSERT INTO [dbo].[testnotnullvarchar] ( [id],[NAME] )
26 SELECT 1,'' UNION ALL
27 SELECT 2,''
28 GO
29 
30 SELECT * FROM testnullchar
31 SELECT * FROM testnotnullchar
32 SELECT * FROM testnotnullvarchar
View Code

查看數據頁

 1 ------------------------------------------------------------------------
 2 --TRUNCATE TABLE DBCCResult
 3 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,testnullchar,-1) ')
 4 
 5 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
 6 
 7 
 8 DBCC TRACEON(3604,-1)
 9 GO
10 DBCC PAGE([pratice],1,15658,3)
11 GO
12 
13 
14 --------------------------------------------------------
15 --TRUNCATE TABLE DBCCResult
16 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,testnotnullvarchar,-1) ')
17 
18 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
19 
20 
21 DBCC TRACEON(3604,-1)
22 GO
23 DBCC PAGE([pratice],1,8353,3)
24 GO
25 
26 
27 --------------------------------------------------------
28 --TRUNCATE TABLE DBCCResult
29 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,testnotnullchar,-1) ')
30 
31 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
32 
33 
34 DBCC TRACEON(3604,-1)
35 GO
36 DBCC PAGE([pratice],1,37266,3)
37 GO
38 
39 
40 
41 ----------------------------------------------------------------
View Code

testnullchar表,因為testnullchar表沒有可變長度數據類型,所以兩行數據都是NULL_BITMAP

 

testnotnullvarchar表,因為testnotnullvarchar表有可變長度數據類型,所以第二行為NULL_BITMAP VARIABLE_COLUMNS

testnotnullchar表,跟testnullchar表一樣

 

 

而NULL_BITMAP VARIABLE_COLUMNS只是說明了數據行中有可變長度類型的數據,不是說某個字段就是可變長度數據類型


題外話

其實這篇文章是我前天看到某篇文章特別而寫的,覺得這個null bitmap要好好研究一下,以免被人誤導

本人不喜歡某些人以泰山壓頂之勢去評論別人,你知道的某些東西可能不一定正確的,而別人不知道的東西,日后一定會知道的,只是時間問題

知道某樣東西的時間問題,遲早問題,或者這就是技術人的通病,自己技術厲害了,就XXXXXX!!!

 


如有不對的地方,歡迎大家拍磚o(∩_∩)o 哈哈


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM