為什么where=Version就是樂觀鎖了?
先看一段代碼,為什么下面這個代碼就是樂觀鎖了?
// 樂觀鎖更新:使用最新的version
LambdaUpdateWrapper<DistributeEvent> upd = new LambdaUpdateWrapper<>();
upd.eq(DistributeEvent::getId, currentEvent.getId())
.eq(DistributeEvent::getVersion, currentEvent.getVersion())
.set(DistributeEvent::getNodeAddress, targetAddress)
.set(DistributeEvent::getVersion, currentEvent.getVersion() + 1)
.set(DistributeEvent::getModifierTime, java.time.LocalDateTime.now());
?? 什么是樂觀鎖?
樂觀鎖是一種并發控制機制,它假設多個事務同時訪問數據時沖突的概率很低,所以不會在讀取數據時加鎖,而是在更新時檢查數據是否被其他事務修改過。
?? 樂觀鎖的核心原理
樂觀鎖的核心思想是:"我讀取數據時記住一個版本號,更新時檢查版本號是否還是原來的,如果不是說明被別人改過了"
?? 通過具體例子理解
場景:兩個節點同時想處理同一個事件
// 初始數據庫狀態
// id=123, status='PENDING', version=1, node_address=null
// === 時刻1:節點A和節點B同時讀取 ===
// 節點A讀取:{id=123, status='PENDING', version=1, node_address=null}
// 節點B讀取:{id=123, status='PENDING', version=1, node_address=null}
傳統方式(沒有樂觀鎖)會出現什么問題?
-- 節點A執行:
UPDATE distribute_event
SET status='PROCESSING', node_address='192.168.1.10:8080'
WHERE id=123 AND status='PENDING';
-- 影響行數:1 ? 成功
-- 節點B執行:
UPDATE distribute_event
SET status='PROCESSING', node_address='192.168.1.11:8080'
WHERE id=123 AND status='PENDING';
-- 影響行數:0 ? 失敗,但節點B不知道為什么失敗
問題:節點B不知道是因為沒有數據還是因為并發沖突導致的失敗。
使用樂觀鎖的方式
-- 節點A執行:
UPDATE distribute_event
SET status='PROCESSING',
node_address='192.168.1.10:8080',
version = version + 1 -- 版本號遞增!
WHERE id=123 AND version=1; -- 檢查版本號!
-- 影響行數:1 ? 成功,數據變成 {id=123, version=2, ...}
-- 節點B執行:
UPDATE distribute_event
SET status='PROCESSING',
node_address='192.168.1.11:8080',
version = version + 1
WHERE id=123 AND version=1; -- 還在用舊版本號!
-- 影響行數:0 ? 失敗,因為現在version已經是2了
優勢:節點B知道這是因為并發沖突導致的失敗,可以重新讀取數據再試。
?? 代碼中的樂觀鎖實現
1. 版本字段定義
`version` INT NOT NULL DEFAULT 1 COMMENT '樂觀鎖版本,用于并發控制'
2. 樂觀鎖更新邏輯
// 代碼中:
upd.eq(DistributeEvent::getId, evt.getId())
.eq(DistributeEvent::getVersion, evt.getVersion()) // ?? 關鍵:檢查版本號
.set(DistributeEvent::getNodeAddress, targetAddress)
.set(DistributeEvent::getModifierTime, java.time.LocalDateTime.now());
int updated = distributeEventMapper.update(null, upd);
if (updated > 0) {
success++; // ? 更新成功
} else {
skipped++; // ? 版本沖突,跳過
}
這段代碼轉換成SQL就是:
UPDATE distribute_event
SET node_address = #{targetAddress},
modifier_time = NOW()
WHERE id = #{id} AND version = #{version}; -- 同時檢查ID和版本號
?? 樂觀鎖的完整流程演示
// === 場景:事件遷移時的并發控制 ===
// 步驟1:讀取數據
DistributeEvent evt = {id: 123, version: 5, node_address: "offline-node"};
// 步驟2:樂觀鎖更新
UPDATE distribute_event
SET node_address = 'new-node',
version = 6, -- 版本號+1
modifier_time = NOW()
WHERE id = 123
AND version = 5; -- 必須匹配讀取時的版本號
// 結果分析:
// - 如果影響行數=1:說明更新成功,沒有并發沖突
// - 如果影響行數=0:說明version已經不是5了,有其他線程修改過數據
?? 樂觀鎖 vs 悲觀鎖對比
| 特性 | 樂觀鎖 | 悲觀鎖 |
|---|---|---|
| 加鎖時機 | 更新時檢查 | 讀取時就加鎖 |
| 并發性能 | 高(無鎖讀取) | 低(串行執行) |
| 適用場景 | 沖突少的場景 | 沖突多的場景 |
| 實現方式 | 版本號/時間戳 | SELECT...FOR UPDATE |
悲觀鎖的例子:
-- 悲觀鎖:先鎖定再更新
SELECT * FROM distribute_event WHERE id=123 FOR UPDATE; -- 加鎖
UPDATE distribute_event SET node_address='new-node' WHERE id=123; -- 更新
-- 事務結束時釋放鎖
樂觀鎖的例子:
-- 樂觀鎖:讀取時不加鎖,更新時檢查版本
SELECT * FROM distribute_event WHERE id=123; -- 無鎖讀取
UPDATE distribute_event
SET node_address='new-node', version=version+1
WHERE id=123 AND version=#{原版本號}; -- 更新時檢查版本
?? 總結
為什么代碼是樂觀鎖?
- 有版本字段:
version字段用于記錄數據版本 - 更新時檢查版本:
WHERE version = #{原版本} - 檢查更新結果:通過
updated > 0判斷是否沖突 - 沖突時的處理:跳過沖突的記錄
這就是典型的樂觀鎖模式:相信數據不會沖突,但在更新時驗證這個假設是否成立!

浙公網安備 33010602011771號