??之前在(十三)事務處理中簡單的介紹了鎖系統,并且介紹了由于并發而產生的種種問題,例如臟讀、幻讀等,因此這里對如何解決這些問題再進行一下補充。
1、悲觀鎖
??在關系型數據庫管理系統里,悲觀并發控制是一種并發控制的方法,它可以阻止一個事務以影響其他事務的方式來修改數據。悲觀鎖需要使用數據庫的鎖機制,可以從字面理解為這種并發方式就是很悲觀,每次調用數據的時候都認為同時會有其他事務在修改數據,因此每次在調用數據前都會先上鎖,這樣可以防止其他事務讀取或修改表中的數據。
??例如:

??此處之所以會出現死鎖,就是因為執行這條語句時已經使用了鎖,
UPDATE tbl_name SET col_name = newValue WHERE id = x;
??因此互相調用相同的字段造成了死鎖。
??悲觀并發控制主要用于數據爭用激烈的環境,以及發生并發沖突時使用鎖保護數據的成本要低于回滾事務的成本的環境中。悲觀并發控制實際上是”先取鎖再訪問”的保守策略,為數據處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓數據庫產生額外的開銷,并且會增加產生死鎖的機會;另外,在只讀型事務處理中由于不會產生沖突,也沒必要使用鎖,這樣做只能增加系統負載,還會降低并行性,因為一個事務如果鎖定了某行數據,其他事務就必須等待該事務處理完才可以進行處理。
2、樂觀鎖
??樂觀鎖相對于悲觀鎖而言,通常會假設多用戶并發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自負責的數據。在提交數據更新之前,每個事務會先檢查在讀取數據后,有無其他事務再次修改了該數據。如果有,那么當前正在提交的事務會進行回滾。可以從字面理解為這種并發方式就是很樂觀,每次調用數據的時候都認為其他事務不會在同時修改數據,因此不會上鎖。
??在處理數據時,樂觀鎖并不會使用數據庫提供的鎖機制,通常樂觀鎖的實現方式是記錄數據版本,即為數據增加一個版本標識,一般是通過為數據庫表增加一個數字類型的 “version” 字段來實現。當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值加1。當提交更新的時候,判斷當前version值是否與之前讀出的version值一致,如果相同,則予以更新,否則認為是過期數據。
??例如:
??假設用戶A和用戶B對同一張數據表的同一個字段記錄值進行修改,此時用戶A和用戶B從該表中讀取的version值為2,用戶A對記錄修改結束后,version值增加1,此時該表的version字段的值是3,而用戶B依然按照version值為2進行操作,不滿足“提交版本必須大于記錄當前版本才能執行更新”的樂觀鎖策略,因此,用戶B的提交被駁回。這樣,就避免了用戶B用基于version值為2的舊數據修改的結果覆蓋用戶A的操作結果的可能。
??樂觀并發控制多數用于數據爭用不大、沖突較少的環境中,此時偶爾回滾事務的成本會低于讀取數據時鎖定數據的成本,因此可以獲得比其他并發控制方法更高的吞吐量。
3、MVCC
??多版本并發控制(Multi-Version Concurrency Control)是為了實現數據庫的并發控制而設計的一種機制。大多數的關系型數據庫都支持MVCC,其突出特點是:讀不加鎖,讀寫不沖突。
??在MVCC中,讀操作可以分成兩類,快照讀和當前讀:
- 快照讀,讀取的是記錄的可見版本(可能是歷史版本,即最新的數據可能正在被當前執行的事務并發修改),不會對返回的記錄加鎖;
- 當前讀,讀取的是記錄的最新版本,并且會對返回的記錄加鎖,保證其他事務不會并發修改這條記錄。
??在MySQL InnoDB中,基本的SELECT操作,如
SELECT * FROM tbl_name WHERE xxxx;
??都屬于快照讀;而屬于當前讀的包含以下操作:
SELECT * FROM tbl_name WHERE xxxx LOCK IN SHARE MODE;(共享鎖)
SELECT * FROM tbl_name WHERE xxxx FOR UPDATE;(排他鎖)
INSERT,UPDATE,DELETE操作(排他鎖)
??可以將MVCC理解為行級鎖的一種妥協,它在許多情況下避免了使用鎖,同時可以提供更小的開銷。根據實現的不同,它可以允許非阻塞式讀,在寫操作進行時只鎖定必要的記錄。
??各個存儲引擎對于MVCC的實現各不相同,下面將通過一個簡化的InnoDB版本的行為來展示MVCC工作原理:
簡單來說,通過為每一行記錄添加兩個額外的隱藏的值來實現MVCC,這兩個值一個記錄這行數據何時被創建,另外一個記錄這行數據何時過期(或者被刪除)。但是InnoDB并不存儲這些事件發生時的實際時間,相反它只存儲這些事件發生時的系統版本號。這是一個隨著事務的創建而不斷增長的數字。每個事務在事務開始時會記錄它自己的系統版本號。每個查詢必須去檢查每行數據的版本號與事務的版本號是否相同。
??以下是在默認隔離級別REPEATABLE READ下,MVCC具體是怎樣實現的:
SELECT:
??InnoDB只查找版本早于(包含等于)當前事務版本的數據行。這保證了不管是事務開始之前,或者事務創建時,或者修改了這行數據的時候,這行數據是存在的;這行數據的刪除版本必須是未定義的或者比事務版本要大,這可以保證在事務開始之前這行數據沒有被刪除。
??符合這兩個條件的行可能會被當作查詢結果而返回。INSERT:
InnoDB為這個新行記錄當前的系統版本號。DELETE:
InnoDB將當前的系統版本號設置為這一行的刪除ID。UPDATE:
InnoDB會寫一個這行數據的新拷貝,這個拷貝的版本為當前的系統版本號。它同時也會將這個版本號寫到舊行的刪除版本里。
4、MVCC與樂觀鎖的區別
??在了解了MVCC的實現機制后可能會感覺與樂觀鎖中使用版本號加鎖有相似之處,實際上MVCC可以保證不阻塞地讀到一致的數據。但是,MVCC并沒有對實現細節做約束,在InnoDB引擎下是只對讀無鎖,寫操作仍是上鎖的悲觀并發控制,這也意味著,InnoDB中只能見到因死鎖和不變性約束而回滾,而不會出現因為寫沖突而回滾的現象;MVCC對數據表中的每行數據只保留一份,在更新數據時上行級鎖,同時將舊版數據寫入undo log;數據表和undo log中行數據都記錄著事務ID,在檢索時,只讀取來自當前已提交的事務的行數據。這種額外的記錄所帶來的結果就是對于大多數查詢來說根本就不需要獲得一個鎖。MVCC只是簡單地以最快的速度來讀取數據,確保只選擇符合條件的行。但其缺點也正是存儲引擎必須為每一行存儲更多的數據,做更多的檢查工作,處理更多的善后操作。
版權聲明:歡迎轉載,歡迎擴散,但轉載時請標明作者以及原文出處,謝謝合作! ↓↓↓