2010-03-12 31 views
2

問題是這樣的:如何確保這種併發情況下的數據一致性?

  • 我都需要訪問一個數據庫表中的多個競爭線程(100+)
  • 每個線程將傳遞一個String name - 在該名稱的表存在,數據庫應返回該行的id,其中該名稱尚不存在,應該插入該名稱並返回該id。
  • 數據庫中只能有一個name的實例 - 即。名稱必須是唯一

如何確保一個線程在同一時間不會插入name1爲線程兩人還試圖插入name1?換句話說,我如何保證name在併發環境中的獨特性?這也需要儘可能高效 - 這可能是一個嚴重的瓶頸。

我正在使用MySQL和Java。

謝謝

回答

7

假設名稱列上有一個唯一的約束,每個insert將獲得一個鎖。任何嘗試第二次同時插入的線程都將等到第一個insert成功或失敗(tx提交或回滾)。

如果第一筆交易成功,第二筆交易將失敗並且唯一的密鑰違規。那麼你知道它已經存在。

如果每個事務有一個插入,它就是。如果每個事務有多個插入,則可能會死鎖。

每個線程將傳遞一個字符串名稱 - 如該名稱的表存在, 數據庫應該返回ID爲 行,其中名稱不 已經存在,名字應該是 插入並返回ID。

因此,所有的一切,算法中是這樣的:

1 read row with name 
    2.1 if found, return row id 
    2.2 if not found, attempt to insert 
     2.2.1 if insert succeeds, return new row id 
     2.2.2 if insert fails with unique constraint violation 
      2.2.2.1 read row with name 
      2.2.2.2 read should succeed this time, so return row id 

因爲可能有上唯一索引高爭,則insert可能會阻塞一段時間。在這種情況下,交易可能會超時。進行一些壓力測試,然後調整配置,直至其與負載正常工作。

此外,你應該檢查你是否得到一個唯一約束違規異常或一些其他異常。

而且,只有當每個事務有一個插入時,這纔有效,否則可能會出現死鎖


此外,您可以嘗試使用「select * for update」讀取第1步的行。在這種情況下,它會一直等到併發插入提交或成功。由於爭用索引,這可以稍微減少步驟2.2.2中的錯誤數量。

+0

謝謝,很好的答案 - 還有一件事,我可以檢測到在SQL中的唯一約束異常?或者我不得不讓它回到java中被檢測到? – MalcomTucker

+0

哦,還有一個問題 - 我是否認爲mysql會緩存查詢結果,所以理論上如果名稱已經插入並返回一次,我應該能夠利用緩存返回存在的名稱? – MalcomTucker

+0

抱歉,還有一個問題 - 我假設你上面概述的算法都可以在sql中完成,但是我需要手動管理表鎖嗎,還是mysql會爲我做這件事?感謝您的建議:) – MalcomTucker

3

在數據庫的名稱列上創建一個唯一的約束。

+0

實際上,它稍微複雜一些...... – ewernli

2

爲名稱列添加一個唯一約束。

+0

Plus正確檢測異常中的唯一約束問題。根據JDBC驅動程序在異常中返回的信息,這可能不是微不足道的。 Spring在它的JDBC模塊中執行此操作,因此如果您遇到困難,請仔細閱讀該代碼。 –

+0

在實踐中,它稍微複雜... – ewernli