SQL Server 深刻解析索引存儲(堆)

標籤:SQL SERVER/MSSQL SERVER/數據庫/DBA/索引體系結構/堆數據庫

概述

     本篇文章是關於堆的存儲結構。堆是不含彙集索引的表(因此只有非彙集索引的表也是堆)。堆的 sys.partitions 中具備一行,對於堆使用的每一個分區,都有 index_id = 0。默認狀況下,一個堆有一個分區。當堆有多個分區時,每一個分區有一個堆結構,其中包含該特定分區的數據。例如,若是一個堆有四個分區,則有四個堆結構;每一個分區有一個堆結構。根據堆中的數據類型,每一個堆結構將有一個或多個分配單元來存儲和管理特定分區的數據。每一個堆中的每一個分區至少有一個 IN_ROW_DATA 分配單元。若是堆包含大型對象 (LOB) 列,則該堆的每一個分區還將有一個 LOB_DATA 分配單元。若是堆包含超過 8,060 字節行大小限制的可變長度列,則該堆的每一個分區還將有一個 ROW_OVERFLOW_DATA 分配單元。有關分配單元的詳細信息,json

sys.system_internals_allocation_units 系統視圖中的列 first_iam_page 指向管理特定分區中堆的分配空間的一系列 IAM 頁的第一頁。SQL Server 使用 IAM 頁在堆中移動。堆內的數據頁和行沒有任何特定的順序,也不連接在一塊兒。數據頁之間惟一的邏輯鏈接是記錄在 IAM 頁內的信息。測試

 

正文

堆結構

能夠經過掃描 IAM 頁對堆進行表掃描或串行讀操做來找到容納該堆的頁的擴展盤區。由於 IAM 按擴展盤區在數據文件內存在的順序表示它們,因此這意味着串行堆掃描連續沿每一個文件進行。使用 IAM 頁設置掃描順序還意味着堆中的行通常不按照插入的順序返回。大數據

 

 頁面的組成spa

 

一個SQL數據頁面=標頭+數據行+剩餘空間+行偏移表(若是表中存在大數據類型字段)+溢出表(若是存在) 3d

行偏移指針

---測試數據
CREATE TABLE Theap
(ID INT IDENTITY(1,1) NOT NULL,
NAME NVARCHAR(MAX) NOT NULL,
IDATE DATETIME DEFAULT(GETDATE()) NOT NULL
)
GO
---插入1000條測試數據
DECLARE @ID INT=1
WHILE(@ID<=1000)
BEGIN
INSERT INTO Theap(NAME)VALUES((@ID))
SET @ID=@ID+1 
END
GO
SELECT * FROM Theap

---開啓跟蹤標誌
DBCC TRACEON(3604,2588)
--DBCC TRACEOFF(3604,2588)
---獲取對象的數據頁,結構:數據庫、對象、顯示
DBCC IND(Ixdata,Theap,-1)

SELECT * FROM sys.system_internals_allocation_units WHERE container_id=72057594039566336

分析114頁code

DBCC page(Ixdata,1,114,3)

整個數據頁有四部分組成對象

1.頁面在內存中的映射信息(BUFFER:)blog

2. 頁頭部分(PAGE HEADER):記錄了頁號、頁類型、記錄數,LSN及其餘信息,在上一章已經講過

3. 數據部分(DATA):以16進制格式存儲行記錄(從第96個字節開始)

4. 行偏移部分(OFFSET TABLE):以倒序的順序記錄了行記錄的指針位置,這個使用2的顯示方式比較明顯看出

看看一行記錄在頁面中是怎樣記錄的

 

 

 

00000000: 30001000 01000000 76ff7401 64a40000 †0.......v.t.d...
00000010: 0300b801 00190031 00†††††††††††††††††.......1.

1字節:30>00110000 ;右邊第一位開始是0位,第4位和第5位是1,因爲在2008中null bit map老是存在的,因此只考慮第五位,即存在變長字段。 

1字節:00;狀態位B在SQLServer2005/2008中未啓用,因此爲00

2字節:1000;這兩個字節是表示定長列的字節數,反過來排0010=1*16=16個字節,表中的定長列ID(4個字節)+IDATE(8個字節)+4個字節(默認加的)=16個字節

N個字節:01000000 76ff7401 64a40000;這N個字節是定長字段的內容,總共12個字節

2個字節:0300;表中的字段數,因爲表中只有3個字段因此用0300表示

1個字節:b8>10111000;這個字節表示主要是判斷對應的字段內容是否有空值,1表明容許爲空,前三個字段都不容許爲空,並且表只有三個字段因此不用看後面。

2個字節:01 00;這個字段表示變長列的個數,根據剛纔說的方法倒過來00 01=1個字段,表中頁只有NAME字段是變長字段。

2個字節*變長字段的個數:1900;因爲表中只有一個變長字段,因此只有兩個字節,表示第一個變長列的終止位置=25

N個字節:變長字段的內容,3100轉換成字符恰好是‘1’

在線16進制轉字符 http://www.bejson.com/tools/0x/

 

查詢

SELECT [ID]
      ,[NAME]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Theap]
  WHERE NAME='1'
    
  SELECT [ID]
      ,[NAME]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Theap]
  WHERE NAME='900'

分析查詢能夠看出不管你查詢的是'1'仍是'900',都是掃描一次,邏輯讀取4次,由於存在4個頁,用ID去查也是同樣.

 

行溢出

CREATE TABLE Theapover
(ID INT IDENTITY(1,1) NOT NULL,
NAME VARCHAR(5000) NOT NULL,
NAME1 VARCHAR(5000) NOT NULL,
IDATE DATETIME DEFAULT(GETDATE()) NOT NULL
)
GO
---插入1000條測試數據
DECLARE @ID INT=1
WHILE(@ID<=1000)
BEGIN
INSERT INTO Theapover(NAME,NAME1)VALUES(REPLICATE(1,5000),REPLICATE(2,5000))
SET @ID=@ID+1 
END
GO
SELECT * FROM Theapover
ORDER BY ID
GO

DBCC IND(Ixdata,Theapover,-1)

SELECT * FROM sys.system_internals_allocation_units WHERE container_id=72057594039828480

 

總共插入了1000條記錄,一行佔一頁再加上兩個IAM頁恰好2002頁,

存在兩個IAM頁,分別是3281和3283頁,還有一個比較特殊的頁3280頁,3280頁是溢出數據裏面的根頁,等一下看一下這頁的數據。

 

分析IAM頁

DBCC page(Ixdata,1,3283,3)

分析溢出頁

DBCC page(Ixdata,1,3282,3)

 

注意:不是堆頁和溢出頁就只能一一對應,因爲當前表中堆頁容納不下兩條記錄因此就致使了堆頁和溢出頁同樣,當堆頁能夠存多條記錄的時候就會出現一個堆頁對應多個溢出頁。

測試查詢

  SELECT  [ID]
      ,[NAME]
      ,[NAME1]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Theapover]
  where ID=500

當我繼續往堆表裏插入數據直到表超過4G的時候會有新的IAM頁生成,並且IAM頁之間存在鏈關係(數據頁)。

查詢發現新生成的3135IAM頁種的數據頁的行溢出指向的是新生成的511256IAM頁的溢出頁,這樣的話IAM頁之間的鏈關係對查詢效率貌似沒有什麼改善的好處。

1. IAM用於查找分配給heap的全部數據頁信息,IAM頁中記錄了全部的頁面的頁id。

2. 對於大多數較小的heap表來講,僅須要一個IAM頁就能夠管理其頁面。

3. 若heap表大於4GB或包含LOB數據類型的話,則會包含多個IAM頁面。

4. 當查詢要獲取heap表的全部記錄時,SQL Server使用IAM頁來掃描heap表

總結

  堆表的頁是沒有規律的不存在頁鏈,因此致使堆表的查詢效率不好,當查詢一個10萬條記錄的堆表邏輯讀取就須要10萬次,若是堆表的數據量很大須要屢次進行物理讀獲取頁面的時候對於IO的消耗是很是大的,建議表都應該建彙集索引。

 

備註:

    做者:pursuer.chen

    博客:http://www.cnblogs.com/chenmh

本站點全部隨筆都是原創,歡迎你們轉載;但轉載時必須註明文章來源,且在文章開頭明顯處給明連接,不然保留追究責任的權利。

《歡迎交流討論》

相關文章
相關標籤/搜索