資料庫併發下的髒資料問題

2021-07-14 13:35:55 字數 2824 閱讀 6375

事情是這樣的,我有個需求,簡單來說是每次insert三條記錄,每次都給本次insert的記錄version+1,理想情況下,假設沒有併發,最後的資料應該是這樣

id    name        version

1     name           1

2     name           1

3     name           1

4     name           2

5     name           2

6     name           2

7     name           3

8     name           3

9     name           3

然後由於併發,結果變成這樣,

id    name        version

1     name           1

2     name           1

3     name           1

4     name           1

5     name           1

6     name           1

7     name           2

8     name           2

9     name           2

sql語句如下:

開始事務

insert into test set name=『name』;

執行三次,存下這三次lastinsertid

select max(version) from test where name=』name';

newversion = max version + 1

update test set version = newversion where id in (lastinsertid1, lastinsertid2, lastinsertid3)

select max(version) from test;

提交事務

在闡述如何解決之前,

先在mysql的innodb下做了個實驗,是關於insert是否鎖表,形式是兩個併發的程序同時開始事務,結果既有意料之中又有意料之外。

意料之中的是,mysql auto-increatement機制是簡單insert語句不會鎖表,沒錯,驗證通過。

意料之外的是,說好的事務互相不可見呢,

竟然在沒有commit之前互相影響了,好吧,是個坑,mark一下。

進入正題,分析下上面的sql語句,

假設insert不鎖表,那也就是說,

兩個事務在insert之後,

先將其中乙個事務(取名事務a) select max version,最開始得到的值是0,然後+1,再update new version,最後select max version,得到值為1。

然後另乙個事務(取名事務b) 也select max version,此時事務之間不可見的原理總算真理了,得到的值依然是0,不受事務a影響。也就是說,此時事務b如何和事務a一樣,在null的基礎上version+1,最後事務b插入的三條資料version也是b,髒資料就應運而生了。

本來打算使用redis計數器來解決這個問題,不過後來想想,內部矛盾內部解決,借助第三者終非正途。靈光一閃,想到innodb的多版本併發控制,主要手段是借助附加的隱藏version欄位。

同樣的思路,我也加個版本控制字段,比如control_version(值為當前最大自增id),另外name必須加索引,下面會用到行鎖,不加索引導致表鎖,會影響到其它不相關的insert操作。

然後sql語句如下,

開始事務

insert into test set name=『name』;

執行三次,存下這三次的lastinsertid

select max(version),max(control_version) from test where name=』name』;

newversion = max version + 1

update test set version = newversion where id in (lastinsertid1, lastinsertid2, lastinsertid3) and control_version = max_control_version and name=』name';

update test set control_version= lastinsertid3 where name=』name』;

select max(version) from test;

提交事務

以上事務併發會導致在第三句update test set version = newversion … where … name=』name』 阻塞,此時有2種情況

1)因為其他事務insert導致本事務丟擲鎖超時異常,或其他事務也執行到第三局update而丟擲死鎖異常,一旦抓到異常就rollback

2)阻塞在有效時間內得到release鎖(之前擁有鎖的事務commit或rollback),但由於可重複讀,即使其他事務已經commit更新了最大version值,本事務拿到的max version依然是舊version。此時control_version就起作用了,本事務commit後,第三句update會找不到滿足條件的記錄,因此 id in (lastinsertid1, lastinsertid2, lastinsertid3) 的version為空,此處可根據該線索寫刪除邏輯或者重複紅色部分sql事務直至成功為止。

解決資料庫併發下的髒資料的思考

size large color blue 一般解決資料庫髒資料的問題,常常用到樂觀鎖和悲觀鎖。樂觀鎖是在我取出資料進行操作前獲取到當前乙個時間戳,當更新的時候在對比下時間戳。如果時間戳相同則更新,否則不更新。悲觀鎖則是在取出資料的時候將這條資料加鎖,其他要獲取操作這條資料的動作要等到釋放鎖之後才能...

解決Redis高併發下資料庫穿透問題

假如上萬或數十萬個請求同時請求乙個介面,介面中從redis中查詢相應資訊。如果redis查詢結果為空,就回去查資料庫,應為是在高併發情況下,所以會多次查資料庫,有可能是成千上萬次。錯誤示例 這會使資料庫的一壓力會非常大。這時我們就用synchronize同步鎖來解決。一萬個請求同時進來,只有乙個請求...

資料庫併發的問題

併發操作會帶來一系列的問題 更新丟失 lost update 當兩個或多個事務選擇了同一行然後基於最初選定的值更新改行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新的問題,最後更新覆蓋了由其他事務所做的更新 髒讀 dirty reads 乙個事務正在對一條記錄做修改,在這個事務完成並提交前...