事情是這樣的,我有個需求,簡單來說是每次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 乙個事務正在對一條記錄做修改,在這個事務完成並提交前...