数据库悲观锁实现分布式锁

核心提示分布式锁实现方式~DB本节重点分布式锁实现方式悲观锁无事务的for update演练有事务的for update演练乐观锁加锁原理解锁原理实现代码锁正确使用实战演练总结参考资料本节重点数据库分布式锁实现原理掌握悲观锁和乐观锁分布式锁实现方式

分布式锁实现~DB本节重点介绍分布式锁实现。悲观锁、无事务更新钻、事务更新钻、乐观锁锁原理、解锁原理、正确使用密码锁、实用钻参考资料总结

本节的重点

分布式数据库锁的实现原理

掌握悲观锁和乐观锁。

分布式锁实现模式

悲观锁

悲观锁是在数据被修改之前锁定要修改的数据,以防止并发修改。一般通过锁定更新来实现,使用时需要注意以下两点:

首先开始交易

在for update语句中的where条件字段上创建索引,因为它被索引锁定,否则整个表都将被锁定。

用于更新演练的无事务

模拟100个用户抢购商品,商品销售数量加1,查看数据库中的销售数量是否为100。代码如下:

@ DS @ override public base response testLock { good info good info = base mapper . select one . eq。最后);good info . setsalecount+1);整数result = base mapper . update byid;if > 0){ log . warn;返回base response . ok);} else { log.warn返回base response . ok);}}

结果:记录“测试锁成功!”印了100张,在库销量却是2张,明显和我们预期的结果不符。

有针对事务的更新演练。

@ DS @ Transactional @ override public base response testLock { good info good info = base mapper . select one . eq。最后);good info . setsalecount+1);整数result = base mapper . update byid;if > 0){ log . warn;返回base response . ok);} else { log.warn返回base response . ok);}}

结果:记录“测试锁成功!”印了100张,在库销售的数量是100张,显然和我们的预期结果一致。

在实际开发中,很少使用悲观锁来阻止并发操作,因为如果这种方法控制不当,往往会出现死锁。

乐观锁定

乐观锁是相对于悲观锁而言的,假设数据一般不会冲突,所以在提交数据时会进行数据冲突检查。如果发送的数据有冲突,用户将决定如何操作。乐观锁定通常由数据版本机制实现。什么是数据版本?数据是向表中添加一个版本字段。读取数据时,版本字段同时被读出。每更新一次数据,这个版本值就加1。当我们提交数据时,我们会将表中相应记录的版本与取出的版本进行比较。如果一致,则更新成功,否则更新失败。

这里的数据库分布式锁是通过乐观锁实现的。首先,创建一个锁表distribute_lock来保存锁记录。SQL如下所示:

create ` distribute _ lock ` not null auto _ increment comment ' self-increment ID ',` lock _ name ` varchar collateutf8 MB 4 _ bin not null default ' ' comment ' thread ID ',` lock _ time ` bigint default ' 0 ' comment '锁到期时间',` is _ deleted ` tinyint unsigned not null default ' 0 ' comment ' delete status 0 normal 1 delete ',` GMT _ create ` datetime not null default current _ timestamp comment ' ` GMT _ update ` datetime not null default current _ timestamp on update current _ timestamp comment ' modified time ',主键,唯一键` idx _ lock _ name`using btree,KEY ` idx _ lock name _ threadid ` using btree)engine = innod auto _ increment = 1 default charset = ut F8 MB 4 collate = ut F8 MB 4 _ bin comment = ' distributed lock ';

锁定原理

在字段lock_name上创建唯一索引,并在第一次锁定时将锁定记录插入distribute_lock表中。如果插入成功,则锁成功,否则触发唯一索引约束,插入失败,即锁失败。

插入到distribute_lock值中;

如果锁不是第一次锁,那么通过lock_name会找到当前的锁记录。如果线程持有锁,锁将失败;否则,thread_id字段将被更新为当前线程id,lock_time字段将被更新为当前锁到期时间。如果更新成功,锁将成功,否则,锁将失败。

update distribute _ lock set thread _ id = ' thread 2 ',lock_time=5000其中lock_name='test ',thread _ id = ' thread1

解锁原理

释放当前线程持有的锁,即将thread_id字段更新为空,lock_time字段更新为0。

update distribute _ lock set thread _ id = ' ',lock_time=0其中lock_name='test ',thread _ id = ' thread1

实现代码

//lock public boolean try lock { try { distribute lock distribute lock = base mapper . select one . eq . eq . last);if { distribute lock = new distribute lock;distribute lock . set lock name;distribute lock . setlocktime+expri emis);distribute lock . setthreadid . getname);整数result = baseMapper.insert返回结果!= null result . int value > 0 true:false;} if) distribute lock . getlocktime!= 0 system . current time millis 0 true:false;} catch { log.error返回false} }//unlock @ override public boolean unlock { distribute lock update lock = newdistributelock;updateLock.setThreadIdupdateLock.setLockTime整数updateResult = baseMapper.update。eq . eq . getname));返回updateResult!= null update result . int value > 0 true:false;}

正确使用锁。

让我们来看看下面的添加和解锁代码:

//添加和解锁方式错误@ override public boolean use lock _ error { string key = " lock _ "+id;//lock boolean lock succes = try lock;if {抛出新的baseException}//业务逻辑处理...//解锁;返回false}

这种写法有问题吗?不都是锁着没锁着的吗?感觉是对的。没什么不好。如果此时业务逻辑处理异常,解锁方式是否仍解锁?所以解锁一定要放在最后。有时当我们使用分布式锁时,我们也希望使用事务来确保数据的一致性。我们做什么呢只需在方法中添加@Transactional来启动事务,并在捕捉时再次抛出异常,以确保事务回滚。

@Transactional@Overridepublic布尔值use lock _ Transactional { String key = " lock _ "+id;try {//lock boolean lock succes = try lock;if {抛出新的baseException}//业务逻辑处理} catch {//}捕获的异常,直接抛出,回滚事务throw ex}最后{// unLock解锁;}返回false}

战斗演习

模拟100个用户抢购商品,商品的销售数量加1,检查成功销售的数量是否与数据库中的数量一致。实际代码如下:

@ DS @ Transactional @ override public base response testLock { try { Boolean is success = distributelockservice . try lock;if {抛出新的baseException} good info good info = base mapper . select byid;good info . setsalecount+1);base mapper . update byid;int rand = random util . random int;if { log.error抛出新的baseException} else { log.error返回base response . ok);} } catch { if { throw ex} log.error扔ex;}最后{ distributeLockService.unLock}}

结果如下:

数据库产品id=1的销售数量sale _ count: 98,'测试锁成功!'日志输出的数量也是98,这表明可以正确地防止并发操作。

摘要

当使用for update to lock时,需要打开事务,并且where条件应该被索引。因为容易出现死锁,所以在实际开发中很少使用。

使用乐观锁定时,一定要将解锁操作放在最后。与redis分布式锁相比,其性能较差,但更安全。

 
友情链接
鄂ICP备19019357号-22