*分布式锁一般有三种实现方式:1. 数据库锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
基于Redis的分布式锁;
一、悲观锁
1、排它锁,当事务在操作数据时把这部分数据进行锁定,直到操作完毕后再解锁,其他事务操作才可操作该部分数据。这将防止其他进程读取或修改表中的数据。
2、实现:大多数情况下依靠数据库的锁机制实现
一般使用 select ...for update 对所选择的数据进行加锁处理,例如select * from account where name=”Max” for update, 这条sql 语句锁定了account 表中所有符合检索条件(name=”Max”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
二、乐观锁
1、如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户重新操作。
2、实现:大多数基于数据版本(Version)记录机制实现
具体可通过给表加一个版本号或时间戳字段实现,当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断当前版本信息与第一次取出来的版本值大小,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据,拒绝更新,让用户重新操作。
乐观锁的实现:
使用版本控制字段,再利用行锁的特性实现乐观锁,如下
有一张订单表order,有字段id、order_no、 price, 为实现乐观锁控制,添加version字段,默认值为0
order
id 1
order_no 123456
price 5
version 0
假设两个人同时进来修改该条数据,操作为:
1. 先查询该数据 select * from order where id = 1
2. 修改该条数据 update order set price = 1 where id = 1
如果两个人同时查询到该条数据price = 5, 可以执行update操作, 但任意一方还没执行update操作,那么最后双方都执行update,导致数据被修改两次,产生脏数据 !
使用version字段控制版本后:
-
两人先查询该数据 select * from order where id = 1
此时两人查询到的数据一样,
id = 1, price = 5, order_no = 123456, version = 0
-
两人都发现该条数据price = 5, 符合update条件,第一人执行update(因为mysql行锁的特性,两人不可能同时修改一条数据,所以update同一条数据的时候,是有先后顺序的,只有在第一个执行完update,才能释放行锁,第二个继续进行update):
update order set price = 1, version = version + 1 where id = 1 and version = 0
执行完成后,version字段值将变成1, 第二人执行update:
update order set price = 1, version = version + 1 where id = 1 and version = 0
此时的version的值已经被修改为1,所以第二人修改失败,实现乐观锁控制。
死锁的处理:
数据库使用乐观锁导致产生死锁:
事务A
update order set price = 1 where id = 1
update order set price = 2 where id = 2
事务B
update order set price = 1 where id = 2
update order set price = 2 where id = 1
假设在两个事务中有以上两个操作,同时修改order表中两条数据
事务A在执行完第一条update的时候,刚好事务B也执行完第一条update
此时, 事务A中order表中的id = 1的行被锁住, 事务B中order表中id = 2的行被锁住,两个事务继续往下执行
事务A中第二条update执行需要order表中id = 2的行数据,而事务B中第二条update执行需要id = 1的行数据, 两条update往下执行的条件都需要对方事务中已经被锁住的行,于是陷入无限等待,形成死锁。
解决死锁的产生:
指定锁的执行顺序,比如把以上两事务稍作修改
事务A
update order set price = 2 where id = 2
update order set price = 1 where id = 1
事务B
update order set price = 1 where id = 2
update order set price = 2 where id = 1
事务A执行第一条update时,id = 2 的行被锁住,此时,事务B想修改id = 2的行,只能等待事务A执行完成,当事务A执行完成时,事务B再执行, 这样就不会产生死锁了。