理解共享锁和排它锁

一曲冷凌霜 提交于 2020-02-27 14:23:31

1.共享锁 (lock in share mode)

1.1 概念

允许不同事务之前共享加锁读取,但不允许其它事务修改或者加入排他锁
如果有修改必须等待一个事务提交完成,才可以执行,容易出现死锁

1.2 例子:

共享锁事务读取

以不同的 session 来举例:

a:

start transaction;    
select * from demo where id = 1 lock in share mode;

b:

start transaction;    
select * from demo where id = 1 lock in share mode;

此时 a 和 b 都可以正常获取结果,那么再加入 c 排他锁读取尝试

c:

start transaction;    
select * from demo where id = 1 for update;

在 c 中则无法获取数据,直到超时或其它事物 commit

共享锁事务更新

a:

update demo set name = 'xxx' where id = 1;

可以很快获取执行结果。当 b 再次执行修改 id=1 的语句时:
b:

update demo set name = 'yyy' where id = 1;

就会出现死锁或者锁超时,错误如下:

Deadlock found when trying to get lock; try restarting transaction

或者:

Lock wait timeout exceeded; try restarting transaction

必须等到 a 完成 commit 动作后,b 才会正常执行,如果此时多个 session 并发执行,可想而知出现死锁的几率将会大增。

c 则更不可能

1.3应用场景

拿mysql官方文档的例子来说,一个表是child表,一个是parent表,假设child表的某一列child_id映射到parent表的c_child_id列,那么从业务角度讲,此时我直接insert一条child_id=100记录到child表是存在风险的,因为刚insert的时候可能在parent表里删除了这条c_child_id=100的记录,那么业务数据就存在不一致的风险。

正确的方法是再插入时执行select * from parent where c_child_id=100 lock in share mode,锁定了parent表的这条记录,然后执行insert into child(child_id) values (100)就ok了。

1.4 小结

  • 允许其它事务也增加共享锁读取
  • 不允许其它事物增加排他锁 (for update)
  • 当事务同时增加共享锁时候,事务的更新必须等待先执行的事务 commit 后才行,如果同时并发太大可能很容易造成死锁

共享锁,事务加多少,都能读。修改是唯一的,必须等待前一个事务 commit,才可以下个修改

2.排它锁

2.1 概念

当一个事物加入排他锁后,不允许其他事务加共享锁或者排它锁读取,更加不允许其他事务修改加锁的行。

2.2 例子

排他锁不同事务之间的读取

同样以不同的 session 来举例

a:

start transaction;
select * from demo where id = 1 for update;

b:

start transaction;
select * from demo where id = 1 for update;

当 a 执行完成后,再次执行 b,此时 b 也会卡住,无法立刻获取查询的数据。直到出现超时

Lock wait timeout exceeded; try restarting transaction

或 a commit 才会执行

那么再使用 c 加入共享锁尝试

select * from demo where id = 1 lock in share mode;

结果也是如此,和 b 一样,超时或等待 a commit

Lock wait timeout exceeded; try restarting transaction

排他锁事务之间的修改

当在 a 中执行 update 语句:

update demo set name ='xxx'  where id = 1;

可以正常获取结果,接着在 b 中执行修改

update demo set name = 'yyy' where id = 1;

则会卡住直接超时或 a commit, 才会正常吐出结果

c 也很明显和 b 一样的结果,这里就不多赘述

2.3应用场景

如果是同一张表的应用场景,举个例子,电商系统中计算一种商品的剩余数量,在产生订单之前需要确认商品数量>=1,产生订单之后应该将商品数量减1。

1 select amount from product where product_name='XX';  
2 update product set amount=amount-1 where product_name='XX';  

显然1的做法是是有问题,因为如果1查询出amount为1,但是这时正好其他session也买了该商品并产生了订单,那么amount就变成了0,那么这时第二步再执行就有问题。使用排它锁就可以避免这种问题:

select * from demo for update

2.4 小结

  • 事务之间不允许其它排他锁或共享锁读取,修改更不可能
  • 一次只能有一个排他锁执行 commit 之后,其它事务才可执行

不允许其它事务增加共享或排他锁读取。修改是唯一的,必须等待前一个事务 commit,才可以下一个修改

最后

lock in share mode适用于两张表存在业务关系时的一致性要求,for  update适用于操作同一张表时的一致性要求。

— —感谢浏览♥

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!