1. 程式人生 > 資料庫 >mysql:如何解決資料修改衝突(事務+行級鎖的實際運用)

mysql:如何解決資料修改衝突(事務+行級鎖的實際運用)

摘要:最近做一個接診需求遇到一個問題,假設一個訂單諮詢超過3次就不能再接診,但如果兩個醫生同時對該訂單進行諮詢,查資料庫的時候查到的接診次數都是2次,那兩個醫生都能接診,所謂接診可以理解為更新了接診次數,此時就出現了問題(接診了4次)。

其實這個問題看似很明朗,但想要完全解決需要理解事務和鎖的概念,以前總對事務的隔離級別有點雲裡霧裡,現在可以通過這個案例可以理清楚。

事務

操作資料庫最小的工作單位,簡單講就是將多條dml(增刪改)語句聯合完成。要麼同時成功,要麼同時失敗。看到這裡你可能會發現光加事務解決不了上述問題,而且加了事務之後,多條事務之間的相互關係就涉及到事務的隔離級別,所以接著往下看。

事務隔離級別

READ UNCOMMITTED(讀未提交,髒讀)

事務中的修改,即使沒有提交,對其他會話也是可見的。可以讀取未提交的資料——髒讀。髒讀會導致很多問題,一般不適用這個隔離級別.。

 

-- ------------------------- read-uncommitted例項 ------------------------------
-- 設定全域性系統隔離級別
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- Session A
START TRANSACTION;
SELECT * FROM USER;
UPDATE USER SET NAME="READ UNCOMMITTED";
-- commit;

-- Session B
SELECT * FROM USER;

//SessionB Console 可以看到Session A未提交的事物處理,在另一個Session 中也看到了,這就是所謂的髒讀
id    name
2    READ UNCOMMITTED
34    READ UNCOMMITTED

 

READ COMMITTED(讀已提交,不可重複讀) 

一般資料庫都預設使用這個隔離級別(MySQL 不是), 這個隔離級別保證了一個事務如果沒有完全成功(commit 執行完),事務中的操作對其他會話是不可見的。

-- ------------------------- read-cmmitted例項 ------------------------------
-- 設定全域性系統隔離級別
SET GLOBAL TRANSACTION ISOLATION LEVEL READ  COMMITTED;
-- Session A
START TRANSACTION;
SELECT * FROM USER;
UPDATE USER SET NAME="READ COMMITTED";
-- COMMIT;

-- Session B
SELECT * FROM USER;

//Console OUTPUT:
id    name
2    READ UNCOMMITTED
34    READ UNCOMMITTED


---------------------------------------------------
-- 當 Session  A執行了commit,Session B得到如下結果:
id    name
2    READ COMMITTED
34    READ COMMITTED

REPEATABLE READ (可重複讀)

一個事務中多次執行統一讀 SQL,返回結果一樣。這個隔離級別解決了髒讀的問題,幻讀問題。這裡指的是 innodb 的 rr 級別,innodb 中使用 next-key 鎖對"當前讀"進行加鎖,鎖住行以及可能產生幻讀的插入位置,阻止新的資料插入產生幻行。

會話T1事務中執行一次查詢,然後會話T2新插入一行記錄,這行記錄恰好可以滿足T1所使用的查詢的條件。然後T1又使用相同 的查詢再次對錶進行檢索,但是此時卻看到了事務T2剛才插入的新行。這個新行就稱為“幻像”,因為對T1來說這一行就像突然 出現的一樣。innoDB 的 RR 級別無法做到完全避免幻讀。

SERIALIZABLE (可序列化)

最強的隔離級別,通過給事務中每次讀取的行加鎖,寫加寫鎖,保證不產生幻讀問題,但是會導致大量超時以及鎖爭用問題。

mysql預設的隔離級別是可重複讀,看到這裡大家應該明白,即使隔離級別是可重複讀,但因為select操作時並未加鎖,導致都會查到符合條件的資料,所以這裡要引入一個鎖的概念:行級鎖。

行級鎖

  • 共享鎖(S) 共享鎖也稱為讀鎖,讀鎖允許多個連線可以同一時刻併發的讀取同一資源,互不干擾;

  • 排他鎖(X) 排他鎖也稱為寫鎖,一個寫鎖會阻塞其他的寫鎖或讀鎖,保證同一時刻只有一個連線可以寫入資料,同時防止其他使用者對這個資料的讀寫。

總結(解決方案)

其實上文分析了那麼多,最後的解決方案很簡單。就是在原來的read和update合起來加事務,原來的select語句加排他鎖,即在select語句後面加for update。假如有事務A,B,加入排他鎖之後,假設事務A先獲得鎖,事務B必須等到事務A commit之後才能開始select,所以讀到的是最新修改的資料。至於為什麼不加共享鎖,除了可能造成髒寫之後,在這種情況下還可能造成死鎖。假如兩個事務 A 、 B 都讀取同一行記錄,那麼在這一行就加上了共享鎖,但是 A 和B 事務中都需要修改這一行,那麼都要等待對方釋放共享鎖才能進行,結果造成了死鎖。