先查询mysql数据库是否存在数据,不存在插入,如何避免重复插入

场景:小程序用户扫码成为会员
扫码缓存=》未登录=>跳转登录=>请求接口成为会员(先查询mysql数据库是否存在数据,不存在插入。)
由于前端用钩子函数的时候重复调用,重复请求了2次,即使设置了唯一索引,经常会出现重复插入报错
除了前端处理,后端应该如何避免这个问题发生?

补充:成为会员的时候还会做其他大量的处理

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

使用redis锁

        //防止重复提交 加redis锁
        $key = '_punch_'.$user['id'].'_'.$data['landmark_pk'];//自定义的key
        $rs = RedisLocal::get_redis();//换成自己的redis对象
        $ttl = 86400;
        $lock = $rs->set($key, $key, ['nx', 'ex' => $ttl]);
        if (!$lock) {
            throw new BaseException(['code' => -1, 'msg' => '请不要频繁提交哦~']);
        }
        //$res = $this->checkPrize($params);  //自己的业务
        if ($rs->get($key) == $key) {
            //如果系统是负载均衡的 有可能同一时间的多次提交打在了不同的服务器上 这里按实际需要要sleep几秒再删除锁
            $rs->del($key);
        }

laravel有种简洁的写法:不考虑其他因素

$lockKey = sprintf('order_submit_%s_%s', $this->userId(), md5(serialize($input)));
        $lock = Cache::lock($lockKey, 5);
        if (!$lock->get()) {
            return $this->fail(CodeResponse::FAIL, '请勿重复请求');
        }
1年前 评论
╰ゝSakura 1年前
my38778570 (楼主) 1年前
xtheme 1年前
Aroad 1年前
e_404_303_202 (作者) 1年前
e_404_303_202 (作者) 1年前
讨论数量: 24

接口做幂等处理,此处后端可用限流器或加锁解决

1年前 评论

利用mysql的唯一索引

1年前 评论
my38778570 (楼主) 1年前

数据库保证不出现重复记录就是唯一索引,程序中可以捕获异常友好的返回,场景并不存在并发,这种异常实际情况一般不会发生,把前端改过来再说

1年前 评论
my38778570 (楼主) 1年前

有自带功能

User::updateOrCreate(); // 会直接新增
User::firstOrNew(); // 需手动调用 save()

结合确定唯一字段创建主键或唯一索引

1年前 评论
my38778570 (楼主) 1年前

可以尝试在创建这一块进行加锁。创建完成后解锁,然后在创建这个位置只允许一个接口处理,重复请求的时候进行等待。

我这里说的只允许一个接口请求,针对的是同一个 openid 多次请求,只先处理一个,剩下的先挂着,等第一个处理完了别的请求再去申请锁

1年前 评论
porygonCN

尝试使用同步队列吧 在队列中同时只会有一个添加新会员的逻辑在跑 每次跑的时候先判断是否存在再新建 应该就不会重复了, 然后前端做一下防抖 比如点击注册 两秒内不能再点之类的

1年前 评论
DB::transaction(function (){
   $user = User::lockForUpdate()->first(); 
   // 后续sql操作
});
// 您可以使用 lockForUpdate 方法。「update」锁可防止所选记录被修改或被另一个共享锁选中:

我感觉直接悲观锁就行了

1年前 评论

直接依靠Mysql的唯一,出错就抓异常,标识重复了

            try {
                UserModel::insert(...);

            } catch (\PDOException $e) {
                throw new \Exception("用户已存在");
            }

查询再判断本身就无法阻止并发下重复入库,只有依靠锁或者unique,如果业务允许加unique,本写法在并发场景下可以节省一次mysql查询,减少了锁操作,并发场景不二之选

1年前 评论

主键不连续不是正常吗 不用纠结这个吧 我们现在用的分布式主键

1年前 评论
QIN秦同学 1年前
人艰不拆 (作者) 1年前

使用redis锁

        //防止重复提交 加redis锁
        $key = '_punch_'.$user['id'].'_'.$data['landmark_pk'];//自定义的key
        $rs = RedisLocal::get_redis();//换成自己的redis对象
        $ttl = 86400;
        $lock = $rs->set($key, $key, ['nx', 'ex' => $ttl]);
        if (!$lock) {
            throw new BaseException(['code' => -1, 'msg' => '请不要频繁提交哦~']);
        }
        //$res = $this->checkPrize($params);  //自己的业务
        if ($rs->get($key) == $key) {
            //如果系统是负载均衡的 有可能同一时间的多次提交打在了不同的服务器上 这里按实际需要要sleep几秒再删除锁
            $rs->del($key);
        }

laravel有种简洁的写法:不考虑其他因素

$lockKey = sprintf('order_submit_%s_%s', $this->userId(), md5(serialize($input)));
        $lock = Cache::lock($lockKey, 5);
        if (!$lock->get()) {
            return $this->fail(CodeResponse::FAIL, '请勿重复请求');
        }
1年前 评论
╰ゝSakura 1年前
my38778570 (楼主) 1年前
xtheme 1年前
Aroad 1年前
e_404_303_202 (作者) 1年前
e_404_303_202 (作者) 1年前
白小二

事务+redis锁。事务能兜底不出错。redis能减少事务操作

1年前 评论

mysql insert ignore into 重复插入会自动忽略,这个应该是最简单的解决方案了吧

1年前 评论

我有看到别人redis锁,但是我想到一个问题,代码业务逻辑和redis锁的过期不一致,业务逻辑还没有执行完redis就过期了并释放锁,所以自动续期redis的过期时间这个各位大哥有好的解决方法吗?

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!