SQL Server 存儲引擎-剖析Forwarded Records


我們都知道數據在存儲引擎中是以頁的形式組織的,但數據頁在不同的組織形式中其中對應的數據行存儲是不盡相同的,這里通過實例為大家介紹下堆表的中特有的一種情形Forwared Records及處理方式.

概念

堆表中,當對其中的記錄進行更新時,如果當前數據頁無法滿足更新行的容量,此時這行記錄將會轉移到新的數據頁中,而原數據頁中將會留下指針(文件號,頁號,槽號)鏈接到新的數據頁中.

Code 創建測試數據

create database testpage
go
use testpage
go
create table testtb
(
id int identity(1,1),
str1 char(100) default replicate('a',100),
str2 varchar(2000) default replicate('b',500),
str3 varchar(2000) default replicate('c',1000)
)
go

insert into testtb default values
go 20

Code 查看相關數據頁 如圖1-1

DBCC TRACEON(3604)

GO

DBCC IND(testpage,'testtb', 1)---find the data page

GO

DBCC PAGE('testpage', 1, 79, 3)-----view data page find slot 2(ID=3)

GO

                                  

                                                              圖1-1

現在我們來更新ID=3的數據使當前數據頁(79)無法容納此行數據,然后觀察數據頁,

Code

update dbo.testtb set str2=replicate('t', 1000) where ID=3--update ID=3

GO

DBCC PAGE('testpage', 1, 79, 3)

GO

繼續找到slot 2槽位(ID=3)觀察 如圖1-2所示,此時slot2數據的Record Type = FORWARDING_STUB,也就是此時槽位2只留下RID記錄,數據轉到其他數據頁中了(Forwarding to  =  file 1 page 94 slot 0  )

 

                                                圖1-2

 

這里稍微深入的講下RID的存儲內容,實例中根據dbcc page已經給我們展示RID的內容,實際上存儲是16進制的如圖1-2中的黑色部分(045e0000 00010000 00

).具體對應RID內容如圖1-3

 

                                            圖1-3

 

我們在找到實際存儲ID=3的數據頁看下數據內容(1:94:0) 如圖1-4

圖中我省去了數據內容

code

DBCC PAGE('testpage', 1, 94, 3)

GO

                                         圖1-4

 

接下來我們繼續更新ID=3讓新的數據頁也無法容納它,然后觀察相應的數據頁如圖1-5(三個dbcc page 合成圖)

(此時ID=3的原始頁94,槽號2指向了新的數據頁位置184)如圖所示1-5所示

code

insert into testtb default values
go 20----先插入一些數據

update dbo. Testtb set str2=replicate('t', 2000),str3=replicate('t', 2000) where ID=3 GO---繼續更新ID=3

DBCC PAGE('testpage', 1, 79, 3)--------源ID=3,現在執行(1:184:2)
GO
DBCC PAGE('testpage', 1, 94, 3)--------第一次修改時ID=3存儲位置(1:94:0),現在slot 0沒有了
GO
DBCC PAGE('testpage', 1, 184, 3)------目前id=3的數據存儲位置
GO

 

                                               圖1-5

 

可以看出id=3的原始頁(1:79:2)的數據再次變更后的由(1:94:0)挪到了(1:184:2)中,

而頁號94槽號0就不存在了.

堆表中的非聚集索引.

當堆表中有非聚集索引存在時,非聚集索引RID指向的原始頁位置

我們通過實例看下

注:關於heap rid我就不做詳細介紹了,實例中通過查詢轉換可以算出10進制對應的RID

Code

CREATE UNIQUE NONCLUSTERED INDEX inx_1 ON testtb (id )

DBCC IND(testpage,'testtb', -1)----find the index page (page type 2)115
GO
DBCC PAGE('testpage', 1, 115, 3)---find the heap rid where id=3 heap rid =0x4F00000001000200

DECLARE @HeapRid BINARY(8)
SET @HeapRid = 0x4F00000001000200
       SELECT
       CONVERT (VARCHAR(5),
                    CONVERT(INT, SUBSTRING(@HeapRid, 6, 1)
                               + SUBSTRING(@HeapRid, 5, 1)))
     + ':'
     + CONVERT(VARCHAR(10),
                    CONVERT(INT, SUBSTRING(@HeapRid, 4, 1)
                               + SUBSTRING(@HeapRid, 3, 1)
                               + SUBSTRING(@HeapRid, 2, 1)
                               + SUBSTRING(@HeapRid, 1, 1)))
     + ':'
          + CONVERT(VARCHAR(5),
                    CONVERT(INT, SUBSTRING(@HeapRid, 8, 1)
                               + SUBSTRING(@HeapRid, 7, 1)))
                               AS 'Fileid:Pageid:slot'

可以看到select 的輸出正好是(1:79:2)我們原始的id=3的位置

 

關於性能

由於forwarded record的存在,當訪問到這種數據行時,會消耗額外的隨機IO,從而影響性能.更有甚者,由於額外的數據頁被放入內存中,造成BP的污染,致使性能下降.

(研發要求對一個頻繁訪問的大堆表更新擴充欄位,執行完了性能依舊下降有木有?)

 

我們通過簡單實例來看下

訪問forwarded record會造成額外IO如圖2-1

Code

set statistics io on

select * from testtb where id=2

select * from testtb where id=3

                                    圖2-1

當表數據量大時,大批量更新擴充欄位會造成對緩沖池的污染

code

create table testbp
(
id int identity(1,1),
str1 char(100) default replicate('a',100),
str2 varchar(2000) default replicate('b',500),
str3 varchar(2000) default replicate('c',1000)
)
go
insert into testbp default values
go 10000

dbcc dropcleanbuffers
select * from testbp

SELECT count(*)*8/1024 AS 'Cached Size (MB)'
,CASE database_id
WHEN 32767 THEN 'ResourceDb'
ELSE db_name(database_id)
END AS 'Database'
FROM sys.dm_os_buffer_descriptors with(nolock)
where db_name(database_id)='testpage'
GROUP BY db_name(database_id) ,database_id
-----buffer pool 15MB

update dbo. testbp set str2=replicate('t', 1000)---make forwarded recordes

dbcc dropcleanbuffers

select * from testbp


SELECT count(*)*8/1024 AS 'Cached Size (MB)'
,CASE database_id
WHEN 32767 THEN 'ResourceDb'
ELSE db_name(database_id)
END AS 'Database'
FROM sys.dm_os_buffer_descriptors with(nolock)
where db_name(database_id)='testpage'
GROUP BY db_name(database_id) ,database_id
------31MB

順序執行代碼時可以看出,testpage表更改前后占Buffer Pool的大小分別為15M,31M,對BP影響明顯.

監控/發現

實際生產環境中我們需要監控一些性能指標用來輔助DBA解決問題,保證運維效率,針對這里,我們監控性能計數器中SQL Server Access Methods對象中的forwarded records/sec,如果你設定的了性能Baseline,這個值如果有異常變化,則需要我們關注.

同時我們也可以根據系統的DMF找出特定對象的forwarded records信息.代碼如下

select 
    object_name(object_id) as objectName
    ,index_type_desc
    ,forwarded_record_count
 from 
    sys.dm_db_index_physical_stats(db_id(),null,null,null, 'detailed')
    where object_name(object_id)='testbp'
------view the forwarded records info

注:可以通過簡單的Batch檢索整個庫甚至實例中的堆表的相關信息,有興趣的朋友自己寫下.

處理

如果發現了因為forwarded Recordes引起的性能問題,我們可以選擇表中創建聚集索引改變數據組織結構(forwarded Recordes只在堆表中存在).如果無法添加聚集索引,也可以選擇重組堆表(alter table heap rebuild)操作時應注意時間窗口

結語

任何事物都存在因果,套用數據庫系統中,我們應該清楚自己的所作所為,以及帶來的效用/影響.合理到位的分析,評估會讓我們的工作變得從容.


免責聲明!

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



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