在現實的生產環境中,有可能遇到高並發insert的應用.在此應用時由於堆表(Heap)和聚集表的結構不同導致在高並發的情形下insert效率不盡相同.接下來我會簡單的以測試用例來簡要說明.並舉例說明如果提高聚集表下高並發插入效率.
在測試前我們先簡單了解下堆表和聚集表都是如何完成插入操作的.
關於堆表和聚集表的介紹:SQL Server 索引知識-結構,實現
堆表Insert
方式1 a獲得第一個IAM頁
b 獲取與之相關的PFS頁,從中找到第一個能容納Insert數據行大小的數據頁
c 如果沒有找到相應數據頁則轉到下一個IAM頁然后重復b操作
d 如果到最后的IAM頁還是沒有找到可容納的數據頁則分配新的擴展區(extent)
e Insert指定行
方式2 a 獲取所有IAM頁
b 獲取與之相關的PFS頁(s)找到能容納數據行(s)的數據頁(s)
c 如果沒有相關數據頁,或者沒有足夠的相關數據頁,分配新擴展區(extent)
d 插入相應的行(s)
聚集表Insert
由於聚集表本身的特性,插入數據的時的數據行必須在葉子節點的特定位置.
a 獲取root頁
b 通過B-tree需找到指定插入行的數據頁的位置
c 如果此時數據頁中空間可以容納此行,則insert,如果不能則分配新數據頁或者頁分裂.然后insert
注:頁分裂相對於頁內的DML操作(insert,update)消耗巨大,頁分裂的頻率上升會明顯影響實例的性能
測試用例
測試工具: sqlquerystress
測試環境:sql2008R2,3台不同服務器上分別安裝運行sqlquerystress,100 threads/server
2000 times/thread
注意:如要模擬高並發需多台機器共同執行,單台即便多threads測試,測試結果也不能合理反應高並發情況.
測試腳本
我們在相應的機器上sqlquerystress中分兩次(堆表,聚集表)分別運行insert into tempdb.dbo.tx(str1) select 'aa'測試
然后比較堆表,聚集表相關的性能指標.(Batch requests/sec, elapsed time,wait stats)
堆表
create table t1 ( id int identity(1,1), str1 char(5) ) go DBCC SQLPERF ("sys.dm_os_wait_stats", CLEAR)with NO_INFOMSGS -----run the sqlquerystress at three servers -----100 threads /per server -----2000 times /thread select * from sys.dm_os_wait_stats order by waiting_tasks_count desc
聚集表
checkpoint dbcc dropcleanbuffers create table t2 ( id int identity(1,1) primary key,---clustered index default str1 char(5) ) DBCC SQLPERF ("sys.dm_os_wait_stats", CLEAR)with NO_INFOMSGS -----run the sqlquerystress at three servers -----100 threads /per server -----2000 times /thread select * from sys.dm_os_wait_stats order by waiting_tasks_count desc
測試結果
執行時的吞吐量Batch requests/sec
可以看出在我們的測試環境下堆表的吞吐量是聚集表的2倍以上 圖1-1
圖1-1
執行時間 elapsed time
可以看出在我們的測試環境中執行時間堆表為45s,聚集表為85s 圖1-2
圖1-2
等待事件sys.dm_os_wait_stats
可以看出我們的測試環境中,相比堆表中我們的聚集表操作產生了大量的等待 圖1-3
圖1-3
由上面的實例我們可以看出,由於聚集表和堆表的insert方式差異,導致了在高並發下聚集表的insert效率低於堆表,在實際的項目中可能由於要求使用聚集表的情況下有大量並發插入請求,此時聚集表的insert操作就有可能出現瓶頸.此時我們可以根據sql server的一些知識來解決此瓶頸.
瓶頸分析
由於聚集表的數據組織特性,insert操作時數據只能逐行按序插入.圖1-4
我們來看下聚集表數據頁數據的具體情況.
code
dbcc traceon(3604) dbcc ind(tempdb,t2,1) -----find a datapage pageid 114 dbcc page('tempdb',1,114,3) WITH TABLERESULTS-----view the datapage 114
圖1-4
解決思路
看了數據頁的結構,數據行id 1,2,3…也就是順序插入時大量並發只能集中在一個數據頁中排隊插入.看到這我們應該已經有了相應的思路,能否聚集表中同時往多個數據頁中插入數據?
改變不了數據頁的結構,我們可以改變數據的組織結構---分區.關於分區我就不做介紹了,如果想了解我的朋友宋沄劍的Blog里有篇優秀的文章介紹分區表T-SQL查詢進階--理解SQL SERVER中的分區表
哈希分區.Sql Server中不提供哈希分區,(雖然2014中內存數據庫中已經提供了hash index.)
我們可以以其他方式變向地實現sql server中的hash分區.
這里我采用id/2取模的形式實現奇偶分區(odd-even)
創建完成后我們再執行上面的insert測試.
code
use tempdb CREATE PARTITION FUNCTION f_hash (int) AS RANGE LEFT FOR VALUES (0,1)----------Dim PARTITION FUNC CREATE PARTITION SCHEME OE_f_hash AS PARTITION f_hash all TO ([PRIMARY])-------Dim PARTITION architecture create table t3 ( id int identity (1,1), str1 varchar(2), hashid as id%2 PERSISTED ------hashid odd/even PERSISTED )-----create common table CREATE CLUSTERED INDEX [inx_1] ON t3 ( [hashid] ASC, [id] ASC )ON [OE_f_hash]([hashid])-------Dim PARTITION table DBCC SQLPERF ("sys.dm_os_wait_stats", CLEAR)with NO_INFOMSGS -----run the sqlquerystress at three servers -----100 threads /per server -----2000 times /thread select * from sys.dm_os_wait_stats order by waiting_tasks_count desc
我們先來看下奇偶分區后的數據頁數據行的組織結構
code
dbcc ind(tempdb,t3,1) -----find a datapage pageid xx partition number (1,2) dbcc page('tempdb',1,94,3) WITH TABLERESULTS---view the datapage xx
分區1(偶數區)的數據頁 圖2-1
圖2-1
分區2(奇數區)數據頁 圖2-2
圖2-2
由上面截圖中可以看出分區后的id按奇偶不同在不同的分區數據頁中組織.這時實際就可以進行同時往多個數據頁中insert數據(2個,奇偶)
這時我們再看下奇偶分區的實現下截取與堆表,聚集表相同的相關性能計數截圖2-3,2-4,2-5
圖2-3
圖2-4
圖2-5
可以看出無論從吞吐量Batch Requests/sec,還是執行時間,還是等待事件中相關計數都有了明顯的改善.由於上面的哈希分區只是奇偶分區,如果按照其他hash分區(如除3取模),性能可能還會有一定的提升.感興趣的朋友可以自行測試.但也應注意,由於聚集表和堆表的insert的實現方式不同 ,高並發下堆表(無索引)都具有一定優勢.