✅为什么不建议用数据库唯一性约束做幂等控制?

典型回答

在做幂等控制的时候,通常会选择基于数据库的唯一性约束来做,一般流程是这样的:

1、根据幂等字段查询是否有历史记录

2、如果有,则直接返回

3、如果没有,则执行数据库操作,并捕获数据库唯一性约束冲突异常

4、没有异常,返回成功

5、捕获到异常,反查一下数据,如果真的成功,则返回成功

大致的路基如下:

public Boolean createUser(User user){

    User existUser = userDao.getUserByTel(user.getTelephone());

    if(existUser){
        return true;
    }

    try{
        return userDao.insert(user);
    }catch(DuplicateKeyException e){
        User existUser = userDao.getUserByTel(user.getTelephone());
        if(existUser){
            return true;
        }
    }

    return false;
}

首先我们说,这么做肯定是可以的,没啥大问题,一些小项目中,这么用也都是OK的。

但是为啥有人不建议这么做呢?或者说这么做有啥问题呢?为啥还需要一锁二判三更新呢?

这个方案,主要有以下几个问题:

1、依赖insert,以上的方案,有一个局限性,就是需要在做insert的时候才行,这种情况下会因为幂等操作导致重复记录,而出发唯一性约束冲突。(不是说update不会触发,而是一般业务的update操作都不太会导致这种情况发生)

2、依赖异常,这里是通过catch了DuplicateKeyException依赖来做的处理,这样就对异常有了依赖,不建议大家在代码中根据异常来控制业务流程(原文见下方链接),另外,这里和DuplicateKeyException绑定了,一旦有一天换了数据库、换了ORM框架,换了Spring版本等等,这个异常就可能会发生改变,也许就不再抛这个异常了,那么就会出现问题。

✅为什么不建议使用异常控制业务流程

3、依赖数据库,这种做法,还有一个问题,就是把并发请求交给数据库来抗了,如果没有特别大的并发的话没啥问题,但是如果一旦有并发比较高的话,那么就可能会对数据库造成很大的压力。

当然,这个做法也有好处,一方面是简单,不需要额外的引入分布式锁,还有一个好处,就是大多数情况下,其实业务幂等场景中出现并发或者重复的概率并不大,所以走到这个catch的流程也并不多,这样做相比先加个锁要更加高效。

所以,如果并发冲突不高,并且是insert操作,能基于唯一性约束来做的话,也可以这么干。但是不建议!如果并发比较高,那么建议还是一锁二判三更新。

✅如何解决接口幂等的问题?

原文: https://www.yuque.com/hollis666/xkm7k3/prnect4g81wg2law