天天看點

Mysql事務詳解事務的基本要素(ACID)事務的并發問題MySQL事務隔離級别InnoDB 鎖補充

事務的基本要素(ACID)

  • 原子性(Atomicity):事務開始後所有操作,要麼全部做完,要麼全部不做,不可能停滞在中間環節。事務執行過程中出錯,會復原到事務開始前的狀态,所有的操作就像沒有發生一樣。也就是說事務是一個不可分割的整體,就像化學中學過的原子,是物質構成的基本機關。
  • 一緻性(Consistency):事務開始前和結束後,資料庫的完整性限制沒有被破壞 。比如A向B轉賬,不可能A扣了錢,B卻沒收到。
  • 隔離性(Isolation):同一時間,隻允許一個事務請求同一資料,不同的事務之間彼此沒有任何幹擾。比如A正在從一張銀行卡中取錢,在A取錢的過程結束前,B不能向這張卡轉賬。
  • 持久性(Durability):事務完成後,事務對資料庫的所有更新将被儲存到資料庫,不能復原。

事務的并發問題

  • 髒讀:事務A讀取了事務B更新的資料,然後B復原操作,那麼A讀取到的資料是髒資料
  • 不可重複讀:事務 A 多次讀取同一資料,事務 B 在事務A多次讀取的過程中,對資料作了更新并送出,導緻事務A多次讀取同一資料時,結果 不一緻。
  • 幻讀:系統管理者A将資料庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理者B就在這個時候插入了一條具體分數的記錄,當系統管理者A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。

小結:不可重複讀的和幻讀很容易混淆,不可重複讀側重于修改,幻讀側重于新增或删除。解決不可重複讀的問題隻需鎖住滿足條件的行,解決幻讀需要鎖表

MySQL事務隔離級别

事務隔離級别 髒讀 不可重複讀 幻讀
未送出讀(read-uncommitted)RUC
已送出讀(read-committed)RC
可重複讀(repeatable-read)RR
可串行化(serializable)S
  • 送出讀(RC):隻能讀取到已經送出的資料。
  • 可重複讀(RR):在同一個事務内的查詢都是事務開始時刻一緻的,InnoDB預設級别。
  • 串行化(serializable)會鎖表,是以不會出現幻讀的情況,這種隔離級别并發性極低,

    開發中很少會用到

  • Mysql InnoDB 預設事務隔離級别為RR

InnoDB 鎖

鎖類型

  • 讀鎖:也叫共享鎖、S鎖,若事務T對資料對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務隻能再對A加S鎖,而不能加X鎖,直到T釋放A上的S 鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
  • 寫鎖:又稱排他鎖、X鎖。若事務T對資料對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。
  • 表鎖:操作對象是資料表。Mysql大多數鎖政策都支援(常見mysql innodb),是系統開銷最低但并發性最低的一個鎖政策。事務t對整個表加讀鎖,則其他事務可讀不可寫,若加寫鎖,則其他事務增删改都不行。
  • 行級鎖:操作對象是資料表中的一行。是MVCC技術用的比較多的,但在MYISAM用不了,行級鎖用mysql的儲存引擎實作而不是mysql伺服器。但行級鎖對系統開銷較大,處理高并發較好。

行鎖

InnoDB實作了以下兩種類型的行鎖

  • 共享鎖(S):共享鎖就是多個事務對于同一資料可以共享一把鎖,都能通路到資料,但是隻能讀不能修改
  • 排他鎖(X):排他鎖就是不能與其他所并存,如一個事務擷取了一個資料行的排他鎖,其他事務就不能再擷取該行的其他鎖,包括共享鎖和排他鎖,但是擷取排他鎖的事務是可以對資料就行讀取和修改

對于共享鎖大家可能很好了解,就是多個事務隻能讀資料不能改資料,

對于排他鎖大家的了解可能就有些差别,我當初就犯了一個錯誤,以為排他鎖鎖住一行資料後,

其他事務就不能讀取和修改該行資料,其實不是這樣的。排他鎖指的是一個事務在一行資料加上排他鎖後,

其他事務不能再在其上加其他的鎖。mysql InnoDB引擎預設的修改資料語句,

update,delete,insert

都會自動給涉及到的資料加上排他鎖,

select

語句預設不會加任何鎖類型,

如果加排他鎖可以使用

select ...for update

語句,加共享鎖可以使用

select ... lock in share mode

語句。是以加過排他鎖的資料行在其他事務種是不能修改資料的,

也不能通過

for update

lock in share mode

鎖的方式查詢資料,但可以直接通過

select ...from...

查詢資料,因為普通查詢沒有任何鎖機制。

表鎖(意向鎖)

為了允許行鎖和表鎖共存,實作多粒度鎖機制,InnoDB還有兩種内部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。

  • 意向共享鎖(IS):事務打算給資料行加行共享鎖,事務在給一個資料行加共享鎖前必須先取得該表的IS鎖。
  • 意向排他鎖(IX):事務打算給資料行加行排他鎖,事務在給一個資料行加排他鎖前必須先取得該表的IX鎖。

間隙鎖(Next-Key鎖)

為了解決幻讀問題,innodb引入了gap鎖。

當我們用範圍條件而不是相等條件檢索資料,并請求共享或排他鎖時,InnoDB會給符合條件的已有資料記錄的索引項加鎖;對于鍵值在條件範圍内但并不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。

舉例來說,假如emp表中隻有101條記錄,其empid的值分别是 1,2,…,100,101,下面的SQL:

Select * from  emp where empid > 100 for update;
           

是一個範圍條件的檢索,InnoDB不僅會對符合條件的empid值為101的記錄加鎖,也會對empid大于101(這些記錄并不存在)的“間隙”加鎖。

InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關隔離級别的要求,對于上面的例子,要是不使用間隙鎖,

如果其它事務插入了empid大于100的任何記錄,那麼本事務如果再次執行上述語句,就會發生幻讀;另外一方面,是為了滿足其恢複和複制的需要。

有關其恢複和複制對鎖機制的影響,以及不同隔離級别下InnoDB使用間隙鎖的情況,

很顯然,在使用範圍條件檢索并鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍内鍵值的并發插入,

這往往會造成嚴重的鎖等待。是以,在實際應用開發中,尤其是并發插入比較多的應用,我們要盡量優化業務邏輯,盡量使用相等條件來通路更新資料,避免使用範圍條件。

還要特别說明的是,InnoDB除了通過範圍條件加鎖時使用間隙鎖外,如果使用相等條件請求給一個不存在的記錄加鎖,InnoDB也會使用間隙鎖!

InnoDB行鎖模式相容性清單

請求鎖模式

是否相容

目前鎖模式

X IX S IS
X 沖突 沖突 沖突 沖突
IX 沖突 相容 沖突 相容
S 沖突 沖突 相容 相容
IS 沖突 相容 相容 相容

如果一個事務請求的鎖模式與目前的鎖相容,InnoDB就将請求的鎖授予該事務;反之,如果兩者不相容,該事務就要等待鎖釋放。

意向鎖是InnoDB自動加的,不需使用者幹預。對于UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集加排他鎖(X);對于普通SELECT語句,InnoDB不會加任何鎖;事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。

  • 共享鎖(S):

    SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

  • 排他鎖(X):

    SELECT * FROM table_name WHERE ... FOR UPDATE

SELECT ... IN SHARE MODE

獲得共享鎖,主要用在需要資料依存關系時來确認某行記錄是否存在,

并確定沒有人對這個記錄進行

UPDATE

或者

DELETE

操作。但是如果目前事務也需要對該記錄進行更新操作,則很有可能造成死鎖,

對于鎖定行記錄後需要進行更新操作的應用,應該使用

SELECT... FOR UPDATE

方式獲得排他鎖。

InnoDB行鎖實作方式

InnoDB行鎖是通過給索引上的索引項加鎖來實作的,這一點MySQL與Oracle不同,後者是通過在資料塊中對相應資料行加鎖來實作的。

InnoDB這種行鎖實作特點意味着:隻有通過索引條件檢索資料,InnoDB才使用行級鎖,否則,InnoDB将使用表鎖!

在實際應用中,要特别注意InnoDB行鎖的這一特性,不然的話,可能導緻大量的鎖沖突,進而影響并發性能

InnoDB 的意向鎖有什麼作用

  • 在mysql中有表鎖,
LOCK TABLE my_tabl_name READ; 用讀鎖鎖表,會阻塞其他事務修改表資料。

LOCK TABLE my_table_name WRITe; 用寫鎖鎖表,會阻塞其他事務讀和寫。`
           
  • Innodb引擎又支援行鎖,行鎖分為
    • 共享鎖,一個事務對一行的共享隻讀鎖。
    • 排它鎖,一個事務對一行的排他讀寫鎖。
  • 這兩中類型的鎖共存的問題

考慮這個例子:

事務A鎖住了表中的一行,讓這一行隻能讀,不能寫。之後,事務B申請整個表的寫鎖。如果事務B申請成功,那麼理論上它就能修改表中的任意一行,這與A持有的行鎖是沖突的。資料庫需要避免這種沖突,就是說要讓B的申請被阻塞,直到A釋放了行鎖。

資料庫要怎麼判斷這個沖突呢?

  • step1:判斷表是否已被其他事務用表鎖鎖表
  • step2:判斷表中的每一行是否已被行鎖鎖住。

注意step2,這樣的判斷方法效率實在不高,因為需要周遊整個表。

于是就有了意向鎖。

在意向鎖存在的情況下,事務A必須先申請表的意向共享鎖,成功後再申請一行的行鎖。

在意向鎖存在的情況下,上面的判斷可以改成

  • step1:不變
  • step2:發現表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,是以,事務B申請表的寫鎖會被阻塞。

注意:申請意向鎖的動作是資料庫完成的,就是說,事務A申請一行的行鎖的時候,資料庫會自動先開始申請表的意向鎖,不需要我們程式員使用代碼來申請。

MVCC

MVCC (Multiversion Concurrency Control),即多版本并發控制技術,它使得大部分支援行鎖的事務引擎,

不再單純的使用行鎖來進行資料庫的并發控制,取而代之的是把資料庫的行鎖與行的多個版本結合起來,

隻需要很小的開銷,就可以實作非鎖定讀,進而大大提高資料庫系統的并發性能

關于innodb中MVCC的一些了解

補充

  • 事務隔離級别為讀送出時,寫資料隻會鎖住相應的行
  • 事務隔離級别為可重複讀時,如果檢索條件有索引(包括主鍵索引)的時候,預設加鎖方式是next-key 鎖;如果檢索條件沒有索引,更新資料時會鎖住整張表。一個間隙被事務加了鎖,其他事務是不能在這個間隙插入記錄的,這樣可以防止幻讀。
  • 事務隔離級别為串行化時,讀寫資料都會鎖住整張表
  • 隔離級别越高,越能保證資料的完整性和一緻性,但是對并發性能的影響也越大。
  • MYSQL MVCC實作機制參考連結
  • 關于next-key 鎖可以參考連結
  • MySQL 加鎖處理分析