探索資料庫內部存儲數據存儲結構,將從資料庫內部如何存儲數據,索引數據如何存儲,操作數據對存儲影響,最後總結。 ...
資料庫經常需要打交道,但是從來沒想過資料庫內部是如何存儲數據。
今天探索一下資料庫內部如何存儲數據,從下麵幾個方面探索
資料庫內部如何存儲數據
1. 要驗證,先準備數據,這裡創建是一個表,並添加3條數據
create table DataTable(Id int identity(1,1), [Name] varchar(50), [Address] varchar(200), CreateTime datetime2) insert into DataTable select 'Wilson','廣州市天河區',GETDATE() union all select 'Alice','北京市朝陽區',GETDATE() union all select 'Key','廣州市番禺區',GETDATE()View Code
2. 利用DBCC查看頁數據,資料庫名稱Demo
DBCC TRACEON(2588,3604) --打開追蹤 DBCC IND(Demo,DataTable,-1) --查看分配情況,這裡查到的PageFID,PagePID,用於PAGE查詢,PageType = 1 是數據頁 DBCC PAGE(Demo,1,224,1) --查看頁槽位情況
PAGE是查看cha內容太多,截取部分數據,
Slot 0, Offset 0x60, Length 43, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 43 Memory Dump @0x000000557C77A060 0000000000000000: 30001000 01000000 c0bb04c8 7fea400b 04000002 0.............@..... 0000000000000014: 001f002b 0057696c 736f6eb9 e3d6ddca d0ccecba ...+.Wilson......... 0000000000000028: d3c7f8 ... Slot 1, Offset 0x8b, Length 42, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 42 Memory Dump @0x000000557C77A08B 0000000000000000: 30001000 02000000 c0bb04c8 7fea400b 04000002 0.............@..... 0000000000000014: 001e002a 00416c69 6365b1b1 bea9cad0 b3afd1f4 ...*.Alice.......... 0000000000000028: c7f8 .. Slot 2, Offset 0xb5, Length 40, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 40 Memory Dump @0x000000557C77A0B5 0000000000000000: 30001000 03000000 c0bb04c8 7fea400b 04000002 0.............@..... 0000000000000014: 001c0028 004b6579 b9e3d6dd cad0b7ac d8aec7f8 ...(.Key............ OFFSET TABLE: Row - Offset 2 (0x2) - 181 (0xb5) 1 (0x1) - 139 (0x8b) 0 (0x0) - 96 (0x60)
上面是16進位,拿第一條數據來分析,分析完再跟另外兩條數據來驗證
Slot 0, Offset 0x60, Length 43, DumpStyle BYTE 0000000000000000: 30001000 01000000 c0bb04c8 7fea400b 04000002 0.............@..... 0000000000000014: 001f002b 0057696c 736f6eb9 e3d6ddca d0ccecba ...+.Wilson......... 0000000000000028: d3c7f8
30001000:欄位的標誌位,沒找到官方說明,目前瞭解是,有可變欄位和無可變欄位不一致,已經刪除的行會從30->3c
01000000:這個明顯代表是Id,從另外兩條數據可以猜到
c0bb04c8 7fea400b:因為看到Name在後面,這裡剛好8個字元,這個應該是時間datetime2,寫了個代碼將16進位轉換datetime2,現在就用代碼驗證,代碼在文章最後放出來,因為時間轉換有精度丟失,所以兩個時間不是完全一直
04000002:04代表一共有4個欄位,02代表一共有連個可變長欄位
001f002b: 這裡是兩個可變字元的偏移量
0057696c 736f6eb9 e3d6ddca d0ccecba d3c7f8:這一串保存是姓名和地址,前面兩個00,應該是上面長度的一個分隔符,下麵也是用代碼驗證一下
到這裡也大概清楚SQL如何存儲數據,它不是按欄位順序存儲,先存固定長度的欄位,然後插入分隔的符號,然後存可變長的欄位,這樣做可能為了減少移動數據帶來的成本,後面操作數據會有講到。
索引數據如何存儲
到目前為止,是沒有涉及到索引,因為我們上面的數據是沒有創建索引,是一個堆表。
索引分非聚集索引和聚集索引
1. 創建非聚集索引
create index IX_DataTable_Name on DataTable(Name ASC)
1.1 查看數據頁分配情況
DBCC IND(Demo,DataTable,-1) --查看分配情況 DBCC PAGE(Demo,1,280,1) --查看頁分配情況
1.2 Page分配情況
Slot 0, Offset 0x60, Length 21, DumpStyle BYTE Record Type = INDEX_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 21 Memory Dump @0x00000055721FA060 0000000000000000: 36000100 00010001 00020000 01001500 416c6963 6...............Alic 0000000000000014: 65 e
Slot 1, Offset 0x75, Length 22, DumpStyle BYTE Record Type = INDEX_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 22 Memory Dump @0x00000055721FA075 0000000000000000: 36000100 00010002 00020000 01001600 4b657920 6...............Key 0000000000000014: 4c69 Li Slot 2, Offset 0x8b, Length 22, DumpStyle BYTE Record Type = INDEX_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 22 Memory Dump @0x00000055721FA08B 0000000000000000: 36000100 00010000 00020000 01001600 57696c73 6...............Wils 0000000000000014: 6f6e on
1.3 Page頁面分析
0100 00010001:這個是行Id,轉義過來是(1:256:1),因為現在還是堆表,所以指向行Id
00020000:這個也是正文開始標記
01001500:這裡記錄索引的長度
後面的就是索引內容
可以看到,索引存儲大概結構
指針 -> 索引信息(長度)-> 索引內容
查看行Id,可以使用sys.fn_PhysLocFormatter(%%physloc%%)
select sys.fn_PhysLocFormatter(%%physloc%%),* from DataTable
2 創建聚集索引
create clustered index IX_DatTaable_Name on DataTable(Name ASC)
2.1 查看數據頁分配情況
DBCC IND(Demo,DataTable,-1) --IndexID=1就是聚集索引 DBCC PAGE(Demo,1,234,3)
2.2 Page分配情況
0000000000000000: 30001000 02000000 20d1565f 0deb400b 05000003 0....... .V_..@..... 0000000000000014: 001b0020 002c0041 6c696365 b1b1bea9 cad0b3af ... .,.Alice........ 0000000000000028: d1f4c7f8
2.3 Page頁面分析
這裡數據大概格式行Id+定長欄位+行信息+變長數據,這裡就不展開驗證。
值得註意是,這裡的欄位數量和可變長欄位數量為 05000003
這裡之所以會多了一個欄位,是因為我們添加的聚集索引沒有指定唯一,SQL SERVER會自動添加一個4位元組的欄位,確保聚集索引唯一。
操作數據對存儲影響
1. INSERT
insert into DataTable(Name,Address,CreateTime) select 'Jack','廣州市天河區',GETDATE()
1.1 查看Page情況
Slot 0, Offset 0x60, Length 44, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 44 Memory Dump @0x0000005CD11FA060 0000000000000000: 30001000 02000000 40f61b57 1aeb400b 05000003 0[email protected]..@..... 0000000000000014: 001b0020 002c0041 6c696365 b1b1bea9 cad0b3af ... .,.Alice........ 0000000000000028: d1f4c7f8 .... Slot 1, Offset 0xe3, Length 43, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 43 Memory Dump @0x0000005CD11FA0E3 0000000000000000: 30001000 04000000 350f285e 1aeb400b 05000003 0.......5.(^..@..... 0000000000000014: 001b001f 002b004a 61636bb9 e3d6ddca d0ccecba .....+.Jack......... 0000000000000028: d3c7f8 ...
可以看到在Alice後面槽位插入了數據(因為這個表的聚集索引是在Name,升序)
2. UPDATE
update DataTable set Address = '廣州市白雲區黃邊路8號' where Id = 1
2.1 查看Pag情況,用2,查看整個Page
0000005CD387A064: 02000000 40f61b57 1aeb400b 05000003 001b0020 [email protected]..@........ 0000005CD387A078: 002c0041 6c696365 b1b1bea9 cad0b3af d1f4c7f8 .,.Alice............ 0000005CD387A08C: 30001000 03000000 40f61b57 1aeb400b 05000003 0[email protected]..@..... 0000005CD387A0A0: 001b001e 002a004b 6579b9e3 d6ddcad0 b7acd8ae .....*.Key.......... 0000005CD387A0B4: c7f83000 10000100 000040f6 1b571aeb 400b0500 ..0[email protected]..@... 0000005CD387A0C8: 0003001b 0021002d 0057696c 736f6eb9 e3d6ddca .....!.-.Wilson..... 0000005CD387A0DC: d0b0d7d4 c6c7f830 00100004 00000035 0f285e1a .......0.......5.(^. 0000005CD387A0F0: eb400b05 00000300 1b001f00 2b004a61 636bb9e3 .@..........+.Jack.. 0000005CD387A104: d6ddcad0 ccecbad3 c7f83000 10000100 000040f6 ..........0.......@. 0000005CD387A118: 1b571aeb 400b0500 0003001b 00210036 0057696c .W..@........!.6.Wil 0000005CD387A12C: 736f6eb9 e3d6ddca d0b0d7d4 c6c7f8bb c6b1dfc2 son.................
可以看到有2個Wilson的記錄,因為更新的欄位比原來的長,原來地方放不下,在當前頁空閑的地方重新整條記錄複製過去,然後偏移量指向新的地址。實際上欄位變短了也會發生這種遷移。
這樣原來的地方就變成碎片。事實上索引頁的維護也是一樣道理。
3.DELETE
delete DataTable where Id = 2
3.1 查看Pag情況,用2,查看整個Page
3c001000 ................<... 0000005CD627A064: 02000000 40f61b57 1aeb400b 05000003 001b0020 [email protected]..@........ 0000005CD627A078: 002c0041 6c696365 b1b1bea9 cad0b3af d1f4c7f8 .,.Alice............ 0000005CD627A08C: 30001000 03000000 40f61b57 1aeb400b 05000003 0[email protected]..@.....
可以看到Alice這條記錄還存在頁上,原來行頭的4個標誌位從30001000 -> 3c001000
總結
其實資料庫如何存儲對平常開發沒什麼影響,只是無聊研究一下。
其實還是有點影響,我能想到如下,未必準確和完整。
1. 儘量選擇定長的欄位,例如狀態,類型這種欄位定義int
2. char 是用空間換時間,經常更新欄位且比較短的欄位可以考慮定義char
3. 超長的varchar欄位可以考慮不放在主表,不然一頁存不下,又會發生行溢出問題
4. 索引有利有弊,要儘量合理使用索引,特別是聚集索引,非常要謹慎使用。
轉發請標明出處:https://www.cnblogs.com/WilsonPan/p/12605299.html
示例代碼:https://github.com/WilsonPan/Net.Demos/tree/master/Demo.SQLTools