Mysql锁系列(2):乐观锁探究

本文深入探讨了Mysql数据库中乐观锁与悲观锁的实现原理,通过具体场景如用户转账,详细解释了两者如何防止并发操作中的数据冲突。乐观锁利用版本号机制,悲观锁则依赖于X锁实现串行操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

抛出一个面试问题

Mysql如何实现乐观锁与悲观锁?

关于悲观锁已经探讨过了,传送门:Myql悲观锁

乐观锁

再次看下用户转账的场景,可能涉及多个步骤

  • 首先查询用户账户信息:select操作
  • 检测是否能进行转账:业务检查
  • 进行转账,更新账户余额等信息

假定这个操作就在一个本地事物中,看起来是不会有问题的。但是如果出现并发,就有可能出现丢失更新,如下

上图为丢失更新的一种,这种情况下是很可怕的,产生了资损。

前面已经介绍过了悲观锁的解决办法,下面看看乐观锁怎么解决这个问题

乐观锁的设计

悲观锁,顾名思义,就是每次操作的时候都要获取唯一的一把锁。Mysql里面悲观锁都是X锁,独占的,简而言之就是操作变成了串行。如果并发比较高,每次都要等待前面的操作释放,恰巧业务操作比较耗时,就可能会造成许多失败。

相对的,另一种假设每次操作都不会产生业务冲突,所以在操作中也就不加锁。不加锁的操作就为乐观锁。

那么问题来了,不加锁怎么保证操作的正确呢?

在每次更新时,都带上一个版本号!

大致就是

  • 以Mysql数据库为例,在设计表结构的时候,加上一个version字段,
    CREATE TABLE `t1` (
      `id` int(11) NOT NULL,
      `name` varchar(255) NOT NULL,
      `version` int(11) NOT NULL,
      `age` int(11) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='测试表1';
  • 每次更新的时候条件带上version,每次更新对version+1

    // 获取账户信息
    select * from account where user_id = 'XX'
    
    // 更新账户信息,同时version+1,条件里面带上前一个version
    update account set balance = yy, version = version + 1 where user_id = 'XX' and version = '' 
  • 比较更新结果是否为1,如果为1,表示更新成功,反之更新失败,根据业务规则决定是重新还是报出异常

注意上面更新的数据库字段最好是主键或者唯一索引,不然可能会造成锁表

流程图

借用网上的经典图:如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。

看到这里不知道大家有没有一个疑问,就是如果A,B同一时刻(无线接近,趋于相等)执行数据库更新操作,怎么能保证呢只有一个成功,一个失败?

个人理解是这样的,在系统时钟上来看,A,B操作肯定有一个在前,一个在后。说的同一时刻,可能是在A执行的过程中,B也开始执行了,那么为什么A执行了,B乐观更新失败呢?因为Mysql在RR隔离级别下,对update默认是加X锁的,这个时候B根本拿不到锁,也就执行不了。而B能执行时,是A已经更新完成,此时version已经发生变化,自然就失败了。

所以说的乐观锁不加锁,一般指的在业务整个过程中,不用对业务记录一直加锁,减少加锁的时间而已

应用场景

JVM中的CAS就用到了version机制来解决CAS存在ABA问题,可以视为一种乐观锁应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值