業務需求:數據庫如何保證先查詢後插入/更新 原子性?

1、業務需求:

當操做積分用戶表時,若是accountId在表中沒有數據,那麼咱們新增一條數據,設置用戶積分。若是accountId在表中有數據,咱們須要更新用戶積分。
 
這個操做簡單來講就是: 
 
在單線程下 咱們先查詢後處理固然沒有問題,可是在併發下問題就顯而易見了,系統裏可能同時插入兩條同樣的accountId數據。
 
 

2、問題解決:

 

解決方式一: ON DUPLICATE KEY UPDATE

數據庫中account_id設置惟一索引,當發現account__id已經存在時,會執行update操做,不存在時會執行insert操做。
一行sql語句就能完成兩種操做,保證了原子性。
 
sql語句以下:
 
添加單元測試,查看耗時以及查驗數據庫在併發下數據是否正確。
代碼隱去業務代碼,以下:
 
查看打印的日誌,共耗時:22690ms
數據庫數據可以保持正確性
 

解決方式二: 使用分佈式鎖

這個耗時比第一種方式差不少,因此沒有測試完就放棄了。
 
由於高併發的狀況下 鎖的搶佔很激烈,這裏不少時間都耗費在鎖的搶佔上,沒有搶佔到鎖的線程須要重試而不能失敗,相似於CAS操做,因此這種方式不適合當前業務。
 

解決方式三: INSERT INTO SELECT

此種方式也是最優的,耗時:20010ms
sql語句以下:
 
查詢accountId不存在時結果:
 
查詢accountId存在時結果:
 
這裏須要注意的是,此sql語句在Mapper.xml中是insert語句:
 
 

3、原理分析

一、ON DUPLICATE KEY UPDATE
 
mysql "ON DUPLICATE KEY UPDATE" 語法:
若是在INSERT語句末尾指定了ON DUPLICATE KEY UPDATE,而且插入行後會致使在一個UNIQUE索引或PRIMARY KEY中出現重複值,則在出現重複值的行執行UPDATE;若是不會致使惟一值列重複的問題,則插入新行。
 
 
二、 INSERT INTO SELECT
 
INSERT INTO SELECT 語句從一個表複製數據,而後把數據插入到一個已存在的表中。目標表中任何已存在的行都不會受影響。
 
其中使用到了dual虛擬表, 根據mysql的官方定義:
DUAL is purely for the convenience of people who require that all SELECT statements should have FROM and possibly other clauses. MySQL may ignore the clauses. MySQL does not require FROM DUAL if no tables are referenced.
 
官方的解釋說:純粹是爲了知足select … from…這一習慣問題,mysql會忽略對該表的引用。因此上面的語句from dual能夠去掉。
簡言之,from dual徹底是一個無關緊要的東西。只是爲了方便使用select 語句中喜歡帶上from的開發者。
例如咱們使用select 1 查詢等價於select 1 from dual
 

4、總結

到了這裏就分析完了,若是你們有更好的解決方案也能夠拿出來學習下,文中若有問題懇請你們指正一下。
第一種方式會有一個id不是連續自增的問題,具體能夠參考文章: http://www.javashuo.com/article/p-rrmkodiz-gr.html
相關文章
相關標籤/搜索