MySQL乐观锁在分布式场景下的实践背景在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作。在单实例部署的情况,我们可以简单地使用JVM提供的锁机制对减库存操作进行加锁,防止多个用户同时点击购买后导致的库存不一致问题。 但在实践中,为了提高系统的可用性,我们一般都会进行多实例部署。而不同实例有各自的JVM,被负载均衡到不同实例上的用户请求不能通过JVM的锁机制实现互斥。 因此,为了保证在分布式场景下的数据一致性,我们一般有两种实践方式:一、使用MySQL乐观锁;二、使用分布式锁。 本文主要介绍MySQL乐观锁,关于分布式锁我在下一篇博客中介绍。 乐观锁简介乐观锁(Optimistic Locking)与悲观锁相对应,我们在使用乐观锁时会假设数据在极大多数情况下不会形成冲突,因此只有在数据提交的时候,才会对数据是否产生冲突进行检验。如果产生数据冲突了,则返回错误信息,进行相应的处理。 那我们如何来实现乐观锁呢?一般采用以下方式:使用版本号(version)机制来实现,这是乐观锁最常用的实现方式。 版本号那什么是版本号呢?版本号就是为数据添加一个版本标志,通常我会为数据库中的表添加一个int类型的"version"字段。当我们将数据读出时,我们会将version字段一并读出;当数据进行更新时,会对这条数据的version值加1。当我们提交数据的时候,会判断数据库中的当前版本号和第一次取数据时的版本号是否一致,如果两个版本号相等,则更新,否则就认为数据过期,返回错误信息。我们可以用下图来说明问题:
如图所示,如果更新操作如第一个图中一样顺序执行,则数据的版本号会依次递增,不会有冲突出现。但是像第二个图中一样,不同的用户操作读取到数据的同一个版本,再分别对数据进行更新操作,则用户的A的更新操作可以成功,用户B更新时,数据的版本号已经变化,所以更新失败。 代码实践我们对某个商品减库存时,具体操作分为以下3个步骤:
为了使用MySQL的乐观锁,我们需要为商品表goods加一个版本号字段version,具体的表结构如下: CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL DEFAULT '', `remaining_number` int(11) NOT NULL, `version` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; Goods类的Java代码:
public class Goods implements Serializable {
private static final long serialVersionUID = 0L;
private Integer id;
/**
* 商品名字
*/
private String name;
/**
* 库存数量
*/
private Integer remainingNumber;
/**
* 版本号
*/
private Integer version;
@Override
public String toString() {
return "Goods{" +
"id=" + id +
", name='" + name + '\'' +
", remainingNumber=" + remainingNumber +
", version=" + version +
'}';
}
}
GoodsMapper.java:
public interface GoodsMapper {
Integer updateGoodCAS(Goods good);
}
GoodsMapper.xml如下:
<update id="updateGoodCAS" parameterType="com.ztl.domain.Goods">
<![CDATA[
update goods
set `name`=#{name},
remaining_number=#{remainingNumber},
version=version+1
where id=#{id} and version=#{version}
]]>
</update>
GoodsMapperTest.java测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class GoodsMapperTest {
@Autowired
private GoodsMapper goodsMapper;
@Test
public void updateGoodCASTest() {
Integer id = 1;
Goods good1 = goodsMapper.selectGoodById(id);
Goods good2 = goodsMapper.selectGoodById(id);
//打印当前商品信息
System.out.println(good1);
System.out.println(good2);
//更新good1
good1.setRemainingNumber(good1.getRemainingNumber()-1);
int result1 = goodsMapper.updateGoodCAS(good1);
System.out.println("减库存" + (result1 == 1 ? "成功" : "失败"));
//更新good2
good2.setRemainingNumber(good2.getRemainingNumber()-2);
int result2 = goodsMapper.updateGoodCAS(good2);
System.out.println("减库存" + (result2 == 1 ? "成功" : "失败"));
}
}
输出结果:
Goods{id=1, name='手机', remainingNumber=7, version=3}
Goods{id=1, name='手机', remainingNumber=7, version=3}
减库存成功
减库存失败
代码说明: 在updateGoodCASTest()的测试方法中,我们同时查出id=1的商品的同一个版本信息,然后分别通过good1和good2对象对库存数量进行修改,第一次通过good1的修改成功了;但第二次修改时,update where 条件中的版本号已经不匹配,所以更新失败。 这样,我们就可以通过MySQL的乐观锁机制保证在分布式场景下的数据一致性。 (责任编辑:好模板) |



ecshop汽车配件类外贸模板
人气:1826
空包代发刷快递单空包网
人气:3590
ecshop拉夫红酒模板|红酒商
人气:957
ecshop仿天猫商城2016最新模
人气:1092
淘宝客|返利网|易购51模板
人气:13549
ecshop仿美乐乐模板|ecshop免
人气:6937