Latch是什麼sql
Latch是SQL Server引擎保證內存中的結構的一致性的輕量同步機制。好比索引,數據頁和內部結構(好比非葉級索引頁)。SQL Server使用Buffer Latch保護緩衝池中的頁,用I/O Latch保護還未加載到緩衝池的頁,用Non-Buffer Latch保護內存中的內部結構。數據庫
Buffer Latch:當工做線程訪問緩衝池中的某個頁以前,必需要先得到此頁的Latch。主要用於保護用戶對象和系統對象的頁。等待類型表現爲PAGELATCH_*session
I/O Latch:當工做線程請求訪問的頁未在緩衝池中時,就會發一個異步I/O從存儲系統將對應的頁加載到緩衝池中。這個過程會獲取相應頁上 I/O Latch,避免其它線程使用不兼容的Latch加載同一頁到緩衝池中。等待類型表現爲PAGEIOLATCH_*數據結構
Non-Buffer Latch:保護緩衝池頁以外的內部內存結構時使用。等待類型表現爲LATCH_XX。架構
Latch只在相應頁或者內部結構被操做期間持有,而不像鎖那樣在整個事務中持有。好比,使用WITH NOLOCK查詢某表。查詢過程當中不會在表的任何層級上獲取共享鎖,可是在數據頁能夠讀取前,須要獲取這些的頁的Latch。併發
Latch的模式異步
Latch跟鎖同樣,是SQL Server引擎併發控制的一部分。在高併發環境下的Latch爭用的狀況無可避免。SQL Server使用模式兼容性強制互不兼容的兩個Latch請求線程中的一個等待另外一個完成操做並釋放目標資源上的Latch後,才能訪問此目標資源。ide
Latch有5種模式:函數
KP – Keep Latch 保證引用的結構不能被破壞高併發
SH – Shared Latch, 讀數據頁的時候須要
UP – Update Latch 更改數據頁的時候須要
EX – Exclusive Latch 獨佔模式,主要用於寫數據頁的時候須要
DT – Destroy Latch 在破壞引用的數據結構時所須要
Latch模式兼容性,Y表示兼容,N表示不兼容:
KP |
SH |
UP |
EX |
DT |
Y |
Y |
Y |
Y |
N |
Y |
Y |
Y |
N |
N |
Y |
Y |
N |
N |
N |
Y |
N |
N |
N |
N |
N |
N |
N |
N |
N |
影響Latch爭用的因素
因素 |
明說 |
使用過多的邏輯CPU |
任何多核的系統都會出現Latch爭用。Latch爭用超過可接受程度的系統,多數使用16個或者以上的核心數。 |
架構設計和訪問模式 |
B樹深度,索引的大小、頁密度和設計,數據操做的訪問模式均可能會致使過多的Latch爭用 |
應用層的併發度太高 |
多數Latch爭用都會伴有應用層的高併發請求。 |
數據庫邏輯文件的佈局 |
邏輯文件佈局影響着分配單元結構(如PFS,GAM,SGAM,IAM等)的佈局,從而影響Latch爭用程度。最著名的例子就是:頻繁建立和刪除時臨表,致使tempdb的PFS頁爭用 |
I/O子系統性能 |
大量的PAGEIOLATCH等待就說明SQL Server在等待I/O子系統。 |
診斷Latch爭用
診斷的主要方法和工具包括:
觀察性能監視器的CPU利用率和SQL Server等待時間,判斷二者是否具備關聯性。
經過DMV獲取引發Latch爭用的具體類型和資源。
診斷某些Non-Buffer Latch爭用,可能還須要獲取SQL Server進程的內存轉儲文件並結合Window調試工具一塊兒分析。
Latch爭用是一種正常的活動,只有當爲獲取目標資源的Latch而產生的爭用和等待時間影響了系統吞吐量時,才認爲是有害的。爲了肯定一個合理的爭用程度,須要結合性能、系統吞吐理、IO和CPU資源一塊兒分析。
經過等待時間衡量Latch爭用對應用性能的影響程度
1.頁平均Latch等待時間增加與系統吞吐理增加一致。
若是頁平均Latch等待時間增加與系統吞吐理增加一致,特別是Buffer Latch等待時間增加的超過了存儲系統的響應時間,就應該使用sys.dm_os_waiting_tasks檢查當前的等待任務。還須要結合系統活動和負載特徵觀察。診斷的通常過程:
使用「Query sys.dm_os_waiting_tasks Ordered by Session ID」腳本或者「Calculate Waits Over a Time Period」腳本觀察當前的等待任務和平均Latch等待時間狀況。
使用「QueryBufferDescriptorsToDetermineObjectsCausingLatch」腳本肯定爭用發生的位置(索引和表)。
使用性能計數器觀察MSSQL%InstanceName%\Wait Statistics\Page Latch Waits\Average Wait Time或者查詢sys.dm_os_wait_stats觀察頁平均Latch等待時間。
2.業務高峯期Latch等待時間佔總等待時間百分比。
若是Latch等待時間比率隨着應用負載增加而直線增加,可能Latch爭用會影響性能,須要優化。經過等待統計性能計數觀察Page和Non-Page Latch等待狀況。而後將之與CPU\RAM\IO\Network吞吐量等相關的計數器比較。例如使用Transactions/sect和Batch Requests/sec衡量資源利用狀況。
sys.dm_os_wait_stats沒有包含全部等待類型的等待時間,這是由於它記錄是的自上次實例啓動(或清空)之後的等待數據。也能夠經過dbcc SQLPERF ('sys.dm_os_wait_stats', 'CLEAR')手動清空它。在業務高峯前取一次sys.dm_os_wait_stats數據,在業務高峯時取一次,而後計算差別。
3. 系統吞吐理沒有增加(甚至降低),同時應用負載加劇,SQL Server可用的CPU增長。
在高併發和多CPU系統中,對相似自增彙集索引的併發插入,會致使一種像現:CPU數量增長,而系統吞吐量降低,同時Latch頁等待會增長。
4. 應用負載增加時CPU利用率卻沒有增加。
當CPU利用率沒有隨着應用併發負載增加而增加,說明SQL Server在等待某種資源(Latch爭用的表現)。
查詢當前的Latch
下面的查詢能夠查看當前實時的等待信息。wait_type爲PAGELATCH_* 和PAGEIOLATCH_*的即爲Buffer Latch的等待。
SELECT wt.session_id, wt.wait_type
, er.last_wait_type AS last_wait_type
, wt.wait_duration_ms
, wt.blocking_session_id, wt.blocking_exec_context_id, resource_description
FROM sys.dm_os_waiting_tasks wt
JOIN sys.dm_exec_sessions es ON wt.session_id = es.session_id
JOIN sys.dm_exec_requests er ON wt.session_id = er.session_id
WHERE es.is_user_process = 1
AND wt.wait_type <> 'SLEEP_TASK'
ORDER BY wt.wait_duration_ms desc
查詢返回列的說明:
列 |
說明 |
Session_id |
task所屬的session id |
Wait_type |
當前的等待類型 |
Last_wait_type |
上次發生等待時的等待類型 |
Wait_duration_ms |
此等待類型的等待時間總和(毫秒) |
Blocking_session_id |
當前被阻塞的session id |
Blocking_exec_context_id |
當前task的ID |
Resource_description |
具體等待的資源 |
下面的查詢返回Non-Buffer Latch的信息
select * from sys.dm_os_latch_stats where latch_class <> 'BUFFER' order by wait_time_ms desc
返回列的說明:
列 |
說明 |
Latch_class |
Latch類型 |
Waiting_requests_count |
當前Latch類型發生的等待次數 |
Wait_time_ms |
當前Latch類型發生等待的時間總和 |
Max_wait_time_ms |
當前Latch類型發生等待最長時間 |
Latch爭用的常見場景
尾頁的數據插入爭用
在彙集索引表上,若是彙集索引的首列(leading key)是一個有序增加的列(如自增列和Datetime),則可能致使Latch爭用。
這種場景中表除了在歸檔時不多執行刪除和更新操做;一般表很大,行較窄。
插入一條數據到索引中的步驟:
1. 檢索B樹,定位到新行將要被存儲的頁
2. 在此頁上加上排他Latch(PAGELATCH_EX),避免其它操做同時修改此頁。而後在全部非葉級頁中加上共享Latch(PAGELATCH_SH).
有時也會在非葉頁獲取排他Latch,如頁拆分時直接受到影響的非葉頁。
3. 向日志文件寫一條記錄,表示此行已經被修改
4. 向頁中寫中新行並標記爲髒頁
5. 釋放全部Latch.
若是表的索引是基於有序增加的鍵,則新行都會被插入到B樹的最後一頁,直到這頁存滿。高併發負載下,就會致使彙集和非彙集索引B樹中最右頁被爭用。一般這種併發爭用發生在以插入操做爲主且頁密度較大的索引上。經過sys.dm_db_index_operational_stats的能夠觀察到頁Lath爭用和B樹中非葉級頁Latch爭用的狀況。
舉個例子:
線程A和線程B同時向尾頁(好比1999頁)插入新行。邏輯上,二者均可以同時獲取尾頁中對應行的行級排他鎖。可是,爲維護內存完整性一次只能夠有一個線程獲取到頁上排他Latch。假設A獲取EX Latch,則B就須要等待。則B就會在 sys.dm_os_waiting_tasks表現出等待類型爲PAGELATCH_EX的等待。
具備非彙集索引的小表上進行隨機插入致使的Latch爭用
將表當作臨時隊列結構使用,一般會出現這種場景。在知足下面的條件時,就可能出現Latch爭用(包括EX和SH):
高併發的INSERT,DELETE,UPDATE和SELECT操做
頁密度較大,行較窄
表的行數較少,因此B樹也較淺,索引深度在2~3級。
較淺的B樹上執行大量隨機的INSERT很可能致使頁拆分。執行頁拆分時,須要在B樹全部層級上獲取SH Latch和在數據發生修改的全部頁上獲取EX Latch。在很是高併發的INSERT和DELETE狀況,還很可能致使B樹ROOT頁發生拆分。ROOT頁拆分會致使Non-Buffer Latch:ACCESS_METHODS_HBOT_VIRTUAL_ROOT。
能夠經過下面的腳本觀察表的索引深度:
select o.name as [table], i.name as [index],
indexProperty(object_id(o.name), i.name, 'indexDepth')
+ indexProperty(object_id(o.name), i.name, 'isClustered') as depth, --clustered index depth reported doesn't count leaf level
i.[rows] as [rows], i.origFillFactor as [fillFactor],
case (indexProperty(object_id(o.name), i.name, 'isClustered'))
when 1 then 'clustered'
when 0 then 'nonclustered'
else 'statistic'
end as type
from sysIndexes i
join sysObjects o on o.id = i.id
where o.type = 'u'
and indexProperty(object_id(o.name), i.name, 'isHypothetical') = 0 --filter out hypothetical indexes
and indexProperty(object_id(o.name), i.name, 'isStatistics') = 0 --filter out statistics
order by o.name
PFS頁的Latch爭用
這屬於分配瓶頸的一種狀況。PFS記錄着數據頁的空間使用狀況。PFS頁上使用1個字節(Byte)表示一個頁的使用狀況。一個PFS頁能夠表示8088個數據頁,因此每8088個數據頁就會有一個PFS頁。一個數據文件的第二個頁就是PFS頁(PageID=2)。
當須要分配空間給新對象或者數據操做,SQL Server會在PFS頁上獲取SH Latch查看目標區區是否有可用的頁。若是有,則會在PFS上獲取 UP Latch,並更新對應頁的空間使用信息。相似的過程也會發生在SAM,GSAM頁上。在多CPU的系統上,文件組中只有不多的數據文件,過多的PFS頁請求,則可能致使Latch爭用。邊種場景在Tempdb中會相對較常見些。
當在Tempdb的PFS或者SGAM頁上出現較多PATHLATCH_UP等待時,能夠採起下面的方法消除Latch爭用:
增長Tempdb的數據文件個數,個數=CPU的核心數
啓用跟蹤標記TF 1118
Tempdb中的表值函數致使的Latch爭用
這個發生的緣由跟PFS的Latch爭用同樣。每次調用多語句的表值函數總要建立和刪除表變量,當在一個查詢的屢次引用多語句的表值函數時就可能會生不少的表變量建立和刪除操做,從而致使Latch爭用。
解決不一樣模式下的Latch爭用
使用已有的列將數據分佈到全部的索引鍵區間
在銀行ATM系統場景中考慮使用ATM_ID將對交易表的INSERT操做分佈到全部鍵值區間。由於一個用戶同一時間只能在使用一臺ATM。這樣就將銷售系統的場景中,能夠考慮使用Checkout_ID列或者Store_ID列。這種方式須要使用惟一複合索引。這個複合索引的首鍵一般使用被選定的標識列或者某些列通過Hash的計算列,而後再結合其它列共同組成。使用Hash計算列會較優,由於標識列的惟一值會太多,形成鍵值區間過多,也會讓表數據的物理結構變差。雖然Hash計算列的索引鍵值區間較少,但對於分散INSERT負載減小Latch爭用而言,已經足夠了。好比銷售系統中,可使用Store_ID對CPU核數取模作爲Hash值。
這種方式會加大索引碎片,下降範圍掃描的性能。可能還須要修改應用架構,例如語句的WHERE須要根據新的索引結構進行調整。
示例:在一個32核心的系統中的交易表
原表結構:
create table table1
(
TransactionID bigint not null,
UserID int not null,
SomeInt int not null
)
go
alter table table1
add constraint pk_table1
primary key clustered (TransactionID, UserID)
go
方式1.:使用UserID作爲索引首鍵,將INSERT操做分佈到全部數據頁上。須要注意的是:索引修改後,全部SELECT的WHERE等式中須要同時指定UserID和TransactionID。
create table table1
(
TransactionID bigint not null,
UserID int not null,
SomeInt int not null
)
go
alter table table1
add constraint pk_table1
primary key clustered (UserID, TransactionID)
go
方式2:使用TransactionID對CPU核數取模作爲索引首鍵,將INSERT操做較均勻分佈到表上。
create table table1
(
TransactionID bigint not null,
UserID int not null,
SomeInt int not null
)
go
-- Consider using bulk loading techniques to speed it up
ALTER TABLE table1
ADD [HashValue] AS (CONVERT([tinyint], abs([TransactionID])%(32))) PERSISTED NOT NULL
alter table table1
add constraint pk_table1
primary key clustered (HashValue, TransactionID, UserID)
go
使用GUID列作爲索引首鍵
這個我本人很是不認同,因此也不想分析了,只是爲了維護完整性加在這裏。併發負載和Latch爭用嚴重到須要GUID這種極端的方式來分散負載時,分表、分區或者使用Redis類產品纔是更好的方法。
使用計算列對錶進行Hash分區
表分區能減小Latch爭用。使用計算列對錶進行Hash分區,通常的步驟:
新建或者使用現有的文件組承載各分區
若是使用新文件組,須要考慮IO子系統的優化和文件組中數據文件的合理佈局。若是INSERT負載佔比較高,則文件組的數據文件個數建議爲物理CPU核心數的1/4(或者1/2,或者相等,視狀況而定)。
使用CREATE PARTITION FUNCTION將表分紅N個分區。N值等於上一步的數據文件的個數。
使用CREATE PARTITION SCHEME綁定分區函數到文件組,而後再添加一個smallint或者tinyint類型的Hash列,再計算出合適的Hash分佈值(例如HashBytes值取模 或者取Binary_Checksum值)。
示例代碼:
--Create the partition scheme and function, align this to the number of CPU cores 1:1 up
to 32 core computer
-- so for below this is aligned to 16 core system
CREATE PARTITION FUNCTION [pf_hash16] (tinyint) AS RANGE LEFT FOR VALUES
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
CREATE PARTITION SCHEME [ps_hash16] AS PARTITION [pf_hash16] ALL TO ( [ALL_DATA] )
-- Add the computed column to the existing table (this is an OFFLINE operation)
-- Consider using bulk loading techniques to speed it up
ALTER TABLE [dbo].[latch_contention_table]
ADD [HashValue] AS (CONVERT([tinyint], abs(binary_checksum([hash_col])%(16)),(0)))
PERSISTED NOT NULL
--Create the index on the new partitioning scheme
CREATE UNIQUE CLUSTERED INDEX [IX_Transaction_ID]
ON [dbo].[latch_contention_table]([T_ID] ASC, [HashValue])
ON ps_hash16(HashValue)
分區後,邏輯上INSERT仍然集中到表尾部,可是Hash分區將INSERT分散到各分區的B樹的尾部。因此能減小Latch爭用。
使用Hash分區消除INSERT的Latch爭用,須要權衡的事項:
一般SELECT語句的查詢謂詞部分須要修改,使其包含Hash分區列。這會致使查詢計劃的分區消除不可用。
某些其它查詢(如基於範圍查詢的報表)也不能使用分區消除。
當分區表與另外一個表JOIN時,若是使用分區消除,則另外一個表須要在一樣的鍵上實現Hash分區而且Hash鍵須要包括在JOIN條件裏。
Hash分區會使滑動窗口歸檔和分區歸檔功能不可用。
總結
1. 本文以SQLCAT的Latch Contention白皮書爲基礎,結合Paul Randal關於Latch的博文,以及本人的經驗而成。
2. Buffer Latch Contention較容易定位和處理。Non-Buffer Latch 是較爲棘手的,由於太少關於此類型Latch的說明資料,形成有時定位到類型,也不知道它是什麼意思,無從下手。
本文出自 「Joe TJ」 博客,請務必保留此出處http://joetang.blog.51cto.com/2296191/1696976