1. 程式人生 > >Mysql中的事務隔離

Mysql中的事務隔離

Innodb的鎖

  • Record lock

單條索引記錄上加鎖,record lock鎖住的永遠是索引,而非記錄本身,行鎖鎖定的是索引記錄,而不是行資料,也就是說鎖定的是key。即使該表上沒有任何索引,那麼Innodb在後臺建立一個隱藏的聚集主鍵索引,那麼鎖住的就是這個隱藏聚集主鍵索引。所以說當一條sql沒有走任何索引的時候,那麼將會在每一個條聚集索引後面加X鎖,這個類似於表鎖,但原理上和表鎖應該是完全不同的。如果一個條件無法通過索引快速過濾,儲存引擎層面就會將所有記錄加鎖後返回,再由MySQL Server層進行過濾。
但在實際使用過程中,MySQL做了一些改進,在MySQL Server過濾條件,發現不滿足後,會呼叫unlock_row方法,把不滿足條件的記錄釋放鎖(違背了二段鎖協議的約束)。這樣做,保證了最後只會持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的。可見即使是MySQL,為了效率也是會違反規範的。(參見《高效能MySQL》中文第三版p181)

  • gap lock

在索引記錄之間的間隙加鎖,或者是在某一條索引記錄之前或者之後加鎖,並不包括索引記錄本身。區間鎖
https://www.zhihu.com/question/51390849

  • next-key lock

在預設情況下,mysql的事務隔離級別是可重複讀,並且innodb_locks_unsafe_for_binlog引數為0,這時預設採用next-key locks。所謂Next-Key Locks,就是Record lock和gap lock的結合,即除了鎖住記錄本身,還要再鎖住索引之間的間隙。

如果使用的是沒有索引的欄位,即使沒有匹配任何資料,那麼會給全表加入gap鎖。同時,它不能像上文行鎖一樣經過MySQL Server過濾自動解除不滿足條件的鎖,因為沒有索引,則這些欄位也就沒有排序,也就沒有區間。除非該事務提交,否則其他事務無法插入任何資料。

行鎖防止別的事務修改或刪除,GAP鎖防止別的事務新增,行鎖和GAP結合的Next-Key鎖共同解決了RR級別在寫資料時的幻讀問題。 ???

Mysql在RR隔離級別下是可以防止幻讀的

為什麼可以解決幻讀,由於Mysql中的MVCC多版本併發控制機制。
在RR級別下,Mysql對讀是不加鎖的,但是對寫加鎖。
MVCC中,對讀並沒有加鎖,而是進行快照讀取(snapshot)。

MVCC一致性讀
當一個T事務開始的時候,T會獲得一個抽象的時間戳(版本),當對資料X進行讀取的時候,並不是直接看到最新寫入的資料而是在T開始前的所有執行中的事務中最後一個對X標記的版本(如果T修改過X,那麼看到的是自己的版本)。也就是說T是基於當前的資料庫的一個映象進行操作的

而T開始執行是獲得的版本就是這個快照的憑證。這樣能保證所有的讀都是基於一個一致的狀態獲取的。所以MVCC一致性讀機制就保證了再RR模式下的可重複讀特性。

MVCC中的併發控制寫
當進行update,insert,delete等修改操作時,是會使用鎖的,而且是在事務提交後釋放鎖,使用了長期寫鎖。

RR模式下的當前讀和快照讀

  • 當前讀
    update ,insert ,delete 在更新之前會讀取當前最新的資料
  • 快照讀
    select 會進行快照讀

Innodb內部每個事務開始時,都會有一個事務id, 同時事務物件中還有一個read_view變數,用於控制該事務可見的記錄範圍(MVCC)。對於每個訪問到的記錄行,會根據read_view的trx_id(事務id)與行記錄的trx_id比較,判斷記錄是否邏輯上可見。 即在select的時候是會去判斷記錄是否可見的,但是修改操作不會,是會直接去拿最新的資料的。

幻讀的解決

MVCC的一致性讀機制就保證了RR隔離級別下可以解決幻讀。因為讀操作會根據每一個訪問到的記錄行,比較read_view的trx_id(事務id)與行記錄的的trx_id。只會取出第一次讀取到的行。

在RR模式下,寫操作加鎖,不僅會對記錄加行鎖,還會加上next-key Lock。next-key Lock在一個session對記錄進行了更改後,會對記錄加上next-key lock(gap lock+record lock)。此時另一個session如果想插入一個條資料或者更新一條資料,是會遇到一些限制的,在一些情況下會被阻塞到session commit。至於哪些情況下更新資料會導致gap lock起作用,要看試圖更新後的吉利是否在gap lock起作用的區間裡。
https://www.zhihu.com/question/51390849

MVCC一致性讀
其實MVCC一致性讀並不是只有在RR模式下有,在RC模式下,也是存在MVCC一致性讀的,但是為什麼RC下會存在不可重複讀呢,原因就是在RC模式下select 讀取資料並不是按照事務開始的trx_id來判斷是否邏輯上可見的,而是按語句開始的時候trx_id來判斷是否邏輯上可見,其實也就變成了當前讀。

mysql使用了一個併發版本控制機制,他們把它叫做MVCC,通俗的也就是說:mysql為了提高系統的併發量,在事務未提交前,雖然事務內操作的資料是鎖定狀態,但是另一個事務仍然可以讀取。

幻讀和不可重複讀

很多人混淆不可重複讀和幻讀,確實這兩者有點相似。但是不可重複讀重點在於update和delete,而幻讀的重點在於insert。

如果使用鎖機制來實現這兩種隔離級別,在可重複讀中,該sql第一次讀取到資料後,就將這些資料 加鎖,其他事務無法修改這些資料,就可以實現可重複讀了。但是這種方法卻無法鎖住insert的資料,所以當事務A先前讀取了資料,或者修改了全部資料,事務 B還是可以insert資料提交,這時事務A就會發現莫名其妙的多了一條之前沒有的資料,這就是幻讀,不能通過行鎖來避免。需要Serializable隔離級別,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這麼做可以有效的避免幻讀、不可重複讀、髒讀等問題,但會幾大的降低資料庫的併發能力。復讀和幻讀最大的區別,就在於如何通過鎖機制來解決他們產生的問題。

上文說的,是使用悲觀鎖機制來處理 這兩種問題,但是MySQL、Oracle、PostgreSQL等成熟的資料庫,出於效能考慮,都是使用了以樂觀鎖為理論基礎的MVCC(多版本併發控制)來避免這兩種問題。

RR級別下的更新丟失

雖然Mysql下RR隔離級別是可以防止幻讀的,但是無法防止丟失更新。

首先說下為什麼會出現丟失更新,出現丟失更新的原因就是在一個事務中,後續的更新操作,使用之前查詢出來的結果進行更新,這樣就會出現丟失更新。

使用者A在銀行卡有100元錢,某一刻使用者B向A轉賬50元(稱為B操作),同時有使用者C向A轉賬50元(稱為C操作);

B操作從資料庫中讀取他此時的餘額100,計算新的餘額為100+50=150

C操作也從資料庫中讀取他此時的餘額100,計算新的餘額為100+50=150

B操作將balance=150寫入資料庫,之後C操作也將balance=150寫入資料庫
最終A的餘額變為150

上面的例子,A同時收到兩筆50元轉賬,最後的餘額應該是200元,但卻因為併發的問題變為了150元,原因是B和C向A發起轉賬請求時,同時打開了兩個資料庫會話,進行了兩個事務,後一個事務拿到了前一個事務的中間狀態資料,導致更新丟失。

那我們要怎麼解決更新丟失的問題呢?

兩種方式,悲觀鎖和樂觀鎖。

  • 樂觀鎖

在進行更新操作的時候才去檢查當前值是否是期望值(類似CAS)

begin;
select balance from account where id=1;
-- 得到balance=100;然後計算balance=100+50=150
update account set balance = 150 where id=1 and balance = 100;
commit;
  • 悲觀鎖

悲觀鎖,在每一次更新之前都認為會有其他的程序對該資料進行修改。即在每一次更新之前都把需要更新的資料加排他鎖。
我們知道insert,update,delete等修改操作在RR模式下是會將資料加上排他鎖直到事務結束的。
那我們現在想要在查詢的時候就將資料加上排他鎖,需要如何去做呢。mysql中提供了相應的語句 for update;

begin;
select * from account where id = 1 for update;
update account set balance=150 where id =1;
commit;

for update 和 in share mode

    SELECT ... LOCK IN SHARE MODE sets a shared mode lock on the rows read. A shared mode lock enables other sessions to read the rows but not to modify them. The rows read are the latest available, so if they belong to another transaction that has not yet committed, the read blocks until that transaction ends. 

在讀取的行上設定共享模式鎖。共享模式鎖允許其他會話讀取行,但不修改行。讀取的行是最新可用的,因此,如果它們屬於尚未提交的另一個事務,則讀取將阻塞,直到該事務結束。

 SELECT ... FOR UPDATE sets an exclusive lock on the rows read. An exclusive lock prevents other sessions from accessing the rows for reading or writing. 

在讀取的行上設定獨佔鎖。獨佔鎖阻止其他會話訪問用於讀寫的行。

for update 為阻塞讀嗎??? 應該不會把

https://blog.csdn.net/cug_jiang126com/article/details/50544728

https://zhuanlan.zhihu.com/p/31875702

gap lock的前置條件:
1 事務隔離級別為REPEATABLE-READ,innodb_locks_unsafe_for_binlog引數為0,且sql走的索引為非唯一索引
2 事務隔離級別為REPEATABLE-READ,innodb_locks_unsafe_for_binlog引數為0,且sql是一個範圍的當前讀操作,這時即使不是非唯一索引也會加gap lock

RR級別下會防止幻讀到底是因為MVCC的快照讀還是因為gap lock

看到網上有很多文章說Mysql中RR級別可以防止幻讀的出現是因為gap lock。並且給出了相應的示例,示例都是以在RR級別下將innodb_locks_unsafe_for_binlog這個引數開啟來佐證gap lock防止了幻讀。

semi-consistent read
https://www.cnblogs.com/yuyutianxia/p/8548063.html