悲观锁
当我们要对数据库中的⼀条数据进⾏修改的时候,为了避免同时被其他⼈修改,最
好的办法就是直接对该数据进⾏加锁以防⽌并发。这种借助数据库锁机制在修改数
据之前锁定,再修改的⽅式被称为悲观并发控制(PCC)。
之所以叫做悲观锁,是因为抱有悲观的态度去修改数据的并发控制⽅式,认为数据
并发修改的概率⽐较⼤,所以需要在修改之前先加锁。
悲观并发控制实际上是 “先取锁,再访问” 的保守策略,为数据处理的安全提供了
保证。
在效率上,处理加锁的机制会让数据库产⽣额外的开销,还会有死锁的可能性。降
低并⾏性,⼀个事务如果锁定了某⾏数据,其他事务就必须等待该事务处理完才可
以处理那⾏数据。
悲观锁的实现⽅式:悲观锁的实现,依靠数据库提供的锁机制。
在数据库中,悲观锁的流程如下:
在对数据修改前,尝试增加排他锁。
加锁失败,意味着数据正在被修改,进⾏等待或者抛出异常。
加锁成功,对数据进⾏修改,提交事务,锁释放。
如果我们加锁成功,有其他线程对该数据进操作或者加排他锁的操作,只能
等待或者抛出异常。
乐观锁
乐观锁是相对悲观锁⽽⾔的,乐观锁假设数据⼀般情况下不会造成冲突,所以在数据进⾏提交更新的时候,才会正式对数据的冲突与否进⾏检测。
相对于悲观锁,在数据库进⾏处理的时候,乐观锁不会使⽤数据库提供的锁机制,
⼀般是增加 version 参数,记录数据版本
乐观并发控制相信事务之间的数据竞争概率⾮常⼩,因此尽可能直接操作,提交的
时候才去锁定,不会产⽣任何锁和死锁。
上⼿试⼀试
基于 MySQL InnoDB 引擎
使⽤悲观锁
begin;
select quantity from products where id = 1 for update;
update products set quanntity = 2 where id = 1;
commit;
以上,对 id 为 1 的产品进⾏修改,先通过 for update 的⽅式进⾏加锁,然后再修
改。典型的悲观锁策略。
如果修改库存的逻辑发⽣并发,同⼀时间只有⼀个线程可以开启事务并获得 id = 1
的锁,其他事务必须等本次提交之后才能执⾏,这样可以保证数据不被其他事务修
改。
使⽤排他锁会把数据锁住,不过需要注意⼀些基本的锁级别,MySQL InnoDB 默认⾏级锁。⾏级锁是基于索引的,如果⼀条 SQL 语句⽤不到索引是不会使⽤⾏级
锁,会使⽤表级锁把整张表锁住。
使⽤乐观锁
select quantity from products where id = 1
update products set quantity = 2 where id = 1 and quantity = 3
先查询库存表当前库存数,然后更新的时候判断数据表对应数据的 quantity 与第
⼀次取出来的是否⼀致,⼀致则更新,否则认为是过期数据。
这样实现有⼀个问题,线程 1 从数据库取出 quantity 为 3,线程 2 也取出同⼀条
数据的 quantity,进⾏操作,变成了 2,然后⼜进⾏某些操作 变成了 3,此时线程
- 进⾏更新操作成功。但是这个过程有问题。
引⼊ version 参数,乐观锁每次在执⾏数据修改的操作,都会带上版本号,⼀旦版
本号和数据的版本号⼀致就可以执⾏修改操作并对 version 执⾏ +1 操作,否则就
执⾏失败。
这样实现也有⼀个问题,如果真的有⾼并发的时候,就只有⼀个线程可以修改成
功,就会存在⼤量的失败。
如果你的应⽤存在超⾼并发,这样解决也不好,因为会总让⽤户感知到失败。
尝试减⼩乐观锁⼒度,最⼤程度提⾼吞吐。
update products set quantity = quantity - 1 where id = 1
and quantity - 1 > 0
使⽤这条 SQL 语句,在执⾏过程中,会在⼀次原⼦操作中查询⼀遍 quantity 的
值,并且减去 1。
简述区别
-
乐观锁不是真的加锁,效率低,性能高,但是要控制好锁的⼒度。
-
悲观锁依赖数据库锁,效率高,性能低。
总结
⽆论是悲观锁还是乐观锁,都是⼈们定义出来的概念,可以认为是⼀种思想。
⼤家要记住锁机制⼀定要在事务中才能⽣效哦。
以上是我对乐观锁与悲观锁⼀点基础实践,希望能和⼤家再深⼊了解了解。