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 (將整個數據表單給鎖住)。