天天看點

堆表上的轉發記錄

今天這篇文章我想談下堆表上特有的性能問題:轉發記錄(Forwarding Records)。首先我們要澄清下什麼是堆表:堆表就是沒有聚集索引定義的表。它對插入新記錄非常快,但當你讀取資料時非常慢。讀取資料會在你的存儲子系統上引入随機存取(random I/O) ,有時候當你碰到轉發記錄,它會進一步降低你的讀取性能。

為什麼會有轉發記錄?

當堆表裡的記錄需要移動到不同的實體位置時,SQL Server使用轉發記錄。假設你有一個變長列的表,首先你在堆表裡插入一些記錄,這個時候你沒有在變長列裡存儲任何資料:

1 -- Create a table to demonstrate forwarding records
 2 CREATE TABLE HeapTable
 3 (
 4     Col1 INT IDENTITY(1, 1),
 5     Col2 CHAR(2000),
 6     Col3 VARCHAR(1000)
 7 )
 8 GO
 9 
10 -- Insert 4 records - those will fit into one page
11 INSERT INTO HeapTable VALUES
12 (
13     REPLICATE('1', 2000),
14     ''
15 ),
16 (
17     REPLICATE('2', 2000),
18     ''
19 ),
20 (
21     REPLICATE('3', 2000),
22     ''
23 ),
24 (
25     REPLICATE('4', 2000),
26     ''
27 )
28 GO      

當你在變長列執行UPDATE語句時,想象下會發生什麼?在那個情況下SQL Server可能需要擴充這個記錄,因為記錄大小更長了,其他記錄必須從同個資料頁移走。

1 -- Let's update the table and expand each row of the table
2 UPDATE HeapTable
3 SET Col3 = REPLICATE('5', 1000)
4 GO      

在那個情況下,SQL Server在原始位置留下稱為轉發記錄,它指向記錄最終存儲的新位置。

堆表上的轉發記錄

SQL Server需要使用這個方法來避免更新同個表上所有非聚集索引。你可能知道,當你在堆表上建立非聚集索引時,在葉子層,非聚集索引指向記錄資料存儲的實體位置。沒有抓發記錄的話,所有這些指針都要改變,這會大幅度降低你的性能。

如何修正轉發記錄?

為了找出表裡是否包含轉發記錄,你可以使用DMF sys.dm_db_index_physical_stats。當你在堆表上調用這個函數并傳入DETAILED模式,SQL Server通過forwarded_record_count列告訴你表上的轉發記錄數。

1 -- Check the forwarding record count through sys.dm_db_index_physical_stats
 2 SELECT
 3     index_type_desc,
 4     page_count, 
 5     avg_page_space_used_in_percent,
 6     avg_record_size_in_bytes,
 7     forwarded_record_count
 8 FROM sys.dm_db_index_physical_stats
 9 (
10     DB_ID('ALLOCATIONDB'), 
11     OBJECT_ID('HeapTable'),
12     NULL, 
13     NULL, 
14     'DETAILED'
15 )
16 GO      
堆表上的轉發記錄

可以看到,表上有2條轉發記錄,在4條記錄之外。為了去掉這些轉發記錄,你可以重建表。

1 -- Rebuild the heap table to get rid of the Forwarding Records
2 ALTER TABLE HeapTable REBUILD
3 GO      

再次運作剛才的查詢,你會發現轉發記錄已經消失。

1 -- Check the forwarding record count through sys.dm_db_index_physical_stats
 2 SELECT
 3     index_type_desc,
 4     page_count, 
 5     avg_page_space_used_in_percent,
 6     avg_record_size_in_bytes,
 7     forwarded_record_count
 8 FROM sys.dm_db_index_physical_stats
 9 (
10     DB_ID('ALLOCATIONDB'), 
11     OBJECT_ID('HeapTable'),
12     NULL, 
13     NULL, 
14     'DETAILED'
15 )
16 GO      
堆表上的轉發記錄

DBA總會考慮到索引碎片,索引重建和索引重新組織操作。但沒有人想過堆表裡的轉發記錄。如果你維護資料庫,經常檢查下堆表上的轉發記錄數,保證始終有最好的性能,這是個非常好的想法。

小結

在這個文章裡你看到了堆表上轉發記錄是咋樣,它是如何降低的記錄讀取,因為需要額外的邏輯讀。當我為資料庫進行健康檢查時,我在檢查堆表時,總會看下轉發記錄數。

相信我:資料庫裡會有巨大數量的堆表,在生産系統裡也會有很多轉發記錄,但DBA們并沒意識到這個副作用。作為第一經驗,我經常推薦在表上建立聚集索引來避免轉發記錄。當然在一些特定場景裡,例如最後頁插入加鎖競争(Last Page Insert Latch Contention),在這裡你可以使用堆表來避免這個問題,但大多數情況下,在表上建立聚集索引還是非常有用的。

參考文章:

https://www.sqlpassion.at/archive/2014/09/08/forwarding-records-on-heap-tables/

注:此文章為

WoodyTu

學習MS SQL技術,收集整理相關文檔撰寫,歡迎轉載,請在文章頁面明顯位置給出此文連結!

若您覺得這篇文章還不錯請點選下右下角的推薦,有了您的支援才能激發作者更大的寫作熱情,非常感謝!