为什么where=Version就是乐观锁了?

先看一段代码,为什么下面这个代码就是乐观锁了?

less 复制代码
   // 乐观锁更新:使用最新的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());

🎯 什么是乐观锁?

乐观锁 是一种并发控制机制,它假设多个事务同时访问数据时冲突的概率很低 ,所以不会在读取数据时加锁,而是在更新时检查数据是否被其他事务修改过

🔍 乐观锁的核心原理

乐观锁的核心思想是: "我读取数据时记住一个版本号,更新时检查版本号是否还是原来的,如果不是说明被别人改过了"

📖 通过具体例子理解

场景:两个节点同时想处理同一个事件

ini 复制代码
// 初始数据库状态
// 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}

传统方式(没有乐观锁)会出现什么问题?

ini 复制代码
-- 节点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不知道是因为没有数据还是因为并发冲突导致的失败。

使用乐观锁的方式

ini 复制代码
-- 节点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. 版本字段定义

sql 复制代码
`version` INT NOT NULL DEFAULT 1 COMMENT '乐观锁版本,用于并发控制'

2. 乐观锁更新逻辑

perl 复制代码
// 代码中:
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就是:

ini 复制代码
UPDATE distribute_event 
SET node_address = #{targetAddress}, 
    modifier_time = NOW()
WHERE id = #{id} AND version = #{version};  -- 同时检查ID和版本号

🎪 乐观锁的完整流程演示

ini 复制代码
// === 场景:事件迁移时的并发控制 ===
​
// 步骤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

悲观锁的例子:

sql 复制代码
-- 悲观锁:先锁定再更新
SELECT * FROM distribute_event WHERE id=123 FOR UPDATE;  -- 加锁
UPDATE distribute_event SET node_address='new-node' WHERE id=123;  -- 更新
-- 事务结束时释放锁

乐观锁的例子:

sql 复制代码
-- 乐观锁:读取时不加锁,更新时检查版本
SELECT * FROM distribute_event WHERE id=123;  -- 无锁读取
UPDATE distribute_event 
SET node_address='new-node', version=version+1 
WHERE id=123 AND version=#{原版本号};  -- 更新时检查版本

🎯 总结

为什么代码是乐观锁?

  1. 有版本字段version 字段用于记录数据版本
  2. 更新时检查版本WHERE version = #{原版本}
  3. 检查更新结果 :通过 updated > 0 判断是否冲突
  4. 冲突时的处理:跳过冲突的记录

这就是典型的乐观锁模式:相信数据不会冲突,但在更新时验证这个假设是否成立!

相关推荐
程序员爱钓鱼2 小时前
Python编程实战 - 函数与模块化编程 - 参数与返回值
后端·python·ipython
程序员爱钓鱼2 小时前
Python编程实战 - 函数与模块化编程 - 局部变量与全局变量
后端·python·ipython
摇滚侠5 小时前
Spring Boot3零基础教程,KafkaTemplate 发送消息,笔记77
java·spring boot·笔记·后端·kafka
马丁的代码日记7 小时前
MySQL InnoDB 行锁与死锁排查实战演示
数据库·mysql
计算机学长felix8 小时前
基于SpringBoot的“面向校园的助力跑腿系统”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
紫荆鱼9 小时前
设计模式-迭代器模式(Iterator)
c++·后端·设计模式·迭代器模式
运维小文9 小时前
MySQL高可用方案MIC&mysqlCluster+mysqlRouter
数据库·mysql·mic·mysql高可用·mysqlcluster·mysqlrouter
RainSky_10 小时前
LNMP 一键安装包部署 Django 项目
后端·django·1024程序员节
IT教程资源C10 小时前
(N_157)基于springboot,vue服装商城系统
mysql·vue3·前后端分离·springboot服装商城
mapbar_front10 小时前
职场中遇到领导针对你怎么办?
程序员