1. 程式人生 > >從一個業務看待 InnoDB事務,InnoDB資料庫鎖,同步的關係

從一個業務看待 InnoDB事務,InnoDB資料庫鎖,同步的關係

起因

寫這篇文章的起因在於現在公司的一個”啟用學習卡“業務,啟用的介面是其它組提供(你只需要給介面”卡號“和”啟用的人“),而分配哪張卡號是自己來寫,關鍵是這個卡號是從一個表裡每次去選一個。用sql語句每次limit 1 去撈取一條,但是這裡面就出現了資源佔用問題,sql語句每次limit 1 都是同一條,除非你已經使用(改變這條資料的狀態)。一開始想到事務,但是越想越不對勁(後面還是用應用層面的鎖同步機制),大概瞭解其原理後也徹底梳理了一下事務,資料庫鎖,同步的關係,整理出這篇文章。

這裡寫圖片描述

事務和鎖

腦中第一個想到了事務,事務的作用是什麼?事務的本質是保持操作的原子性,保證資料的一致性。一個事務內會有多個方法,操作的一致性要求所有方法要麼全部成功,要麼全部失敗。 要說事務跟多執行緒併發有什麼聯絡,那就是事務的隔離機制,當多個事務併發執行,需要用事務的隔離機制保證多個事務併發執行不會相互影響。比如在讀已提交(Read committed)的隔離機制下:下面一個流程我們可以看一下

序號 事務A A結果 事務B B結果
1 set tx_isolation=’Repeatable-Read’; set tx_isolation=’Repeatable-Read’;
2 start transaction; start transaction;
3 select * from test where id = 1; 這裡寫圖片描述 select * from test where id = 1; 這裡寫圖片描述
4 update test set score = score +1;
5 select * from test where id = 1; 這裡寫圖片描述 select * from test where id = 1; 這裡寫圖片描述
7 update test set score = score +1; 拿不到排他鎖(X),最後會超時,更新失敗
8 commit;
9 select * from test where id = 1; 這裡寫圖片描述
10 update test set score = score +1; 事務A已commit,這次可以提交
11 select * from test where id = 1; 這裡寫圖片描述

上面兩個事務的交叉執行就已經涉及到 事務和鎖 的知識點,我們一條條梳理

  • 首先 兩個事務都使用了 Repeatable-Read 事務,可重複讀利用Read View(讀映象)保證前後兩次 讀都不會受其它事務的影響,這點可以從第5步和第9步證明(事務A執行了修改,不管事務A有沒有commit,事務B重新查詢還是原來值,避免了髒讀,不可重複度)。

    關於 Repeatable-Read更多的實現細節,可以看

http://hamilton.duapp.com/detail?articleId=38

從這一特性,我們就可以認為 Repeatable-Read 事務 並不能幫我們解決 “學習卡”的問題,原因在於 當另一個事務拉取了一條卡號資料並回寫這條資料的狀態,即使我們前後查了兩次 ,我們並不能發現這條資料被修改了(因為可重複讀),所以出於業務考慮,只能通過業務層面的同步機制(序列化) 拉取卡號資料。

  • 接下來就是第7步,在事務A做出更新之後,事務B也嘗試更新,但是更新失敗,因為事務A沒有commit操作,這裡面涉及資料庫鎖的知識,先簡單整理下:

InnoDB引擎的鎖機制

共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同資料集的排他鎖。
排他鎖(X):允許獲得排他鎖的事務更新資料,阻止其他事務取得相同資料集的共享讀鎖和排他寫鎖。
意向共享鎖(IS):事務打算給資料行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
意向排他鎖(IX):事務打算給資料行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。

說明

  1. 共享鎖和排他鎖都是行鎖,意向鎖都是表鎖,應用中我們只會使用到共享鎖和排他鎖,意向鎖是mysql內部使用的,不需要使用者干預。
  2. 對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖(SERIALIZABLE事務隔離機制會給select語句加上共享鎖,更新語句會加排他鎖),事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。

    共享鎖(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
    排他鎖(X):SELECT * FROM table_name WHERE … FOR UPDATE。

  3. 在MySQL中,行級鎖並不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。

從上面我們可以看出,當事務A執行更新操作,mysql會自己為這條資料增加 排他鎖(X),而排它鎖只允許獲得的事務執行操作,其它事務將被阻塞掛起,最後超時。 我再簡單做一個不同事務不同鎖的相容排斥情況。

當前鎖/請求鎖 X IX S IS
X 排斥 排斥 排斥 排斥
IX 排斥 相容 排斥 相容
S 排斥 排斥 相容 相容
IS 排斥 相容 相容 相容
  • 第8步事務A commit後,意味著排他鎖被釋放,第10步事務B的更新獲得了排他鎖,第11步證明成功更新。

我也想過在撈取一張“學習卡”的select的sql加上排他鎖( FOR UPDATE)特性,這樣從資料庫層面來說,在這條學習卡狀態改變之前,別的執行緒事務是不能 訪問和修改這條資料的,也就達到了同步的目的,但是當我 進行到“啟用學習卡”的時候,我發現“啟用介面”因為是外部介面,所以外部介面拿不到鎖導致無法回寫更改資料,導致超時,我發現數據庫鎖也不能解決這個業務問題。

業務層面的同步

雖然事務,資料庫鎖都無法幫我解決“學習卡”的問題,但是我轉移思路,事務和資料庫鎖雖然從資料庫層面可以杜絕多事務產生的執行緒問題,但是並不符合現在的業務,更合適的是從程式碼層面解決–比如我是新增redis同步鎖鎖住“撈取一張學習卡”和“啟用學習卡”兩部操作,這樣多執行緒也會同步執行,學習卡也不會重複啟用,也不會因為外部介面因為拿不到行鎖導致 啟用無法進行。

有人說事務,資料庫鎖和同步到底是怎樣的關係,我認為在這個業務實踐中,已經很明白了,事務,資料庫鎖和同步代表不同的領域,具體問題具體分析,要根據業務出發來技術選型。

結語

這篇文章我盡力都用實踐證明所說的一些原理,但是仍然因為是菜鳥,肯定還有疏忽之處,這也是我最近工作的一次總結。發現今天已經是7月了,想想年初所說的事到現在還沒有實現,實在十分懶散,下半年繼續努力了~!