2015-01-15 36 views
6

假設我有兩個不同的線程T1和T2,它們同時訪問同一個數據庫並從同一個表中獲取數據。如何避免兩個不同的線程從數據庫讀取相同的行(Hibernate和Oracle 10g)

現在在線程啓動時,我需要從表中獲取數據並將行存儲到集合中,然後我將使用它們在別處執行某些工作。我不希望這兩個線程能夠處理相同的數據,因爲它會導致重複(和長時間)的工作。更具體地說,這是一個企業應用程序,需要在啓動時加載一些記錄並將其存儲在集合中以完成一些額外的工作。問題是在集羣環境中,這可能會導致兩個不同的實例加載相同的數據,因此工作可能會重複。所以我希望行只能由一個實例加載一次。

我該如何避免這種情況?

我目前使用Hibernate和Oracle 10g。這些是我的解決方案到現在:

  • 以編程方式鎖定行。第一個讀取它會將一些「鎖定」列設置爲true,但如果第一個線程死亡而沒有將行設置爲「已處理」,很可能會發生死鎖。

  • 使用悲觀鎖定。我嘗試使用LockMode.UPGRADE,但似乎沒有幫助,因爲我仍然能夠同時從兩個線程讀取數據。

public List<MyObject> getAllNtfFromDb() { 
     Session session = HibernateUtil.getOraclesessionfactory().openSession(); 
     Query q = session.createQuery(
       "from MyObject n where n.state = 'NEW'"); 
    List<MyObject> list = (List<MyObject>) q.list(); 
     for (int i=0; i<list.size(); i++) 
      session.lock(list.get(i), LockMode.UPGRADE); 
return list; 
} 

任何其他提示嗎?我究竟做錯了什麼?

謝謝。

+1

也許你可以使用select for update語句? – soilworker 2015-01-15 17:09:29

回答

6

你需要在查詢時使用PESSIMISTIC_WRITE

Query q = session 
    .createQuery("from MyObject n where n.state = 'NEW'") 
    .setLockOptions(new LockOptions(LockMode.PESSIMISTIC_WRITE)); 
List<MyObject> list = (List<MyObject>) q.list(); 

鎖定父對象就足夠了。僵局不一定會發生。如果持有鎖的線程在另一個線程超時等待之前沒有釋放鎖,則可能會收到鎖獲取失敗。

由於您使用的是Oracle,這是SELECT FOR UPDATE是如何工作的:

SELECT ... FOR UPDATE鎖定行和任何相關的索引條目 ,因爲如果你發出的那些行的UPDATE語句相同。 阻止其他交易更新這些行,從做 SELECT ...鎖定共享模式,或者通過讀取某些事務隔離級別中的數據。一致的讀取將忽略在讀取視圖中存在的記錄上設置的任何鎖定。 (記錄 的舊版本不能被鎖定;他們通過應用撤消紀錄的 內存副本日誌重建)

因此,如果T1上的一些行獲得的排他鎖,T2贏得」無法讀取這些記錄,直到T1提交或回滾。如果T2使用了一個READ_UNCOMMITTED隔離級別,那麼T2永遠不會阻塞鎖定記錄,因爲它只是使用撤消日誌來重新構建數據,就像查詢開始時一樣。相對於SQL標準,該Oracke READ_UNCOMMITTED會:

爲了提供一個一致的,或不正確,答案,Oracle數據庫將 創建一個包含此行,因爲它存在時 查詢開始塊的副本。實際上,Oracle數據庫繞着 修改後的數據繞過它,從撤消 (也稱爲回滾)段重新構建它。返回一致且正確的答案 而不等待事務提交。

+1

好的,謝謝,這是行得通的,但只有當兩個線程試圖在相近的時間讀取相同的數據。如果T1讀取數據,並且在1分鐘後T2讀取相同的數據(其中T1很可能還在處理中),則T2能夠看到這些行。 我想鎖定行直到它們被更新(例如狀態列設置爲「OK」或「PROCESSED」)。 此外,是否更新一個實體自動釋放行?如果會話關閉或線程崩潰是在某些超時後釋放的行? 非常感謝。 – user2799534 2015-01-16 10:26:28

+0

它可能取決於底層數據庫獨佔鎖支持。 – 2015-01-16 11:43:05

相關問題