java - mysql事务隔离的问题?多线程并发怎么保证可靠?
高洛峰
高洛峰 2017-04-17 17:55:17
0
5
534

mysql采用默认事务隔离级别REPEATABLE-READ;
然后我分别执行以下内容:
1、


2、使用jdbc访问数据库:

3、这时候打印结果,发现线程进入阻塞,一直卡在这里

也就是说我开启的另一个事务仍然能读取到数据,只是在最后执行executeUpdate的时候才被锁定不能执行
4、命令行执行commit


5、结果命令行操作被jdbc操作替换掉,数据改为4。

这不就是说REPEATABLE-READ仍然出现了丢失更新,而且没有行锁定吗?
事务与并发到底是什么区别,我在测试多线程执行以上jdbc操作时,发现最后得到的结果总是不对。例我开十个线程,每个都对数据+1,结果可能只加了5.所以在并发情况下怎么做才能保证数据的安全?

高洛峰
高洛峰

拥有18年软件开发和IT教学经验。曾任多家上市公司技术总监、架构师、项目经理、高级软件工程师等职务。 网络人气名人讲师,...

全部回覆(5)
PHPzhong

如果只是增一的話,number的數值不透過select獲取,select獲取的可能是舊值;然後換成update test set number = number + 1 where id = ?。這裡的number會是最新的,這種方式可以避免select for update的完全串列的效能遺失。

刘奇

REPEATABLE-READ是有可能出現幻讀的
如果你要確保絕對的安全只能把隔離等級設定成SERIALIZABLE
這樣所有事務都只能順序執行,自然不會因為並發有什麼影響了,但是效能會下降許多。

如果你既不想效能下降又想控制讓他不出錯,現在比較常用的做法是,使用更新的版本控制。
維護一個欄位作為UpdateVersion,修改時updateversion也作為一個參數傳入,在條件語句中加入例如where id=? and update_version = ? 當然set裡面要update_version+1。
這樣可以控製到每次只能有一個人更新一個版本。

洪涛

透過試驗,發現某種程度上已經理解了自己的問題。

我之前被關於事務隔離等級錯誤訊息誤導,認為REPEATABLE-READ等級和SERIALIZABLE等級能解決遺失更新的問題,然而實際上是不能的。
因為mysql的select ... from table;語句 不管哪一種隔離等級都是不會被阻塞的,

事务A读取 id为1 number = 3 ,执行更新操作 number设为1;
事务B读取 id为1 number = 3, 执行+1操作  更新--进入阻塞

在隔離等級下只有在更新資料時才會等待寫鎖(排它鎖)的釋放,所以我多個執行緒可以同時讀取到number=3,基於此進行修改就必然會導致遺失更新。

所以解決方法為程式中加入悲觀鎖 或 樂觀鎖 機制。
悲觀鎖採用的是 select ... for update,

事务A:  select ... for update,执行更新操作,
事务B:  select ... for update --阻塞

這時事務B讀取操作將無法執行,只有當事務A的commit完成,事務B才可以繼續執行,相當於採用串行一個個執行。

樂觀鎖常用的是版本控製或時間戳控制,

事务A:执行 此时版本为 1;执行完update ...set version=2 where version = 1;
事务B:执行 此时版本为 1;执行完update ...set version=2 where version = 1;

這時候事務B發現version=1已經不存在了,因為事務A先執行完更新了資料庫,把version字段設為了2,這樣就會導致該事務提交失敗,需要我們在程序中判斷異常情況下如何繼續操作。

以上就是我目前的理解,至於mysql本身支援的MVCC(多版本並發控制)還不知道怎麼用,樂觀鎖的異常處理也不太清楚具體要怎麼實現,可能是要根據具體的業務場景採用不同的處理手段吧。

刘奇

如果是單純的Select会被看做是单纯的查询操作,所以不会被挂起。
如果要对某行进行事务,应该先在Select时就使用Select ... For Update这种格式。
使用For Update会对所选择的行加锁,当另外的事务再进行时,会在那个事务的Select ... For Update時就掛起,等待當前事務完成後才會繼續執行。這樣才能確保查詢和寫入之間沒有中間者。

巴扎黑

透過鎖來控制。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板