記一次悲觀鎖的使用,聊一聊悲觀鎖與樂觀鎖

2022-02-01 03:39:52 字數 2701 閱讀 3958

最近一次寫了乙個介面在併發場景出現了資料覆蓋的問題,記得從一開始學資料庫的時候就沒有深入了解mysql的鎖和事務這塊,每次一想到這塊就總有一些疑惑,特此記錄一下使用場景以便後期回顧。

業務場景

學生答題每道題有多個空,每個空的正確與否以逗號分隔的方式儲存在乙個欄位中,批改人員多次呼叫批閱介面分別批改每道題不同的空,在併發場景如果沒有加鎖就會導致多個空的批改記錄被覆蓋。

表結構大體如下:

create table `test_answer` (

`id` bigint(20) not null auto_increment,

`test_id` bigint(20) not null,

`question_id` bigint(20) not null,

`student_id` bigint(20) not null,

`correct_detail` varchar(64) not null default '',

primary key (`id`),

unique key `idx_test_question_student_id` (`test_id`,`question_id`,`student_id`) using btree,

key `idx_student_id` (`student_id`) using btree

) engine=innodb default charset=utf8mb4;

需要執行的sql大體如下

begin;

#aselect correct_detail from test_answer where test_id = 1 and question_id = 1 and student_id = 1;

#bupdate test_answer set correct_detail = '0,1' where id = 1;

commit;

我們先假定答案的原始批改記錄是0,0,2次呼叫分別要將第一第二個空都改成1,當併發批改同一條記錄時都先執行到a而沒有執行b並提交時,因為mysql的預設隔離級別是repeatable read(可重複讀),所以兩個事務讀取的都是0,0(兩個事務均未提交),就會導致本來想要得到的結果是1,1,但卻變成了0,11,0,如下圖

事務a事務b

begin

begin

select correct_detail from test_answer where test_id = 1 and question_id = 1 and student_id = 1;

select correct_detail from test_answer where test_id = 1 and question_id = 1 and student_id = 1;

update test_answer set correct_detail = '0,1' where id = 1;

update test_answer set correct_detail = '1,0' where id = 1;

commit

commit

悲觀鎖與樂觀鎖

解決上面的場景有幾種方式,如果不單說從資料庫角度考慮,可以基於訊息保證唯一索引的記錄線性執行;當然從資料庫的角度除了將隔離級別設定為serializable級別以外,就是針對記錄新增悲觀鎖與樂觀鎖了。

悲觀鎖悲觀鎖方式的sql如下

begin;

#aselect correct_detail from test_answer where test_id = 1 and question_id = 1 and student_id = 1 for update;

#bupdate test_answer set correct_detail = '0,1' where id = 1;

commit;

使用悲觀鎖時因為查詢的是唯一索引,所以是針對這條資料的行加排他鎖(x鎖),此時其他事務無法讀和寫,只能等待這個事務結束或回滾,排他鎖釋放。(此處多說一句因為innodb的二階段提交,所以每次加鎖的時候是立即加鎖而釋放要等事務結束或回滾後才釋放,所以這些加鎖操作應該盡可能放在整個事務中盡可能靠後的位置,降低鎖的時間)

樂觀鎖樂觀鎖方式的sql如下

begin;

#aselect correct_detail from test_answer where test_id = 1 and question_id = 1 and student_id = 1;

#bupdate test_answer set correct_detail = '0,1' where id = 1 and correct_detail = '0,0';

commit;

樂觀鎖的方式因為查詢時不會加鎖所以可以提高併發,但是也產生了乙個問題就是可能當兩個事務併發執行時其中乙個無法修改資料,因為需要修改的資料已經變化如上例當其他事務在b執行之前提交了事務那麼b語句並不會更改這條記錄。

用哪個好?

上面就是這個業務場景悲觀與樂觀鎖的使用。具體在實際場景用用哪個,還要具體問題具體分析,如果對併發要求沒那麼高可以使用悲觀鎖的方式;如果這張表修改比較頻繁且對併發要求較高可使用樂觀鎖,從業務角度將修改失敗的資訊返回由使用者做判斷。

一次使用spring事務以及悲觀鎖的經歷

四 剩餘疑問點 1.悲觀鎖 顧名思義,認為資料被併發修改的機率非常大,每次修改資料前,都先對資料進行上鎖處理,即用select for update進行上鎖,而平常使用的synchronized其實也是悲觀鎖的一種應用。2.樂觀鎖 顧名思義,每次修改前,都認為資料不會造成衝突,只有在修改時,才去檢測...

聊一聊MySQL裡的鎖和MVCC

在乙個高併發的資料庫系統裡,可能會遇到多個事務同一時刻修改某條資料的情況,這樣就產生了資源衝突,解決衝突就需要用到鎖。一 鎖一說到鎖,就可能會聯想到樂觀鎖 悲觀鎖 共享鎖 讀鎖 排他鎖 互斥鎖 寫鎖 行級鎖 表級鎖 等一堆名詞,那它們之間到底有什麼區別和聯絡呢?其實很簡單,樂觀鎖和悲觀鎖是一種加鎖的...

樂觀鎖和悲觀鎖的使用

1.樂觀鎖和悲觀鎖各自的機制 a.樂觀鎖是一種思想,具體實現是,表中有乙個版本字段 或者是時間戳 第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,需要再次檢視該字段的值是否和第一次的一樣。如果一樣更新,反之拒絕。之所以叫樂觀,因為這個模式沒有從資料庫加鎖。b.悲觀鎖是讀取的時候為後面的...