2017年2月25日 星期六

什麼是Latch(閂鎖)

前幾年,我一直搞不清楚「什麼是Latch?」,只知道Latch是一種「輕量級的鎖」「用於處理同步的資源」等,當時我心裡有很大的疑問:
「輕量級是什麼?」
「為什麼需要Latch?」
「現有的Lock機制無法涵蓋Latch的功用嗎?」


我們先來看看微軟官方文件對於Latch的描述:
=========================================
Latches are lightweight synchronization primitives that are used by the SQL Server engine to guarantee consistency of in-memory structures including; index, data pages and internal structures such as non-leaf pages in a B-Tree. SQL Server uses buffer latches to protect pages in the buffer pool and I/O latches to protect pages not yet loaded into the buffer pool. Whenever data is written to or read from a page in the SQL Server buffer pool a worker thread must first acquire a buffer latch for the page..
Latches are internal to the SQL engine and are used to provide memory consistency, whereas locks are used by SQL Server to provide logical transactional consistency

試著用goole翻譯過後,再修飾一下
==========================================
Latch是輕量級同步原語,由SQL Server引擎使用以保證記憶體中結構的一致性,包括; 索引,數據頁和內部結構,例如B樹中non-leaf頁。SQL Server使用buffer latches來保護緩衝區頁面,使用I / O Latches來保護尚未加載到緩衝區中的頁面。 每當數據寫入或讀取SQL Server緩衝池中的頁面時,工作線程必須首先獲取頁面的buffer latches..
LatchSQL引擎的內部,用於提供記憶體一致性,而Lock則被SQL Server用來提供邏輯事務一致性。

再引用國外網友的其它說明
========================
A latch is a lightweight synchronization object used by the Storage Engine of SQL Server to protect internal memory structures that can’t be accessed in a true multi-threaded fashion
LatchSQL Server存儲引擎使用的輕量級同步物件,在多線程存取中用於保護記憶內部結構不能被同時存取
...
.....
......
呵呵~~我不曉得有沒有人能懂,反正當時我是一頭霧水的~~~
感覺跟Lock好像一樣啊~!為什麼還要多搞一個Latch出來呢!

有篇國外網友寫的評論寫道:
=========================
latches is a very broadbased discussion and sometimes very difficult to explain .
Latch是一個非常廣泛的基礎討論,有時非常難去解釋。

一段時間以來,我查了很多資料,但就是一些零散的概念,始終也沒能把Latch真的弄..或許真的很難解釋吧…,在一番努力沒結果後,心中的求知慾也就隨著時間淡去了。

近年,我開始鑽研SQL SERVER底層的東西,有次在分析page的結構的時候,才恍然覺得「我好像懂得Latch」,之前對於Latch的一些零散的概念,在心裡自動組成了起來。

要知道什麼是Latch,最好得先對page的結構及其資料異動方式有初步的瞭解。

一個8Kpage,前96bytes稱為「page -header(頁頭)」。頁頭中,存放著這個page的一些資訊(例如:這頁是存放哪個物件的、剩餘可用空間、最後異動這個pageLSN、有幾筆資料在裡面)。
在頁頭眾多的資訊中,有一個名為「m_freeData」的值,詳見下圖:









圖為一個page headerdbcc的顯示結果,其中有一個值「m_freeData」,這是指這個page的最後一筆紀錄結尾+1的位址,換句話說,也就是「當有下一筆紀錄新增進來時,新資料將會從m_freeData這個位址開始往下存放」。
註:SQL SERVER會將所用到的頁面載入記憶體中(buffer pool),所有頁面異動都將會在記憶體中完成後,再寫回檔案中。

因此,系統在對page寫入資料前,必需先取得目標pagem_freeData的值,然後才能對這個page寫入資料。寫入完成後,還要再去異動頁頭的m_freeData為下一個新的值,以備後續的資料寫入。

試想,如果有多個threads同時向系統要求寫入資料,若允許它們同時進行(concurrency),那麼就可能取得相同的m_freeData的值,進而將資料新增到同一個位址,就會造成資料彼此複蓋,形成所謂的「遺失更新」(Lost Update)。

從單一page的角度來看,在這樣的頁面結構設計下,所有對單一page的異動,都必需是循序性(serialized)

要確保這種循序性,資料庫藉助「鎖」的機制來管控。要有一種鎖定機制,當對page寫入資料前,先對這個page取得獨佔鎖,以防止它人讀取及異動,等到寫完後再「解鎖」。

很自然的,我們會立刻聯想到Lock。這不就是Lock在做的事嗎?

但請再細想一下…….Lock的設計,是屬於交易層級的,Lock是在整個交易中有效。主要是為了控制交易資料的一致性。舉個簡單的例子:
begin tran
  insert into [TABLE_B] with(tablock) select * from [TABLE_A]
commit
這是一個簡單的insert.. select..指令,假設TABLE_A1萬筆,執行指令後,會將這1萬筆資料新增進TABLE_B,這樣的語句,有可能會引發平行處理(multi-threads)。

我們回想一下前面page表頭m_freeData的情境。配合上面insert…select的例子,在這個例子中,當multi-threads同時併發,假設有4threads(1~4,thread 0通常做為協調線程),若每個thread分配到insert 2500筆的資料。那麼,thread 1 ~ thread 4這四個threads對同一個page的寫入必需是循序的。假設thread 1先取得對這個page的獨佔鎖,其餘thread 2,3,4就必需排隊等待。
thread 1釋放鎖定後,後續的thread 接著取得獨佔鎖以寫入資料,以此類推,直至所有threads寫入完畢。
在整個insert的過程中,由4threads共同完成,並使用「鎖」來管理,以確保不會有同時寫入的情況。全程中,這個頁面「鎖」被掛起及釋放,共四次。

若是用Lock,那麼這個Ex-Lock(獨佔鎖)的掛鎖及解鎖就要多次,此時的交易尚未結束,基於交易一致性,Ex-Lock在整個交易中持有,Ex-Lock是不能在交易途中放掉的。因此,像這樣的動作,顯然已經不是Lock機制所能或所應管控的。

面對這種情境,需要有另一種鎖,它跟Lock類似,也是維持一致性,甚至鎖定的類型也相似(如SHEXUP等)。但這種鎖,主要是針對PageI/O進行循序性的管控。避免併發的處理程序同時存取相同的頁面。

這就是Latch~~~~

Lock用來維持交易的一致性,Latch則用來維持Page 資料的一致性,這兩者的概念相同,但應用的層次則不相同。

Latch不是為「transaction(交易)」而存在,它沒有「交易」的概念。Latch只是為了維持底層page資料的一致性。它不像Lock會在整個交易中持有,Latch是每當進行page I/O時持有(確保循序性),I/O結束,就立即放開。因此,它是屬於較底層的,它鎖定的影響,要遠小於Lock
一個page大小是8K,即使是從頭寫到尾,也只是8K的資料量,不像是Lock,一句SQL語法所產生的Lock可能就影響成千上萬個page的資料。所以跟Lock比起來,Latch真的算是「輕量級的鎖」。

除了以上所說的原因之外,SQL SERVER7.0版開始,同時引進了「row-level Locking」(行鎖)。在單一page的存取異動上,行鎖只能鎖住某一筆record。如果遇到併發式的存取,Lock將無法達成「對單一page的異動,必需是循序性的的規則維持。

現在,再回頭看看文章開頭的問題,或許能更清楚的了解..
「輕量級是什麼?」
「為什麼需要Latch?」
  「現有的Lock機制無法涵蓋Latch的功用嗎?」









==========================================================
Latch是由SQL Engine 內部控制,我們無法對它進行任何設置。也無法在SQL語法中對Latch進行任何的暗示(Hint)。

Latch mode
SQL SERVERLatch共有五種不同的模式
n   KP(Keep):用於確保結構不會被銷毀。
n   SH(Share):用於讀取頁面資料。
n   UP(Update):用於寫入系統分配頁面。
n   EX(Exclusive):用於寫入資料頁面。
n   DT(Destroy):銷毀頁面時使用。

各種Latch模式的相容性如下列表

KP
SH
UP
EX
DT
KP
Y
Y
Y
Y
N
SH
Y
Y
Y
N
N
UP
Y
Y
N
N
N
EX
Y
N
N
N
N
DT
N
N
N
N
N

Latch Wait Type
SQL SERVER會負責追踪所有的等待資訊,我們可以透過動態管理視圖(DMV) sys.dm_os_wait_stats來查詢所有的wait資訊。

Latch共有三個種類
(1) Buffer latch
用於維持buffer pool中的頁面一致性,當buffer poollatch發生等待的情況時,它被紀錄在sys.dm_os_wait_statswait_type為「PAGELATCH_*
(2) Non-buffer latch
用於維持除了buffer pool之外的在記憶體中的頁面一致性,如CLR,當此種類型的Latch發生等待的情況時,它被紀錄在sys.dm_os_wait_statswait_type為「LATCH_*
(3) IO-latch
用於當頁面從磁碟中被讀取並寫入到buffer pool的這個過程。當發生等待的情況時,它被紀錄在sys.dm_os_wait_statswait_type為「PAGEIOLATCH_*
註:
如果看到明顯的PAGEIOLATCH等待,這表示著SQL Server正在等待I / O子系統。 雖然一定量的PAGEIOLATCH等待是正常的行為,但如果平均PAGEIOLATCH等待時間一直在10毫秒(ms)以上,應該調查I / O子系統為什麼處於壓力下。

~~~~Latch的基本說明就到此了~~~~

從前面的說明中,我們知道單一page的異動,是無法同時(concurrency)進行的,它必需是循序性的。

~~~這又會導致一個問題~~~

我們知道每個DB的資料,都是存放在實體的檔案之中(如mdf / ndf),而每個資料檔都會有一些「系統頁面」,如GAMSGAMPFS…等。
這些系統頁面中,紀錄了這個資料檔的空間配置等資訊,每當有資料異動後,空間運用有所變化,這些系統頁面,也需要跟著異動,特別是在使用空間變化幅度大的DB,系統頁面的異動會更為頻繁。
一個GAM/SGAM頁,所能控管的檔案大小大約4G左右,如果你的資料檔大小不到4G,那麼代表這個資料檔只會有一頁GAM/SGAM。另外,一個PFS頁,則可管理8088pages。所以你的資料檔會有幾個GAMSGAMPFS等頁面,可以自行計算。

~~那麼問題來了!

對單一page的異動,必需是循序性的」。也就是說,有多個threads可能可以同時分別存取不同的data page,你改你的pgae,我改我的page,這是可以併發的。
但在這些異動完後,最後要去變更系統頁面資訊內容時(如GAMPFSSGAM),因為系統頁面或許只有一頁,原本的併發處理,到此它就會變成循序的了,要排隊。(就像大賣場,任由許多人一起逛,但結帳出口只有一個)。

在一個空間使用量異動頻繁的DB中(如經常寫入大量資料後又刪除的動作),因空間異動的資訊要寫入GAM/SGAM/PFS等系統頁,最後將會在GAM/SGAM/PFS這邊產生阻塞。這種情況會造成Latch的競爭(等待),也被稱為Latch Contention

Latch contention可能發生在任何多核心的系統(multi-core system), 在16 core(含)以上的系統是常見的,它發生的頻率會隨著core的增加而增加。

面對這樣的問題,最簡單的方法就是「增加資料檔的數目」。也就是DB會有多個mdf /ndf檔。這樣各個資料檔都有自己的GAMSGAMPFS頁,目地在於儘可能增加GAM/SGAM/PFS等系統頁面的並行度,當在不同資料檔案中的空間異動,Latch就不會阻塞在同一個GAM/SGAM/PFS上。

或許你已經想到了,在SQL SERVER中,空間振盪幅度最大的,莫過於tempdb

tempdb的實際使用空間的變化,隨著SQL語法(如join/group by/order by..等)或使用者建立的暫存表,它的空間用量的起伏是劇烈的。也因此tempdb的系統頁面可以說無法避免的會發生latch contention的情況。

這也是為什麼微軟官網提到tempdb資料檔的數目建議跟CPU core一致(主要原因是考量最大可能的並行程度,併發執行緒的數量 <= CPUcorenumber of concurrent threads is <= number of COREs/CPUs )。





另外一份微軟的文件中也提到:






另外,在SQL SERVER 2016安裝過程中,安裝程式預設的tempdb資料檔的數目,是「CPU core的數目 vs 8」的最小值。



















我們可以使用下列指令,檢測tempdb「目前」是否遭遇latch contention
select session_id, wait_duration_ms,resource_description
from  sys.dm_os_waiting_tasks
where  wait_type like 'PAGE%LATCH_%'
 and resource_description like '2:%' 

一般情況下,如果你看到 resource_description2:%:1」「2:%:22:%:3」,表示tempdb的系統頁面有Latch爭用。
2:%:1」這種表示法,以冒號分隔成三部份:
第一部份是DB_ID,一般tempdbID2
第二部份是file_ID,也就是你資料檔的ID(可查詢sys.sysfiles)。
第三部份是page_ID

觀察目前系統的latch wait狀態,如果系統頁面一直發生wait的話,那麼可以依前面的建議,增加tempdb的資料檔的數量,如果是SGAM頁存在瓶頸,也可以在SQL SERVER啟動參數,加入trace flag 1118以解決這個問題
註:trace flag 1118主要作用是新增頁面避免使用混合區,改用統一區。SGAM正是管理混合區空間配置的頁面。

當資料檔夠大時,系統頁可能有多頁,除了第1,2,3頁之外。要識別PFS,可用頁號除以8088,若整除即是。GAM可以用511232來除。SGAM可以將pageID-1後再用511232來除。

呵呵~~
竟講到tempdb的優化了,這有點離題了。先到此,不再細說了

回到正題~~

為避免Latch Contention,可以將資料檔分成多個(不超過CPU Core數),以增加系統頁面(GAM/SGAM/PFS)的並行度。

至於Log檔(ldf),交易紀錄因為有順序性,交易的順序是它賴以還原資料的基礎。它本來就是一種循序性的寫入,無法平行處理。即使你增加多個ldf檔,也不能加大它的平行處理程度(Paul S Randal曾撰文指反而有礙效能),交易紀錄檔從來不會有平行處理。所以交易紀錄檔不需要有多個檔案。

寫在最後
Latch雖然無法被設定,但可以被監控。我們可以透過查詢sys.dm_os_wait_stats來取得系統為我們蒐集的等待資訊。或者透過sys.dm_os_waiting_tasks來取得活動中的等待訊息。

Latch Contention在多核心的系統是無法避免的,相信大多數人在查看wait event時,經常會看到LATCH_*PAGEIOLATCH_*PAGELATCH_*…等的Latch wait type。它雖然可以被視為是必然的,但如果它持續過高,我們也該分析一下其背後的原因,並做出一些適當的處置。

Latch這一議題中,還有一些很有趣的情境值討探討,有些甚至顛覆我長久以來奉為「定律」想法。例如從latch的角度來看,最好是用不連續的隨機值(如GUIDprimary key..
(下一篇文章我會想接著談談這個)

分析wait eventDB效能調校的重要環節。瞭解那些居高不下的wait-type的涵義,是做出正確處置的基礎。
在以往似懂非懂的時候,我經常是人云亦云。自從逐漸深入了解SQL SERVER的一些底層的東西後,之前零散的觀念,會自然的拼湊起來。我發現對於很多觀念變得清晰了,對很多事件也開始有些自己的見解。

這篇主要是想將Latch的觀念釐清,避免將LatchLock混淆了。至於沒談完的Latch wait event及其解法,之後再找時間與大家分享。

以上是個人的心得,如果有錯誤的地方,請各位大大不吝指導。

6 則留言:

  1. 例如從latch的角度來看,最好是用不連續的隨機值(如GUID)當primary key 。
    這點是想讓執行緒在存取資料時,盡量不在同一個page上做操作嗎@@?
    感覺是用空間的碎片化來換掉鎖的等待呢。
    不過碎片化我記得如果在SSD的儲存裝置上是不會發生的 (有錯請指止)
    這樣說不定IO效能會大大提升,感謝前輩分享 ^_^

    回覆刪除
  2. Hi Jaja
    是的,使用GUID當leading key column的好處,就是insert時可以分散到不同的page,可以降低Latch Contention,特別是大量insert時。當然也有壞處,就是您所說的「碎片」。
    有關GUID是否能用來做為key,這還真是個爭議的議題,並無確切的定論。做為SQL DBA,我認為我至少要瞭解它們爭議的點是什麼?各有什麼利弊?
    至於您提到SSD的儲存裝置不會發生碎片...,這我倒是沒有聽過。不過從SQL SERVER的角度來看「碎片應與實體儲存的媒體型態」沒有關係才對。
    我自己的電腦就是SSD,還是一樣會有碎片。我想,所謂的SSD不會有碎片,應該是指SSD並不受碎片影響。因為碎片主要是不連續的頁面,在以往傳統硬碟上,這種不連續會造成磁頭讀取的次數增加。但在現在的SSD,由於存取方式的不同,沒有所謂磁頭讀取的問題,或許對SSD來說,連續或不連續的頁面讀取,是一樣的事.

    謝謝,希望有空能經常討論...互相交流...

    回覆刪除
    回覆
    1. 當時只記得有這個資訊,但沒有細探,再次感謝您的分享。 有機會也希望可以互相交流一下唷 :D

      刪除
  3. 很棒的分享!!! 謝謝!!

    回覆刪除