悲觀鎖定(Pessimistic Locking)


在多個客戶端可能讀取同一筆數據或同時更新一筆數據的情況下,必須要有訪問控制的手段,防止數據被多個並行交易同時修改而造成混亂,最簡單的手段就是對資料進行鎖 定,在自己進行資料讀取或更新等動作時,鎖定其他客戶端不能對同一筆資料進行任何的動作。

悲觀鎖定(Pessimistic Locking)一如其名稱所示,悲觀的認定每次資料存取時,其它的客戶端也會存取同一筆資料,因此對相關的資料進行鎖定,直到自己操作完成後解除鎖定。

對整個資料庫提高隔離層級,會使得並行存取時,整個應用程式的效能變差,我們想要的是,在適當的時機,再對資料庫進行鎖定,而後交易完成後解除鎖定。

可以利用Query或 Criteria的setLockMode()方法,來設定要鎖定的表或列(Row)及其鎖定模式,可設定的鎖定模式有以下的幾個:
  • LockMode.UPGRADE:在資料庫層面利用for update子句進行鎖定。
  • LockMode.UPGRADE_NOWAIT:使用for update nowait子句進行鎖定,若有其它交易嘗試進行更新則會立即失效,而不是等待上一個交易結束,在Oracle資料庫中使用。

一個設定鎖定的例子如下:
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
Query query = session.createQuery("from User user");
List users1 = query.list();
query.setLockMode("user", LockMode.UPGRADE);
...
List users2 = query.list();
...
tx.commit();
session.close();

如上設定,在這個交易期間,若有其它交易嘗試進行更新操作,則必須等待目前的交易結束,這個程式片段會使用以下的SQL進行查詢:
Hibernate: select user0_.id as id, user0_.name as name0_, user0_.age as age0_ from user user0_ for update

也可以在使用Session的lock()時指定鎖定模式以進行鎖定,例如:
User user = (User) session.load(User.class, new Long(1));
session.lock(user, LockMode.UPGRADE);

或是簡化為:
User user = (User) session.load(User.class, new Long(1), LockMode.UPGRADE);

另外還有三種LockMode:
  • LockMode.FORCE:當使用版本號進行 樂 觀鎖定 (Optimistic Locking 時,可強迫指定的物件進行版本號遞增。
  • LockMode.WRITE:在insert或update時進行鎖定,Hibernate會在進行資料寫入時自動獲得鎖定 ,您不必特別在應用程式中指定。
  • LockMode.READ:略過快取,直接檢查目前的物件與資料庫中的對應欄位資料,是否為相同版本,預設上load()與get()會使用這個LockMode。
  • LockMode.NONE:除非快取中沒有資料,才會對資料庫進行操作。

如果資料庫不支援所指定的鎖定模式,Hibernate會選擇一個合適的鎖定替換,而不是丟出一個例外。