白话理解乐观锁和悲观锁

987c6f37698e563c7ac55ad1f6277dc0ec9.jpg

874539c23cea8d6fabb935ed45f71d6b219.jpg

9fd8670e28cd14fca56bbd5448adb0c4f29.jpg

bb0ac538bf902da9fddaa0504f060a7154a.jpg

 

一、并发控制

当程序中可能出现并发的情况时,我们就需要通过一定的手段来保证在并发情况下数据的准确性,通过这种手段保证了当用户和其他用户一起操作时,所得到的结果和他单独操作时的祷告的结果是一样的。

这种手段就叫做并发控制并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。

没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。

db46cd12712f3dd7d75eb3150d53b35807e.jpg

我们常说的并发控制,一般都和数据库管理系统(DBMS)有关,在 DBMS 中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。

d9149c638dfba7f4fd6a5ae8836abdc0215.jpg

实现并发控制的主要手段大致可以分为乐观并发控制和悲观并发控制两种。

245cd6603608ec30b71e5241824957d25b6.jpg

在开始介绍之前要明确一下:无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。

其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像 Memcache、Hibernate、Tair 等都有类似的概念。所以,不应该拿乐观锁、悲观锁和其他的数据库锁等进行对比。

二、悲观锁

当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。

这种借助数据库锁机制在修改数据之前先锁定,再修改的方式被称之为悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)。

之所以叫做悲观锁,是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。

f615e733e5c6d4aa17c58a2e9bcd5d09396.jpg

但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。

另外,还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

b7621e6b7b4239559e09ad32c43f9fd81b5.jpg

三、乐观锁

乐观锁( Optimistic Locking ) 是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突。

所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制一般的实现乐观锁的方式就是记录数据版本

5055c9d85235abdfbdad8563e3fc4aa6dde.jpg

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

cc4704b07a2da41b20da47cde135e4c94f8.jpg

f345f91b01113b467cad8dc1c0133d57279.jpg

四、悲观锁实现方式

悲观锁的实现,往往依靠数据库提供的锁机制。在数据库中,悲观锁的流程如下:

  • 在对记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)

  • 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。

  • 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。

  • 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

我们拿比较常用的 MySQL Innodb 引擎举例,来说明一下在 SQL 中如何使用悲观锁。

要使用悲观锁,我们必须关闭 MySQL 数据库的自动提交属性,因为 MySQL 默认使用 Autocommit 模式。

也就是说,当你执行一个更新操作后,MySQL 会立刻将结果进行提交。set autocommit=0。

d30b6b9028479850077edbd7015edf4c8e7.jpg

以上,在对 id=1 的记录修改前,先通过 for update 的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略

如果以上修改库存的代码发生并发,同一时间只有一个线程可以开启事务并获得 id=1 的锁,其他的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其他事务修改。

上面我们提到,使用 select…for update 会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB 默认行级锁

行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

81954138c608281f45af2ddb7d013723ba6.jpg

五、乐观锁实现方式

使用乐观锁就不需要借助数据库的锁机制了。乐观锁的概念中其实已经阐述了它的具体实现细节,主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是 Compare and Swap(CAS)。

CAS 是项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

1aadf8c42f6c036a46a61480ee294102c12.jpg

以上,我们在更新之前,先查询一下库存表中当前库存数(quantity),然后在做 update 的时候,以库存数作为一个修改条件。

当我们提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。

248fa10d1cc88330f4b3375069142931012.jpg

以上更新语句存在一个比较重要的问题,即传说中的 ABA 问题

比如说一个线程 one 从数据库中取出库存数 3,这时候另一个线程 two 也从数据库中取出库存数 3,并且 two 进行了一些操作变成了 2。

然后 two 又将库存数变成 3,这时候线程 one 进行 CAS 操作发现数据库中仍然是 3,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。

511084896d55c26d44231d17799d2be65f5.jpg

有一个比较好的办法可以解决 ABA 问题,那就是通过一个单独的可以顺序递增的 version 字段

91268baceaacbb992cd33f31907bcbc2ba0.jpg

乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行 +1 操作,否则就执行失败。

因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题,因为版本号只会增加不会减少。

9428a7956da5bb3fd99da9d72c06901a349.jpg

除了 version 以外,还可以使用时间戳,因为时间戳天然具有顺序递增性。

2d84683474d70af440c8be6ede619322538.jpg

207a8d522fd7ef3de243b6444e0191689a2.jpg

以上 SQL 其实还是有一定的问题的,就是一旦发上高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。

对于像淘宝这样的电商网站,高并发是常有的事,总让用户感知到失败显然是不合理的。所以,还是要想办法减少乐观锁的粒度的。

1d33c8fa298627fd6a819200fc0bb28db8b.jpg

以上 update 语句,在执行过程中,会在一次原子操作中自己查询一遍 quantity 的值,并将其扣减掉 1。

高并发环境下锁粒度把控是一门重要的学问,选择一个好的锁,在保证数据安全的情况下,可以大大提升吞吐率,进而提升性能。

d00d80fae96c3c9bc6880c0d11f54221f88.jpg

290a6229a9926fe67b6fe049f3db8f6da49.jpg

六、如何选择

在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了:

  • 乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。

  • 悲观锁依赖数据库锁,效率低。更新失败的概率比较低。

 

随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被使用到生产环境中了,尤其是并发量比较大的业务场景。

9ec303dd237a4492fc976e02ac44106581c.jpg

转载于:https://my.oschina.net/hanchao/blog/3057429

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值