我们知道Mysql并发事务会引起更新丢失问题,解决办法是锁,所以本文将对锁(乐观锁、悲观锁)进行分析,这篇文章主要给大家介绍了关于MySQL悲观锁与乐观锁方案的相关资料,需要的朋友可以参考下
目录
前言
实战
1、无锁
2、悲观锁
3、乐观锁
总结
前言
悲观锁和乐观锁是用来解决并发问题的两种思想,在不同的平台有着各自的实现。例如在java中,synchronized就可以认为是悲观锁的实现(不严谨,有锁升级的过程,升级到重量级锁才算),atomic***原子类可以认为是乐观锁的实现。
悲观锁
具有强烈的独占和排他特性,在整个处理过程中将数据处于锁定状态,一般是通过系统的互斥量来实现。当其他线程想要获取锁时会被阻塞,直到持有锁的线程释放锁。
乐观锁
对数据的修改和访问持乐观态度,假设不会发生冲突,只有当数据提交更新时才会对数据冲突与否进行检测,如果没有冲突则顺利提交更新,否则快速失败,返回一个错误给用户,让用户选择接下来该如何去做,一般来说失败后会继续重试,直到提交更新成功为止。
mysql本身就支持锁机制,例如我们有一个「先查再写」的需求,我们希望整个流程是一个原子操作,中间不能被打断,这时候就可以通过给查询的数据行加「排他锁」来实现。只要当前事务不释放锁,其他事务要想获得排他锁,mysql就会将其阻塞,直到当前事务释放锁。这种mysql底层的排他锁就称作「悲观锁」。
mysql本身不提供乐观锁的功能,需要开发者自己实现。普遍的做法是在表中加一个version列,用来标记数据行的版本,当我们需要更新数据时,必须比对version版本,version一致说明这个期间数据没有被其他事务修改过,否则说明数据已经被其他事务修改,需要自旋重试了。
实战
假设数据库有两张表:商品表和订单表。
用户下单后需要执行两个操作:
初始数据:id为1的商品有100的库存,订单表数据为空。
客户端启动10个线程并发下单,分别在无锁、悲观锁、乐观锁的场景下有哪些表现。
如下是创建表的sql语句:-- 商品表
create table `goods` (
`id` bigint(20) not null auto_increment,
`goods_name` varchar(50) not null,
`price` decimal(10,2) not null,
`stock` int(11) default '0',
`version` int(10) unsigned not null default '0',
primary key (`id`)
) engine=innodb auto_increment=1 default charset=utf8
-- 订单表
create table `t_order` (
`id` bigint(20) not null auto_increment,
`goods_id` bigint(20) not null,
`order_time` datetime not null,
primary key (`id`) using btree
) engine=innodb auto_increment=1 default charset=utf8
1、无锁
不做任何处理。// 下单
private boolean order(){
goods goods = goodsmapper.selectbyid(1l);
boolean success = false;
if (goods.getstock() > 0) {
goods.setstock(goods.getstock() - 1);
// 更新库存
goodsmapper.updatebyid(goods);
// 创建订单
ordermapper.save(goods.getid());
success = true;
}
return success;
}
控制台输出结果:
2、悲观锁
查询商品时加for update,给数据行加排他锁,这样其他线程再查询时就会被阻塞,直到当前线程的事务提交并释放锁,其他线程才能继续下单。这种方式并发性能不高。
sql语句@select("select * from goods where id = #{id} for update")
goods selectforupdate(long id);
控制台输出结果:
注意:for update必须在事务中才有效,查询和更新必须在同一个事务中!!!
3、乐观锁
实现思路是:每次更新时校验版本号,如果版本号一致说明期间数据没有被其他线程改过,当前线程可以正常提交更新,否则说明数据已经被其他线程改过了,当前线程需要自旋重试,直到业务成功为止。
更新数据的同时版本号必须自增!!!@update("update goods set stock = #{stock},version = version+1 where id = #{id} and version = #{version}")
int updatebyversion(long id, integer stock, integer version);
业务代码boolean order(){
goods goods = goodsmapper.selectbyid(1l);
boolean success = false;
if (goods.getstock() > 0) {
goods.setstock(goods.getstock() - 1);
// 更新库存,带上版本号
int result = goodsmapper.updatebyversion(goods.getid(), goods.getstock(), goods.getversion());
if (result <= 0) {
// 更新失败,说明期间数据已经被其他线程修改,需要递归重试
return order();
}
// 创建订单
ordermapper.save(goods.getid());
success = true;
}
return success;
}
控制台输出结果:
总结
到此这篇关于mysql悲观锁与乐观锁方案的文章就介绍到这了,更多相关mysql悲观锁与乐观锁内容请搜索CodeAE代码之家 以前的文章或继续浏览下面的相关文章希望大家以后多多支持CodeAE代码之家!
原文链接:https://juejin.cn/post/7025402745632849957