1. 程式人生 > >Mysql-innodb事務、鎖以及MVCC併發版本控制

Mysql-innodb事務、鎖以及MVCC併發版本控制

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

1、事務

  1.1 事務的四大特性

  • 原子性(Atomicity):原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾。
  • 一致性(Consistency):一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。拿轉賬來說,假設使用者A和使用者B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是5000,這就是事務的一致性。
  • 隔離性(Isolation): 隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離。
  • 永續性(Durability):永續性是指一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。

1.2 併發事務帶來的問題

  • 髒讀:指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。比如使用者A的賬戶裡有500元,事物T1:將money-100=400,但是該事物還未提交。 事物T2:讀取使用者A的賬戶,檢視到A的賬戶有400。然後事物1回滾,使用者A的賬戶又變回了原來的500。 事物T2就產生了髒讀。
  • 不可重複讀:不可重複讀是指A事務讀取了B事務已經提交的更改資料。假設A在取款事務過程中查詢賬戶餘額,B往該賬戶轉賬100後,A再次查詢賬戶餘額,會發現兩次讀取賬戶的餘額發生不一致。不可重複讀和髒讀的區別是,髒讀是某一事務讀取了另一個事務未提交的髒資料,而不可重複讀則是讀取了前一事務提交的資料。
  • 幻讀:A事務讀取到了B事務的新增資料。A事務統計存款賬戶的總金額為10000,這時B事務新增了一個賬戶存款為100,A事務再次統計總金額10100(產生了幻讀)。幻讀與不可重複讀的區別,幻讀讀取了其他事務新增的資料,不可重複讀,讀取了其他事務的更改(或者刪除)資料。

 

1.3 事務的隔離級別

為了達到上述事務特性,資料庫定義了幾種不同的事務隔離級別:

  • READ_UNCOMMITTED(未提交讀): 最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀
  • READ_COMMITTED(提交讀): 允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
  • REPEATABLE_READ(可重複讀): 對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生(Mysql innodb儲存引擎通過一些特殊的處理,在該隔離級別解決了幻讀問題)。
  • SERIALIZABLE(序列): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程式的效能。通常情況下也不會用到該級別。

這裡需要注意的是:Mysql 預設採用的 REPEATABLE_READ隔離級別。

2、Innodb的鎖

Innodb 與 MyISAM 最大的不同在於:一是支援事務,二是採用行級鎖。

共享鎖:又稱為讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一資料可以共享一把鎖,都能訪問到資料,但是隻能讀不能修改;
加鎖釋鎖方式:
select * from procuct WHERE id=1 LOCK IN SHARE MODE;

排他鎖:
又稱為寫鎖,簡稱X鎖,排他鎖不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的鎖(共享鎖、排他鎖),只有獲取了排他鎖的事務才可以對資料行進行讀取和修改,(其他事務要讀取資料可來自於快照)。

意向共享鎖(IS):表示事務準備給資料行加入共享鎖,即一個數據行加共享鎖前必須先取得該表的IS鎖,意向共享鎖之間是可以相互相容的
意向排它鎖(IX):表示事務準備給資料行加入排他鎖,即一個數據行加排他鎖前必須先取得該表的IX鎖,意向排它鎖之間是可以相互相容的
意向鎖(IS、IX)是InnoDB資料操作之前自動加的,不需要使用者干預。


為什麼需要意向鎖?

假如事務A鎖住表中的一行(寫鎖),事務B鎖住整個表(寫鎖)。如果沒有意向鎖,事務A既然鎖住了某一行,其他事務就不可能修改這一行。這與”事務B鎖住整個表就能修改表中的任意一行“形成了衝突。所以,沒有意向鎖的時候,行鎖與表鎖共存就會存在問題!有了意向鎖之後,事務A在申請行鎖(寫鎖)之前,資料庫會自動先給事務A申請表的意向排他鎖。當事務B去申請表的寫鎖時就會失敗,因為表上有意向排他鎖之後事務B申請表的寫鎖時會被阻塞。

加鎖方式:
delete / update / insert 預設加上X鎖
SELECT * FROM table_name WHERE ... FOR UPDATE

InnoDB的行鎖實現方式:

通過給索引上的索引項加鎖來實現的,如果沒有索引,InnoDB將對錶中的所有記錄加鎖,實際效果跟表鎖一樣。

InnoDB的行鎖分為三種情況:

  • Next-key locks:鎖住記錄+區間(左開右閉),當sql執行按照索引進行資料的檢索時,查詢條件為範圍查詢(between and、<、>等)並有資料命中則此時SQL語句加上的鎖為Next-key locks,鎖住索引的記錄+區間(左開右閉)。如果有一張表,用sql “SELECT * FROM product WHERE id > 3 AND id < 6 FOR UPDATE;” 去查詢,那麼該條語句會鎖住(5,7] 這之間的資料。 這時候向表中插入id為6的資料,是會被阻塞。

  • Gap locks:鎖住資料不存在的區間(左開右開),當sql執行按照索引進行資料的檢索時,查詢條件的資料不存在,這時SQL語句加上的鎖即為Gap locks,鎖住索引不存在的區間(左開右開)。當用“SELECT * FROM product WHERE id > 2 AND id < 4 FOR UPDATE;”去查詢,查不到資料,此時會鎖住(1,5)區間的資料。
  • Record locks:鎖住具體的索引項,當sql執行按照唯一性(Primary key、Unique key)索引進行資料的檢索時,查詢條件等值匹配且查詢的資料是存在,這時SQL語句加上的鎖即為記錄鎖Record locks,鎖住具體的索引項。當使用SELECT * FROM product WHERE id =2 FOR UPDATE 去查詢,它會鎖住id為2的這一行記錄。

 

3、MVCC併發版本控制

MVCC就是同一條資料可以同時存在多個版本:更新資料時,先插入一條新記錄,然後把舊記錄標記為刪除;查詢時只查詢事務開始前就已存在的記錄。

  • 快照讀:select語句預設,不加鎖,MVCC實現可重複讀,使用的是MVCC機制讀取undo中的已經提交的資料。所以它的讀取是非阻塞的。
  • 當前讀:select語句加S鎖或X鎖;所有的修改操作加X鎖。

RR隔離級別下的快照讀,不是以begin開始的時間點作為snapshot建立時間點,而是以第一條select語句的時間點作為snapshot建立的時間點。