2025年搞懂幂等性

搞懂幂等性什么是幂等性 这个概念来源于一个数学公式 简言之就是 任意多次的执行 对资源本身所产生的影响均与一次执行的影响相同 什么情况下需要保证幂等性 我们以 sql 为例 来看看什么情况下有幂等性的问题 这里就以 stock 库存表为例 字段名 字段含义 id 雪花算法的 id 值

大家好,我是讯享网,很高兴认识大家。

什么是幂等性

这个概念来源于一个数学公式:

在这里插入图片描述
讯享网

简言之就是:任意多次的执行,对资源本身所产生的影响均与一次执行的影响相同。

什么情况下需要保证幂等性

我们以sql为例,来看看什么情况下有幂等性的问题。这里就以stock(库存表为例)

字段名 字段含义
id 雪花算法的id值
product_id 产品id
product_name 产品名称
balance 剩余数量

1.查询语句

select * from stock where id =

讯享网

select语句无论查询多少次都不会对原记录产生影响,天然就具有幂等性。

2. 插入语句

讯享网insert into stock(id, product_id, product_name, balance) values (, 1, '大白兔奶糖', 200) 

这里要分情况讨论了。假如id是唯一主键或者唯一索引那么即便重复插入多次,结果也就一条记录入库,此时就具备幂等性;反之,如果id不是唯一主键或者唯一索引那么这条插入语句就不具备幂等性。

3. 删除语句

delete from stock where id =  

删除一次和删除多次,结果都是一样的。也是天然具有幂等性的。

4.更新语句

讯享网update stock set balance = 300 where id =  

上面的sql无论执行多少次,最后表中的剩余数量都会是300。所以是具有幂等性的。

update stock set balance = balance-1 where id =  

上面的sql语句每次执行都会造成剩余数量-1,此时就不具备幂等性。

实现幂等性的方案

从上面的分析来看,幂等性可能出现在插入和更新时才有幂等性问题。

1. 保证插入时的幂等性

  • 唯一索引。比如提交一个订单信息,那么用户订单号就可以是唯一索引。这样由数据库来保证幂等性问题。但是分库分表的情况就不太适用了。此时可以由一个中立数据库的表来承担唯一索引校验的工作,校验通过的再执行插入的分库中。
  • token机制防止重复插入。主要采用redis或者redisson来防止重复提交,大概思路就是:在 Redis 中设置一个值表示加了锁,然后释放锁的时候就把这个 Key 删除。

2. 保证更新时的幂等性

  • token机制防止重复更新。

  • 事务隔离级别为SERIALIZABLE【相当于锁表,不推荐】
    讯享网@Transactional(isolation = Isolation.SERIALIZABLE) public void updateStockBalance() { 
          ...... } 
  • 悲观锁。select for update

    相当于行锁,更新后由spring自动提交事务。

  • 方法上加Synchronized(单体环境并发不是非常大的时候适用)
  • 乐观锁方式。

3. 乐观锁+重试机制

这里介绍下我在项目中经常用到的保证幂等性更新的方式,数据库的操作是用MP来完成的。这里还是拿上面的stock表为例。

  • 表加一个version版本号字段
  • entity对象增加注解
    / * 版本号。用于乐观锁 */ @Version private Integer version; 
  • 引入重试依赖
    讯享网<!-- spring重试机制 --> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> 
  • 定义一个版本异常
    / * 版本更新异常,用于版本更新失败时重试 * * @author 老马 */ public class VersionUpdateException extends RuntimeException{ 
          } 
  • 写更新库存剩余的service(示例代码)
    讯享网@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) @Retryable(value = { 
         VersionUpdateException.class}, maxAttempts = 10) public ConfirmResponseBizContent outputAndUpdateRelationInfo(ConfirmContext context) { 
          // 查询库存记录 Stock stock = this.getStockInfo(context); if(ObjectUtil.isNull(stock) || stock.getBalance() <= 0) { 
          return null; } context.setStockInfo(stock); //构建返回信息 ConfirmResponseBizContent responseBizContent = buildResponseBizContent(context); // 更新库存剩余。库存剩余-=1 boolean updateResult = updateStockBalance(context); if(!updateResult) { 
          //没有更新成功时,再次执行本方法。尝试执行10次 throw new VersionUpdateException(); } //构建返回信息 return responseBizContent; } // 更新库存剩余。库存剩余-=1。注意:这里一定要加上版本条件 private boolean updateStockBalance(ConfirmContext context) { 
          return new LambdaUpdateChainWrapper<>(this.stockMapper) .setSql("balance = balance-1") .eq(Stock::getId, context.getStockInfo.getId()) .eq(DiscountDaySummary::getVersion, context.getStockInfo().getVersion()) .update(); } 
  • 启动类加入@EnableRetry注解
    @EnableRetry @SpringBootApplication public class DemoApplication { 
          public static void main(String[] args) { 
          SpringApplication.run(DemoApplication.class, args); } } 
小讯
上一篇 2025-03-18 19:20
下一篇 2025-03-19 16:28

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/52297.html