今天這篇文章我想談下堆表上特有的性能問題:轉發記錄(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在原始位置留下稱為轉發記錄,它指向記錄最終存儲的新位置。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL0gzMxQTM4IjM5QDOwQjMvwlNwUTMwIzLchDNzATN38CX1EDMyc2bsJ2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
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技術,收集整理相關文檔撰寫,歡迎轉載,請在文章頁面明顯位置給出此文連結!
若您覺得這篇文章還不錯請點選下右下角的推薦,有了您的支援才能激發作者更大的寫作熱情,非常感謝!