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
查看數據頁

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

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 輸出了錯誤信息,請與系統管理員聯系。
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
看一下他的數據頁

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
數據頁內容

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 輸出了錯誤信息,請與系統管理員聯系。
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
查看數據頁

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 ----------------------------------------------------------------
testnullchar表,因為testnullchar表沒有可變長度數據類型,所以兩行數據都是NULL_BITMAP
testnotnullvarchar表,因為testnotnullvarchar表有可變長度數據類型,所以第二行為NULL_BITMAP VARIABLE_COLUMNS
testnotnullchar表,跟testnullchar表一樣
而NULL_BITMAP VARIABLE_COLUMNS只是說明了數據行中有可變長度類型的數據,不是說某個字段就是可變長度數據類型
題外話
其實這篇文章是我前天看到某篇文章特別而寫的,覺得這個null bitmap要好好研究一下,以免被人誤導
本人不喜歡某些人以泰山壓頂之勢去評論別人,你知道的某些東西可能不一定正確的,而別人不知道的東西,日后一定會知道的,只是時間問題
知道某樣東西的時間問題,遲早問題,或者這就是技術人的通病,自己技術厲害了,就XXXXXX!!!
如有不對的地方,歡迎大家拍磚o(∩_∩)o 哈哈