【資料一致性】多執行緒寫資料庫,如何保持資料一致性?
阿新 • • 發佈:2019-01-25
如題,這種情況一般在數字類資料更新時需要保證萬無一失,尤其是金額類的數字
比如小明的銀行號有1000塊錢
他做了一筆交易20元,很簡單,我們要做一次更新
UPDATE XXX SET MONEY=NOWMONEY-20 WHERE ID=小明
一次一次的來沒關係,隨便怎麼更新
加入併發量高了,N人都在花小明賬號的錢,
每個人都在背後執行UPDATE XXX SET MONEY=NOWMONEY-Y WHERE ID=小明
這時候就極易出現賬號金額不一致的情況
第一筆 20,剩80才對
第二筆30,有可能剩70,但是應該剩50才對啊
兩個請求一起來的
這裡有個簡易的解決方案
我們在做數字類資料更新時,首先應該將該資料查出來再去更新,這樣保證後續的更新是在這個查出來的基數上面更新的
CAS compare and set/swap
只有原值沒有變化的情況下才會更新
按照這個思想,我們再回到上面的問題看一下
小明賬號1000元
第一次 交易20
UPDATE XXX SET MONEY=NOWMONEY-20 WHERE ID=小明 AND MONEY = NOWMONEY,這裡的NOWMONEY就是查出來的數字,更新成功
第二次 交易30 這時候基礎資料已經變了,仍然用上面的條件更新就會失敗了
更新成功與否通過executeUpdate返回的int值來判斷就行了。
程式碼展示
String url = "jdbc:mysql://XXXXXXXXXX"; String username = "XXX"; String passwd = "XXX"; Connection conn = DriverManager.getConnection(url, username, passwd); String sql = "update test set idinfo = idinfo- ? where id = 5 and idinfo=1000"; String sql2 = "update test set idinfo = idinfo- ? where id = 5 and idinfo=1000"; final PreparedStatement ps = conn.prepareStatement(sql); int temp = 1; ps.setInt(1, temp); final PreparedStatement ps2 = conn.prepareStatement(sql2); int temp2 = 5; ps2.setInt(1, temp2); int count = 10; ThreadPoolExecutor poolExe = new ThreadPoolExecutor(10, 100, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(count)); for (int i = 0; i < count; i++) { poolExe.submit(new Runnable() { public void run() { // TODO Auto-generated method stub try { if (ps.executeUpdate()>0) { System.out.println("-1執行成功"); } else { System.out.println("-1執行失敗"); } if (ps2.executeUpdate()>0) { System.out.println("-5執行成功"); } else { System.out.println("-5執行失敗"); } } catch (SQLException e) { e.printStackTrace(); } } }); } poolExe.shutdown(); try { poolExe.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }
compareAndSet方法呼叫native方法去實現CAS操作,current是在該方法之前獲取到的一個當前值,通過get拿到的,是一個volatile定義的變數,也就是從記憶體中讀到的值,那麼在做update之前當前值與剛才從記憶體中讀到的值再次比對一下,相等則update到next值,否則,什麼都不做。