mysql采用默认事务隔离级别REPEATABLE-READ;
然后我分别执行以下内容:
1、
2、使用jdbc访问数据库:
3、这时候打印结果,发现线程进入阻塞,一直卡在这里
也就是说我开启的另一个事务仍然能读取到数据,只是在最后执行executeUpdate的时候才被锁定不能执行
4、命令行执行commit
5、结果命令行操作被jdbc操作替换掉,数据改为4。
这不就是说REPEATABLE-READ仍然出现了丢失更新,而且没有行锁定吗?
事务与并发到底是什么区别,我在测试多线程执行以上jdbc操作时,发现最后得到的结果总是不对。例我开十个线程,每个都对数据+1,结果可能只加了5.所以在并发情况下怎么做才能保证数据的安全?
如果只是增一的話,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;語句 不管哪一種隔離等級都是不會被阻塞的,
在隔離等級下只有在更新資料時才會等待寫鎖(排它鎖)的釋放,所以我多個執行緒可以同時讀取到number=3,基於此進行修改就必然會導致遺失更新。
所以解決方法為程式中加入悲觀鎖 或 樂觀鎖 機制。
悲觀鎖採用的是 select ... for update,
這時事務B讀取操作將無法執行,只有當事務A的commit完成,事務B才可以繼續執行,相當於採用串行一個個執行。
樂觀鎖常用的是版本控製或時間戳控制,
這時候事務B發現version=1已經不存在了,因為事務A先執行完更新了資料庫,把version字段設為了2,這樣就會導致該事務提交失敗,需要我們在程序中判斷異常情況下如何繼續操作。
以上就是我目前的理解,至於mysql本身支援的MVCC(多版本並發控制)還不知道怎麼用,樂觀鎖的異常處理也不太清楚具體要怎麼實現,可能是要根據具體的業務場景採用不同的處理手段吧。
如果是單純的
Select
会被看做是单纯的查询操作,所以不会被挂起。如果要对某行进行事务,应该先在
Select
时就使用Select ... For Update
这种格式。使用
For Update
会对所选择的行加锁,当另外的事务再进行时,会在那个事务的Select ... For Update
時就掛起,等待當前事務完成後才會繼續執行。這樣才能確保查詢和寫入之間沒有中間者。透過鎖來控制。