1. 程式人生 > >程式設計師過關斬將--資料庫的樂觀鎖和悲觀鎖並非真實的鎖

程式設計師過關斬將--資料庫的樂觀鎖和悲觀鎖並非真實的鎖

菜菜哥,告訴你一個訊息

你有男票啦?

非也非也,我昨天出去偷偷面試,結果又掛了

哦,看來公司是真的不想讓你走呀

面試官讓我說一下樂觀鎖和悲觀鎖,我沒回答上來,回來之後我查了,資料庫沒有這兩種鎖呀

瞭解這兩種鎖之前,我覺得你需要先了解一下資料庫的鎖機制


開局

我們平時編寫程式的時候,有很多情況下需要考慮執行緒安全問題,一個全域性的變數如果有可能會被多個同時執行的執行緒去修改,那麼對於這個變數的修改就需要有一種機制去保證值的正確性和一致性,這種機制普遍的做法就是加鎖。其實也很好理解,和現實中一樣,多個人同時修改一個東西,必須有一種機制來把多個人進行排隊。計算機的世界中也是如此,多個執行緒乃至多個程序同時修改一個變數,必須要對這些執行緒或者程序進行排隊。資料庫的世界亦是如此,多個請求同時修改同一條資料記錄,資料庫必須需要一種機制去把多個請求來順序化,或者理解為同一條資料記錄同一時間只能被一個請求修改。

鎖是資料庫中最為重要的機制之一,無論平時寫的select語句,還是update語句其實在資料庫層面都和鎖息息相關。如果沒有鎖機制,操作資料的時候可能會發生以下情況:

1. 更新丟失:多個使用者同時對一個數據資源進行更新,必定會產生被覆蓋的資料,造成資料讀寫異常。

2. 不可重複讀:如果一個使用者在一個事務中多次讀取一條資料,而另外一個使用者則同時更新啦這條資料,造成第一個使用者多次讀取資料不一致。

3. 髒讀:第一個事務讀取第二個事務正在更新的資料表,如果第二個事務還沒有更新完成,那麼第一個事務讀取的資料將是一半為更新過的,一半還沒更新過的資料,這樣的資料毫無意義。

4. 幻讀:第一個事務讀取一個結果集後,第二個事務,對這個結果集經行增刪操作,然而第一個事務中再次對這個結果集進行查詢時,資料發現丟失或新增。

資料管理角度

在資料庫管理的角度或者資料行的角度來說,資料庫鎖可以分為共享鎖和排它鎖,這是面試過程中經常被提及的兩種型別。本質其實很簡單,站在資料的角度來看,如果資料當前正在被訪問,下一個訪問的請求該如何處理?和計算機二進位制一樣,無非就是允許被訪問和不允許訪問兩種狀態。

共享鎖

共享所被稱為讀鎖或者S鎖,就像以上所述,共享鎖在新請求訪問一個數據的時候,如果是讀請求則允許,如果是寫(刪改)請求,則不允許。由於共享鎖允許其他的讀操作,所以通常情況下共享鎖只應用於select操作,如果一個update或者delete操作應用共享鎖會發生很嚴重的資料不一致情況。

獨佔鎖

獨佔鎖也被稱為排它鎖或者X鎖,相對於共享鎖,獨佔鎖採用的態度比較堅決,一旦資料被獨佔鎖鎖定,其他任何請求(包括讀操作)都必須等待獨佔鎖的釋放才可以繼續,只有當前鎖定資料的請求才可以修改讀取資料。

更新鎖

當資料庫準備更新資料時,它首先對資料物件作更新鎖鎖定,這樣資料將不能被修改,但可以讀取。等到確定要進行更新資料操作時,他會自動將更新鎖換為獨佔鎖,當物件上有其他鎖存在時,無法對其加更新鎖。

意向鎖

簡單來說就是給更大一級別的空間示意裡面是否已經上過鎖。例如表級放置了意向鎖,就表示事務要對錶的頁或行上使用共享鎖。在表的某一行上上放置意向鎖,可以防止其它事務獲取其它不相容的的鎖。意向鎖可以提高效能,因為資料引擎不需要檢測資源的每一列每一行,就能判斷是否可以獲取到該資源的相容鎖。意向鎖包括三種類型:意向共享鎖(IS),意向排他鎖(IX),意向排他共享鎖(SIX)。

實際應用中,站在資料的角度可以看出,資料只允許同時進行一個寫操作

顆粒度角度


鎖用來對資料進行鎖定,我們可以從鎖定物件的粒度大小來對鎖進行劃分,分別為行鎖、頁鎖和表鎖。

1. 行級鎖是資料庫中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。特點:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。

2. 表級鎖是資料庫中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少。特點:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的概率最高,併發度最低。

3. 頁級鎖是資料庫中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。特點:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般

不同資料庫支援的鎖力度不同,甚至同一種資料庫不同的引擎支援的鎖力度都不同,如下表所示(來源於網路)

這裡要強調一點,無論什麼資料庫對資料加鎖,都需要資源的消耗,因此鎖的數量其實是有上限的,當鎖數量到達這個上限會自動進行鎖力度的升級,用更大力度的鎖來代替多個小力度的鎖。

樂觀鎖和悲觀鎖

樂觀鎖

樂觀鎖認為一般情況下資料不會造成衝突,所以在資料進行提交更新時才會對資料的衝突與否進行檢測。如果沒有衝突那就OK;如果出現衝突了,則返回錯誤資訊並讓使用者決定如何去做。類似於 SVN、GIt這些版本管理系統,當修改了某個檔案需要提交的時候,它會檢查檔案的當前版本是否與伺服器上的一致,如果一致那就可以直接提交,如果不一致,那就必須先更新伺服器上的最新程式碼然後再提交(也就是先將這個檔案的版本更新成和伺服器一樣的版本)


樂觀鎖是一種程式的設計思想,通過一個標識的對比來決定資料是否可以操作,現在普遍的做法是給資料加一個版本號或者時間戳的方式來實現樂觀鎖操作過程:

在表中設計一個版本欄位 version,第一次讀的時候,會獲取 version 欄位的取值。然後對資料進行更新或刪除操作時,會執行UPDATE ... SET version=version+1 WHERE version=version。此時如果已經有事務對這條資料進行了更改,修改就不會成功。

悲觀鎖

每次獲取資料的時候,都會擔心資料被修改,所以每次獲取資料的時候都會進行加鎖,確保在自己使用的過程中資料不會被別人修改,使用完成後進行資料解鎖。由於資料進行加鎖,期間對該資料進行讀寫的其他執行緒都會進行等待。

總結

無論是樂觀鎖和悲觀鎖,並非是資料庫自身持有的鎖型別(雖然悲觀鎖形式上很像獨佔鎖),而是程式設計的一種思想,是一種類似資料庫鎖機制保護資料一致性的策略。

1. 悲觀鎖比較適合寫入操作比較頻繁的場景,如果出現大量的讀取操作,每次讀取的時候都會進行加鎖,這樣會增加大量的鎖的開銷,降低了系統的吞吐量。

2. 樂觀鎖比較適合讀取操作比較頻繁的場景,如果出現大量的寫入操作,資料發生衝突的可能性就會增大,為了保證資料的一致性,應用層需要不斷的重新獲取資料,這樣會增加大量的查詢操作,降低了系統的吞吐量。

寫在最後

程式編寫過程中,操作資料無論採用哪個型別的鎖,都需要注意死鎖的發生,一個死鎖有可能對整個應用是致命的。死鎖的本質是對資源競爭的一種失敗表現,所以sql語句的編寫過程中對於多表的操作最好採用一致的順序來進行,另外一個種極端的方式可以一次性鎖定所有資源,而非逐步來鎖資源。


END