补充九:乐观锁、悲观锁
参考博客:
https://www.cnblogs.com/chenpingzhao/archive/2015/12/13/5041967.html
https://www.aliyun.com/jiaocheng/1112687.html
https://www.jianshu.com/p/315b7de976b8
https://mp.weixin.qq.com/s/xMoQk99N2gyz7ftBfcTLGQ
https://www.cnblogs.com/phpper/p/7345332.html
https://blog.csdn.net/xlgen157387/article/details/47906553
各种锁:https://blog.csdn.net/puhaiyang/article/details/72284702
秒杀:https://blog.csdn.net/zsvole/article/details/79099498
https://blog.csdn.net/u012557298/article/details/54917225
InnoDB对数据行的锁定类型一共有四种:共享锁(读锁,S锁)、排他锁(写锁,X锁)、意向共享锁(IS锁)和意向排他锁(IX锁),支持三种行锁定方式:
- 行锁(Record Lock):锁直接加在索引记录上面。
- 间隙锁(Gap Lock):锁加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引之后的空间。
- Next-Key Lock:行锁与间隙锁组合起来用就叫做Next-Key Lock。
默认情况下,InnoDB工作在可重复读隔离级别下,并且以Next-Key Lock的方式对数据行进行加锁,这样可以有效防止幻读的发生。Next-Key Lock是行锁与间隙锁的组合,这样,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。如果一个间隙被事务T1加了锁,其它事务是不能在这个间隙插入记录的。
- 在可重复读级别下,InnoDB以Next-Key Lock的方式对索引加锁;在读已提交级别下,InnoDB以Index-Record Lock的方式对索引加锁。
- 被加锁的索引如果不是聚族索引,那被锁的索引所指向的聚族索引以及其它指向相同聚族索引的索引也会被加锁。
- SELECT * FROM ... LOCK IN SHARE MODE对索引加共享锁;SELECT * FROM ... FOR UPDATE对索引加排他锁。
- SELECT * FROM ... 是非阻塞式读,(除Serializable级别)不会对索引加锁。在读已提交级别下,总是查询记录的最新、有效的版本;在可重复读级别下,会记住第一次查询时的版本,之后的查询会基于该版本。例外的情况是在串行化级别,这时会以Next-Key Lock的方式对索引加共享锁。
- UPDATE ... WHERE 与DELETE ... WHERE对索引加排他锁。
- INSERT INTO ... 以Index-Record Lock的方式对索引加排他锁
悲观锁:一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放.
悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。
通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update排它锁或者select ...lock in share mode共享锁或者delete.....update.....insert into....语句(这些语句在事务中,会使用排它锁),来实现悲观锁。
乐观锁:一段执行逻辑加上乐观锁,不同线程同时执行时,可以同时进入执行,在最后更新数据的时候要检查这些数据是否被其他线程修改了(版本和执行初是否相同),没有修改则进行更新,否则放弃本次操作.
乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。
乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号,或者时间戳
什么是悲观锁
在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。
读取数据时给加锁,其它事务无法修改这些数据,但其它事务可以读取。
修改删除数据时也要加锁,其它事务无法读取这些数据。
悲观锁(pessimistic locking)体现了一种谨慎的处事态度。其流程如下:
- 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)
- 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常
- 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了
- 其间如果有其他操作对该记录做修改或加排他锁,都会等待我们解锁或直接抛出异常
悲观锁确实很严谨,有效保证了数据的一致性,在C/S应用上有诸多成熟方案。
但是他的缺点与优点一样的明显
- 悲观锁适用于可靠的持续性连接,诸如C/S应用。 对于Web应用的HTTP连接,先天不适用。
- 锁的使用意味着性能的损耗,在高并发、锁定持续时间长的情况下,尤其严重。 Web应用的性能瓶颈多在数据库处,使用悲观锁,进一步收紧了瓶颈。
- 非正常中止情况下的解锁机制,设计和实现起来很麻烦,成本还很高
- 不够严谨的设计下,可能产生莫名其妙的,不易被发现的死锁问题
示例:
要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交
mysql > set @@autocommit=0;
mysql> select @@autocommit;
Connect a:
mysql>
begin
;
Query OK, 0
rows
affected (0.00 sec)
mysql>
select
age
from
users
where
id =1
for
update
;
+
-----+
| age |
+
-----+
| 24 |
+
-----+
1 row
in
set
(0.00 sec)
mysql>
update
users
set
age = 25
where
id =1;
Query OK, 1 row affected (0.00 sec)
Rows
matched: 1 Changed: 1 Warnings: 0
mysql>
commit
;
Query OK, 0
rows
affected (0.01 sec)
Connect b: 在a长时间未提交的时候
mysql>
select
*
from
users
where
id =1
for
update
;
ERROR 1205 : Lock wait timeout exceeded; try restarting
transaction
mysql>
update
users
set
age = 22
where
id =1;
ERROR 1205 : Lock wait timeout exceeded; try restarting
transaction
connect b 只有在 connect a 提交之后才会执行,否则会一直等待
在事务中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 会等待其它事务结束后才执行,一般SELECT ... 则不受此影响
乐观锁
在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。
它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。
在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。
如果其他事务有更新的话,正在提交的事务会进行回滚。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。
实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。一般的实现乐观锁的方式就是记录数据版本。
示例:
- 1.查询出商品信息 select (status,status,version) from t_goods where id=#{id}
- 2.根据商品信息生成订单
- 3.修改商品status为2 update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
优点与不足 乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
示例:
- 1. SELECT data AS old_data, version AS old_version FROM …;
- 2. 根据获取的数据进行业务操作,得到new_data和new_version
- 3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
- if (updated row > 0) {
- // 乐观锁获取成功,操作完成
- } else {
- // 乐观锁获取失败,回滚并重试
- }
乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能
posted on 2018-10-17 15:55 myworldworld 阅读(149) 评论(0) 收藏 举报