mysql 並發下數據不一致的問題分析及解決


MySQL 5.6 , InnoDB存儲引擎,默認事務隔離級別(REPEATABLE-READ)

初始sql 腳本如下:

CREATE DEFINER=`root`@`localhost` PROCEDURE `Test`(out debitb decimal(14,2))
BEGIN

START TRANSACTION ;

select @db:=debit_balance from c_account_customer where id=1 ;
set debitb=@db;
insert into abacus.testvalue (val) values (@db);
update abacus.c_account_customer set debit_balance=@db+1 where id=1;
commit;

END

如上,存儲過程中開啟事務,先查詢debit_balance,查詢結果插入testvalue數據表,然后更新debit_balance(加1)

100個並發操作

客戶端,同時開啟一百個線程,每個線程都調用存儲過程Test。

假設數據表c_account_customer中的字段debit_balance初始值為10000.

那么客戶端程序執行完成后,理想情況下debit_balance=100、testvalue 數據表有100數據,val值為0-100。

看看結果:

如上,數據未達到預期的原因是在某一時刻,事務A讀取debit_balance值時並未鎖住數據,事務B(或許更多事務)此時也讀到了相同的值,

那么這些事務總體只對debit_balance進行了加1操作。那么如何解決以上問題?即當一個事務讀取數據時,鎖住數據行,在提交之前,其他事務不能執行

 

mysql :select ... for update 

修改sql腳本:

CREATE DEFINER=`root`@`localhost` PROCEDURE `Test`(out debitb decimal(14,2))
BEGIN

START TRANSACTION ;

select @db:=debit_balance from c_account_customer where id=1 for update;
set debitb=@db;
insert into abacus.testvalue (val) values (@db);
update abacus.c_account_customer set debit_balance=@db+1 where id=1;
commit;

END

如上,在查詢語句后面加上 for update

首先我們來看看並發操作后的結果:

 

通過圖例,我們發現在查詢語句中加入for update 解決了上面存在的問題。即我們在查詢出debit_balance后就把當前的數據鎖定,直到我們修改完畢后再解鎖.

 

現在我們來看看 for update:

在事務中,SELECT ... FOR UPDATE 同一筆數據時會等待其它事務結束后才執行,一般SELECT ... 則不受此影響拿上面的實例來說,當我執行select debit_balance from c_account_customer where id=1 for update;后。我在另外的事務中如果再次執行select debit_balance from c_account_customer where id=1 for update;則第二個事務會一直等待第一個事務的提交,此時第二個查詢處於阻塞的狀態,但是如果我是在第二個事務中執行select debit_balance from c_account_customer where id=1;則能正常查詢出數據,不會受第一個事務的影響 (經過測試)。

補充:MySQL select…for update的Row Lock與Table Lock
上面我們提到,使用select…for update會把數據給鎖住,不過我們需要注意一些鎖的級別,MySQL InnoDB默認Row-Level Lock,所以只有「明確」地指定主鍵,MySQL 才會執行Row lock (只鎖住被選取的數據) ,否則MySQL 將會執行Table Lock (將整個數據表單給鎖住)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM