數據庫的樂觀鎖和悲觀鎖並非真實的鎖

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#開局","title":null}},{"type":"text","text":"開局"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們平時編寫程序的時候,有很多情況下需要考慮線程安全問題,一個全局的變量如果有可能會被多個同時執行的線程去修改,那麼對於這個變量的修改就需要有一種機制去保證值的正確性和一致性,這種機制普遍的做法就是加鎖。其實也很好理解,和現實中一樣,多個人同時修改一個東西,必須有一種機制來把多個人進行排隊。計算機的世界中也是如此,多個線程乃至多個進程同時修改一個變量,必須要對這些線程或者進程進行排隊。數據庫的世界亦是如此,多個請求同時修改同一條數據記錄,數據庫必須需要一種機制去把多個請求來順序化,或者理解爲同一條數據記錄同一時間只能被一個請求修改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鎖是數據庫中最爲重要的機制之一,無論平時寫的select語句,還是update語句其實在數據庫層面都和鎖息息相關。如果沒有鎖機制,操作數據的時候可能會發生以下情況:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"更新丟失:多個用戶同時對一個數據資源進行更新,必定會產生被覆蓋的數據,造成數據讀寫異常。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"不可重複讀:如果一個用戶在一個事務中多次讀取一條數據,而另外一個用戶則同時更新啦這條數據,造成第一個用戶多次讀取數據不一致。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"髒讀:第一個事務讀取第二個事務正在更新的數據表,如果第二個事務還沒有更新完成,那麼第一個事務讀取的數據將是一半爲更新過的,一半還沒更新過的數據,這樣的數據毫無意義。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"幻讀:第一個事務讀取一個結果集後,第二個事務,對這個結果集經行增刪操作,然而第一個事務中再次對這個結果集進行查詢時,數據發現丟失或新增。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#數據管理角度","title":null}},{"type":"text","text":"數據管理角度"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在數據庫管理的角度或者數據行的角度來說,數據庫鎖可以分爲共享鎖和排它鎖,這是面試過程中經常被提及的兩種類型。本質其實很簡單,站在數據的角度來看,如果數據當前正在被訪問,下一個訪問的請求該如何處理?和計算機二進制一樣,無非就是允許被訪問和不允許訪問兩種狀態。"}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#共享鎖","title":null}},{"type":"text","text":"共享鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"共享所被稱爲讀鎖或者S鎖,就像以上所述,共享鎖在新請求訪問一個數據的時候,如果是讀請求則允許,如果是寫(刪改)請求,則不允許。由於共享鎖允許其他的讀操作,所以通常情況下共享鎖只應用於select操作,如果一個update或者delete操作應用共享鎖會發生很嚴重的數據不一致情況。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#獨佔鎖","title":null}},{"type":"text","text":"獨佔鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獨佔鎖也被稱爲排它鎖或者X鎖,相對於共享鎖,獨佔鎖採用的態度比較堅決,一旦數據被獨佔鎖鎖定,其他任何請求(包括讀操作)都必須等待獨佔鎖的釋放纔可以繼續,只有當前鎖定數據的請求才可以修改讀取數據。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#更新鎖","title":null}},{"type":"text","text":"更新鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當數據庫準備更新數據時,它首先對數據對象作更新鎖鎖定,這樣數據將不能被修改,但可以讀取。等到確定要進行更新數據操作時,他會自動將更新鎖換爲獨佔鎖,當對象上有其他鎖存在時,無法對其加更新鎖。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#意向鎖","title":null}},{"type":"text","text":"意向鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單來說就是給更大一級別的空間示意裏面是否已經上過鎖。例如表級放置了意向鎖,就表示事務要對錶的頁或行上使用共享鎖。在表的某一行上上放置意向鎖,可以防止其它事務獲取其它不兼容的的鎖。意向鎖可以提高性能,因爲數據引擎不需要檢測資源的每一列每一行,就能判斷是否可以獲取到該資源的兼容鎖。意向鎖包括三種類型:意向共享鎖(IS),意向排他鎖(IX),意向排他共享鎖(SIX)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際應用中,站在數據的角度可以看出,數據只允許同時進行一個寫操作"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#顆粒度角度","title":null}},{"type":"text","text":"顆粒度角度"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鎖用來對數據進行鎖定,我們可以從鎖定對象的粒度大小來對鎖進行劃分,分別爲行鎖、頁鎖和表鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"行級鎖是數據庫中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少數據庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。特點:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"表級鎖是數據庫中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少。特點:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的概率最高,併發度最低。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"頁級鎖是數據庫中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。特點:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同數據庫支持的鎖力度不同,甚至同一種數據庫不同的引擎支持的鎖力度都不同,如下表所示(來源於網絡) "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b5/b5f1878781836f400aa668c08b3659e8.png","alt":"image","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏要強調一點,無論什麼數據庫對數據加鎖,都需要資源的消耗,因此鎖的數量其實是有上限的,當鎖數量到達這個上限會自動進行鎖力度的升級,用更大力度的鎖來代替多個小力度的鎖。"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#樂觀鎖和悲觀鎖","title":null}},{"type":"text","text":"樂觀鎖和悲觀鎖"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#樂觀鎖","title":null}},{"type":"text","text":"樂觀鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"樂觀鎖認爲一般情況下數據不會造成衝突,所以在數據進行提交更新時纔會對數據的衝突與否進行檢測。如果沒有衝突那就OK;如果出現衝突了,則返回錯誤信息並讓用戶決定如何去做。類似於 SVN、GIt這些版本管理系統,當修改了某個文件需要提交的時候,它會檢查文件的當前版本是否與服務器上的一致,如果一致那就可以直接提交,如果不一致,那就必須先更新服務器上的最新代碼然後再提交(也就是先將這個文件的版本更新成和服務器一樣的版本)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"樂觀鎖是一種程序的設計思想,通過一個標識的對比來決定數據是否可以操作,現在普遍的做法是給數據加一個版本號或者時間戳的方式來實現樂觀鎖操作過程: 在表中設計一個版本字段 version,第一次讀的時候,會獲取 version 字段的取值。然後對數據進行更新或刪除操作時,會執行UPDATE ... SET version=version+1 WHERE version=version。此時如果已經有事務對這條數據進行了更改,修改就不會成功。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#悲觀鎖","title":null}},{"type":"text","text":"悲觀鎖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每次獲取數據的時候,都會擔心數據被修改,所以每次獲取數據的時候都會進行加鎖,確保在自己使用的過程中數據不會被別人修改,使用完成後進行數據解鎖。由於數據進行加鎖,期間對該數據進行讀寫的其他線程都會進行等待。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"link","attrs":{"href":"#總結","title":null}},{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無論是樂觀鎖和悲觀鎖,並非是數據庫自身持有的鎖類型(雖然悲觀鎖形式上很像獨佔鎖),而是程序設計的一種思想,是一種類似數據庫鎖機制保護數據一致性的策略。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"悲觀鎖比較適合寫入操作比較頻繁的場景,如果出現大量的讀取操作,每次讀取的時候都會進行加鎖,這樣會增加大量的鎖的開銷,降低了系統的吞吐量。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"樂觀鎖比較適合讀取操作比較頻繁的場景,如果出現大量的寫入操作,數據發生衝突的可能性就會增大,爲了保證數據的一致性,應用層需要不斷的重新獲取數據,這樣會增加大量的查詢操作,降低了系統的吞吐量。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#寫在最後","title":null}},{"type":"text","text":"寫在最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"程序編寫過程中,操作數據無論採用哪個類型的鎖,都需要注意死鎖的發生,一個死鎖有可能對整個應用是致命的。死鎖的本質是對資源競爭的一種失敗表現,所以sql語句的編寫過程中對於多表的操作最好採用一致的順序來進行,另外一個種極端的方式可以一次性鎖定所有資源,而非逐步來鎖資源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/gQKStRKyHB_KOuDjyyodUw","title":null},"content":[{"type":"text","text":"點擊這裏查看更多精彩內容"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章