持久層框架 (Hibernate/JPA) 已封裝大量細節,但理解底層機制是排查死鎖、失敗重試、數據不一致的前提。本文結合 Hibernate/JPA 標準,系統梳理四大策略。
| 隔離級別 | 髒讀 | 不可重複讀 | 幻影讀 | 性能/並發 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | 最高/最好 |
| READ COMMITTED (多數 DB 默認) | ❌ | ✅ 可能 | ✅ 可能 | 高/好 |
| REPEATABLE READ (MySQL 默認) | ❌ | ❌ | ✅ 可能 | 中/中 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 最低/最差 |
工程共識:採用 READ COMMITTED + 應用層並發控制策略,平衡性能與正確性。SERIALIZABLE 視為核選項。
核心邏輯:更新前檢查「讀取後數據是否被修改」,若被修改則拋異常回滾重試。
@Version int version,更新時 WHERE version = ?,成功則 version++;推薦;lastModified,集群時鐘同步風險、高併發可能碰撞;WHERE col1=? AND col2=? ...,字段多時 SQL 膨脹。SELECT version, ... FROM Account WHERE id = ?;
UPDATE Account SET balance=?, version=version+1 WHERE id=? AND version=?;
if (rowsUpdated == 0) throw new OptimisticLockingFailureException();
@Entity
public class Account {
@Id Long id;
@Version int version; // 或 @Version Timestamp lastModified
// ...
}
Hibernate 自動生成帶版本檢查的 SQL,衝突拋 StaleObjectStateException (JPA 為 OptimisticLockException)。
樂觀鎖不加鎖,故 無法防止不可重複讀 (READ COMMITTED 下)。業務必須容忍「讀取過程中數據被他人修改」的現象。
核心邏輯:SELECT ... FOR UPDATE 讀取時加排他鎖,他事務更新/讀取(若也 FOR UPDATE)阻塞至提交/回滾。
SELECT * FROM Account WHERE id = ? FOR UPDATE;
缺點:所有衝突事務必須一致使用 FOR UPDATE,否則失效。
// 方式 1: 先查後鎖
session.lock(account, LockMode.UPGRADE); // 或 UPGRADE_NO_WAIT
// 方式 2: 查詢時直接加鎖
session.get(Account.class, id, LockMode.UPGRADE);
Hibernate 內部轉為 SELECT ... FOR UPDATE。JPA 對應 LockModeType.PESSIMISTIC_WRITE。
悲觀鎖解決不可重複讀,但無法防止幻影讀 (需 SERIALIZABLE)。且長事務持有鎖易導致死鎖、並發度下降。
| 策略 | 核心思想 | 適用場景 | 實現要點 |
|---|---|---|---|
| 樂觀離線鎖 | 實體攜帶版本號跨請求傳遞,提交時檢查版本 | Web 編輯頁面、長業務流程、用戶思考時間長 | 表單隱藏域攜帶 version,提交時合併檢查;Hibernate 自動支援脫管實體合併 |
| 悲觀離線鎖 | 編輯開始獲取邏輯鎖 (資料庫鎖表/專用鎖表/Redis 分布式鎖),編輯結束釋放 | 極高衝突、業務不允許覆蓋、需強一致性 | 鎖超時機制必備,防止用戶離開不釋放;分布式環境用 Redisson/Redlock |
@Version 字段覆蓋 90% 場景;LockMode.UPGRADE) 或應用層分布式鎖;session.merge() 自動攜帶版本檢查,或手工 DTO 携帶 version;lock_timeout;附錄:原文末尾出現 C++/Java 語言對比片段,屬誤粘內容,已剔除。並發控制無語言邊界,原理通用。