1. 程式人生 > >InnoDB儲存引擎中的鎖

InnoDB儲存引擎中的鎖

下面對innodb儲存引擎中的鎖部分做一個簡單的總結:

1、innodb儲存引擎實現瞭如下的兩種標準的行級鎖

共享鎖(s lock):允許事務讀一行資料 排他鎖(x lock):允許事務刪除或者更新一行資料 注意 s與s,可以同時讀一行資料 s與x則是互斥的、

2、由於innodb不止是能對行上鎖,同時可以對整個表進行上鎖,那麼innodb還支援表級的鎖。

innodb是可以同時允許這個兩個鎖執行的,對錶的鎖成為意向鎖,也分為兩種:

意向共享鎖(IS LOCK):事務獲得一個表的某幾行的鎖 意向排他鎖(IX LOCK):事務要獲得一個表中某幾行的排他鎖

3、innodb中支援的一致性非鎖定讀

它的意思是,如果某一行被使用了排他所,這個時候原則上是不能再去讀該行的資料,但是這樣會阻塞讀操作,innodb提出了這種情況下,可以去讀該行的快照資料,快照資料是舊資料,舊資料是不會被修改的,即不需要加鎖,所以達到了鎖和讀併發操作,但是這個時候讀到的資料是快照資料,不是修改之後的最新資料。

那麼讀快照的資料是哪個版本?

不同的事務隔離級別,那麼讀取的方式越是不同的,並不是所有的讀是一致性讀

在read commited 事務隔離級別,非一致性鎖定讀總是讀最新版本的快照

在repeated read事務隔離級別,非一致性鎖定讀取事務開始時行資料對應的版本

事務隔離性不同的等級,會有不同的一致性等級,不是所有情況都需要保證到強一致性,如果弱一致性讀出來,也可以通過其他方法在去傳送重讀,所以不同的等級對應不同的一致性問題。

4、那麼在innodb中,行鎖具體使用的是哪些鎖的演算法設計

Record lock:單個記錄鎖,即只上鎖一行 Grap lock:間隙鎖,鎖定一個範圍,但不包含技術本身 next-key lock:Record lock+Grap lock,鎖定一個範圍並且鎖定記錄本身

如果使用select對多行進行操作,那麼觸發的就是next-key lock這種鎖演算法 如果使用select對單行進行操作,那麼觸發的就是Record lock這種鎖演算法

5、innodb中鎖是為了很好的讓事務併發,同時可以保證事務的隔離性保證正確,那麼在沒有鎖的情況下,事務操作可能發生哪幾種錯誤呢?

丟失更新:沒有鎖的加入,同時更新一行記錄,前一個更新回丟失 髒讀:一個事務讀到另外一個 事務沒有提交的事務,即讀到了髒資料 不可重複讀:一個事務多次讀到同一個資料,發現對應的值不一致 幻讀:同一個事務中,同一個查詢多次返回的結果不一致

不同的事務級別,對這種錯誤的忍耐級別也是不一樣的。

————————————————————————————————————————————

丟失更新:

兩個事務同時更新一行資料,最後一個事務的更新會覆蓋掉第一個事務的更新,從而導致第一個事務更新的資料丟失,這是由於沒有加鎖造成的;

1. 髒讀 :

髒讀就是指當一個事務正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個資料。

e.g. 1.Mary的原工資為1000, 財務人員將Mary的工資改為了8000(但未提交事務) 2.Mary讀取自己的工資 ,發現自己的工資變為了8000,歡天喜地! 3.而財務發現操作有誤,回滾了事務,Mary的工資又變為了1000 像這樣,Mary記取的工資數8000是一個髒資料。

2. 不可重複讀 :

是指在一個事務內,多次讀同一資料。在這個事務還沒有結束時,另外一個事務也訪問該同一資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第一個事務兩次讀到的的資料可能是不一樣的。這樣在一個事務內兩次讀到的資料是不一樣的,因此稱為是不可重複讀。

e.g. 1.在事務1中,Mary 讀取了自己的工資為1000,操作並沒有完成 2.在事務2中,這時財務人員修改了Mary的工資為2000,並提交了事務. 3.在事務1中,Mary 再次讀取自己的工資時,工資變為了2000 解決辦法:如果只有在修改事務完全提交之後才可以讀取資料,則可以避免該問題。

3. 幻讀 :

是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的資料進行了修改,這種修改涉及到表中的全部資料行。同時,第二個事務也修改這個表中的資料,這種修改是向表中插入一行新資料。那麼,以後就會發生操作第一個事務的使用者發現表中還有沒有修改的資料行,就好象發生了幻覺一樣。

e.g. 目前工資為1000的員工有10人。 1.事務1,讀取所有工資為1000的員工。 2.這時事務2向employee表插入了一條員工記錄,工資也為1000 3.事務1再次讀取所有工資為1000的員工 共讀取到了11條記錄, 解決辦法:如果在操作事務完成資料處理之前,任何其他事務都不可以新增新資料,則可避免該問題

不可重複讀的重點是修改 :

同樣的條件, 你讀取過的資料,再次讀取出來發現值不一樣了

幻讀的重點在於新增或者刪除

同樣的條件, 第 1 次和第 2 次讀出來的記錄數不一樣

6、innodb不會發生鎖升級,1個鎖的開銷與1000000個鎖的開銷是一樣的,因為採用的next-key lock方式去加鎖的。

其他的儲存引擎,當單獨行的鎖的數量表多的時候,可能會發生鎖的升級,即從一個行鎖到頁級的鎖升級為一個表的鎖。

7、資料庫中常說的悲觀鎖和樂觀鎖是什麼呢?

樂觀鎖:

實際不是一種鎖,一種從業務邏輯上層次上,利用程式來處理併發,自己程式的邏輯保證,假定使用者去讀取某一個數據的時候,其他的使用者不會來修改這個資料,然後最後在事務提交的時候,對開始讀的版本和最後的版本進行對比,如果版本不一致,則有其他使用者修改了資料,這個時候會返回提交錯誤的請求並進行回滾操作。

悲觀鎖:

完全依賴上面講到的資料庫的鎖機制來完成的,在資料庫中是可以使用repeatable read的隔離基表來實現悲觀鎖。具體的是當某一個使用者讀取資料的時候,其他使用者可能也會對這個資料進行訪問,所以在讀取的時候會加鎖,其他使用者都不能訪問,只有當自己釋放了之後才能用。

8、悲觀鎖和樂觀鎖的優勢和劣勢?

樂觀鎖:
優勢:

樂觀鎖機制避免了長事務中的資料庫加鎖解鎖開銷,大大提升了大併發量下的系統整體效能表現 所以如果系統的併發非常大的話,悲觀鎖定會帶來非常大的效能問題,所以建議就要選擇樂觀鎖定的方法,樂觀鎖頁比較適合讀比較多的情況。

劣勢:

但是樂觀鎖也存在著問題,只能在提交資料時才發現業務事務將要失敗,如果系統的衝突非常的多,而且一旦衝突就要因為重新計算提交而造成較大的代價的話,樂觀鎖也會帶來很大的問題,在某些情況下,發現失敗太遲的代價會非常的大。而且樂觀鎖也無法解決髒讀的問題

悲觀鎖:
優勢:

能避免衝突的發生,

劣勢:

開銷較大,而且加鎖時間較長,對於併發的訪問性支援不好。

9、對資料庫中樂觀、悲觀鎖,共享、排他鎖,死鎖的總結

併發控制:

事務和鎖的存在都是為了更好的解決併發訪問造成的資料不一致性的的問題 樂觀鎖和悲觀鎖都是為了解決併發控制問題, 樂觀鎖可以認為是一種在最後提交的時候檢測衝突的手段,而悲觀鎖則是一種避免衝突的手段。

樂觀鎖:

是應用系統層面和資料的業務邏輯層次上的(實際上並沒有加鎖,只不過大家一直這樣叫而已),利用程式處理併發, 它假定當某一個使用者去讀取某一個數據的時候,其他的使用者不會來訪問修改這個資料,但是在最後進行事務的提交的時候會進行版本的檢查,以判斷在該使用者的操作過程中,沒有其他使用者修改了這個資料。開銷比較小

樂觀鎖的實現大部分都是基於版本控制實現的, 除此之外,還可以通過時間戳的方式,通過提前讀取,事後對比的方式實現 寫到這裡我突然想起了,java的cuurent併發包裡的Automic 類的實現原理CAS原理(Compare and Swap), 其實也可以看做是一種樂觀鎖的實現,通過將欄位定義為volalate,(不允許線上程中儲存副本,每一次讀取或者修改都要從記憶體區讀取,或者寫入到記憶體中), 通過對比應該產生的結果和實際的結果來進行保證原子操作,進行併發控制(對比和交換的正確性保證 是處理器的原子操作)。

樂觀鎖的優勢和劣勢
優勢:

如果資料庫記錄始終處於悲觀鎖加鎖狀態,可以想見,如果面對幾百上千個併發,那麼要不斷的加鎖減鎖,而且使用者等待的時間會非常的長, 樂觀鎖機制避免了長事務中的資料庫加鎖解鎖開銷,大大提升了大併發量下的系統整體效能表現 所以如果系統的併發非常大的話,悲觀鎖定會帶來非常大的效能問題,所以建議就要選擇樂觀鎖定的方法, 而如果併發量不大,完全可以使用悲觀鎖定的方法。樂觀鎖也適合於讀比較多的場景。

劣勢:

但是樂觀鎖也存在著問題,只能在提交資料時才發現業務事務將要失敗,如果系統的衝突非常的多,而且一旦衝突就要因為重新計算提交而造成較大的代價的話,樂觀鎖也會帶來很大的問題,在某些情況下,發現失敗太遲的代價會非常的大。而且樂觀鎖也無法解決髒讀的問題

同時我在思考一個問題,樂觀鎖是如何保證檢查版本,提交和修改版本是一個原子操作呢? 也就是如何保證在檢查版本的期間,沒有其他事務對其進行操作?

解決方案: 將比較,更新操作寫入到同一條SQL語句中可以解決該問題, 比如

update table1 set a=1, b=2, version = version +1 where version = 1; 

mysql 自己能夠保障單條SQL語句的原子操作性。

如果是多條SQL語句,就需要mySQL的事務通過鎖機制來保障了

悲觀鎖:

完全依賴於資料庫鎖的機制實現的,在資料庫中可以使用Repeatable Read的隔離級別(可重複讀)來實現悲觀鎖,它完全滿足悲觀鎖的要求(加鎖)。

它認為當某一使用者讀取某一資料的時候,其他使用者也會對該資料進行訪問,所以在讀取的時候就對資料進行加鎖, 在該使用者讀取資料的期間,其他任何使用者都不能來修改該資料,但是其他使用者是可以讀取該資料的, 只有當自己讀取完畢才釋放鎖。

悲觀鎖的優勢和劣勢

劣勢:開銷較大,而且加鎖時間較長,對於併發的訪問性支援不好。 優勢 : 能避免衝突的發生

我們經常會在訪問資料庫的時候用到鎖,怎麼實現樂觀鎖和悲觀鎖呢?

以hibernate為例,可以通過為記錄新增版本或時間戳欄位來實現樂觀鎖,一旦發現出現衝突了,修改失敗就要通過事務進行回滾操作。可以用session.Lock()鎖定物件來實現悲觀鎖(本質上就是執行了SELECT * FROM t FOR UPDATE語句)

樂觀鎖和悲觀所各有優缺點,在樂觀鎖和悲觀鎖之間進行選擇的標準是:==發生衝突的頻率與嚴重性。 ==

如果衝突很少,或者衝突的後果不會很嚴重,那麼通常情況下應該選擇樂觀鎖,因為它能得到更好的併發性,而且更容易實現。但是,如果衝突太多或者衝突的結果對於使用者來說痛苦的,那麼就需要使用悲觀策略,它能避免衝突的發生。 如果要求能夠支援高併發,那麼樂觀鎖。 其實使用樂觀鎖 高併發==高衝突, 看看你怎麼衡量了。

但是現在大多數原始碼開發者更傾向於使用樂觀鎖策略

共享鎖和排它鎖是具體的鎖,是資料庫機制上的鎖。

共享鎖(讀鎖)

在同一個時間段內,多個使用者可以讀取同一個資源,讀取的過程中資料不會發生任何變化。讀鎖之間是相互不阻塞的, 多個使用者可以同時讀,但是不能允許有人修改, 任何事務都不允許獲得資料上的排它鎖,直到資料上釋放掉所有的共享鎖

排它鎖(寫鎖)

在任何時候只能有一個使用者寫入資源,當進行寫鎖時會阻塞其他的讀鎖或者寫鎖操作,只能由這一個使用者來寫,其他使用者既不能讀也不能寫。 加鎖會有粒度問題,從粒度上從大到小可以劃分為

表鎖

開銷較小,一旦有使用者訪問這個表就會加鎖,其他使用者就不能對這個表操作了,應用程式的訪問請求遇到鎖等待的可能性比較高。

頁鎖:

是MySQL中比較獨特的一種鎖定級別,鎖定顆粒度介於行級鎖定與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的併發處理能力也同樣是介於上面二者之間。另外,頁級鎖定和行級鎖定一樣,會發生死鎖。

行鎖

開銷較大,能具體的鎖定到表中的某一行資料,但是能更好的支援併發處理, 會發 生死鎖