持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第46天,点击查看活动详情
从广义上来讲,前面提到的行锁、表锁、读锁、写锁、共享锁、排他锁等,这些都属于悲观锁范畴. 接下来我们聊一下乐观锁:
- 乐观锁是相对于悲观锁而言的,它不是数据库提供的功能,需要开发者自己去实现。在数据库操作时,想法很乐观,认为这次的操作不会导致冲突,因此在数据库操作时并不做任何的特殊处理,即不加锁,而是在进行事务提交时再去判断是否有冲突了。
乐观锁实现方式
-
乐观锁的概念中其实已经阐述了它的具体实现细节。主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是CAS(Compare and Swap)。
Compare And Set(CAS),是一种常见的降低读写锁冲突,保证数据一致性的乐观锁机制。
简单理解: 当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
-
比如一个扣减库存的操作,通过乐观锁可以进行实现:
-- 查询商品库存信息,查询结果为: quantity = 3 select quantity from items where id = 1; -- 修改商品库存为2 update items set quantity=2 where id = 1 and quantity = 3; -
以上,我们在更新之前,先查询一下库存表中当前库存数(quantity),然后在做update的时候,以库存数作为一个修改条件。当我们提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。
-
CAS乐观锁机制确实能够提升吞吐,并保证一致性,但在极端情况下可能会出现ABA问题。
- 并发线程1(上):获取出数据的初始值是3,后续计划实施CAS乐观锁,期望数据仍是3的时候,修改才能成功
- 并发线程2:将数据修改成2
- 并发线程3:将数据修改回3
- 并发线程1(下):CAS乐观锁,检测发现初始值还是3,进行数据修改
-
上述并发环境下,并发1在修改数据时,虽然还是3,但已经不是初始条件的3了,中间发生了A变B,B又变A的变化,此A已经非彼A,数据却成功修改,可能导致错误,这就是CAS引发的所谓的ABA问题。
-
解决ABA问题的方法: 设计一个单独的可以顺序递增的version字段,每操作一次,将那条记录的版本号加 1. version 是用来查看被读的记录有无变化,作用是防止记录在业务处理期间被其他事务修改。以下单操作为例:
-- 1.查询出商品信息 version=1 select (quantity,version) from items where id = 1; -- 2.根据商品生成订单 insert into orders ... insert into items ... -- 3.修改商品库存,同时递增版本字段值 update products set quantity=quantity-1,version=version+1 where id=1 and version=#{version};