并发导致重复数据与id生成器生成id冲突解决方法

用户注册流程

在这里插入图片描述

大致流程是:判断手机号是否存在,不存在则新增;id生成依赖micro-basic-service微服务的id生成器,拿到id后用户中心服务再执行插入用户记录。
虽然加了事务,此流程并发时会存在一些问题。
并发场景:
在这里插入图片描述

第一个问题:id生成失败

micro-basic-service 会报id生成失败。
原因:两个事务/进程同时来到第④步,假如查到的table_id=10,然后进程1率先执行第⑤步,把table_id自增1最终更新数据为11;此时,进程2执行到第⑤步,设置table_id = 11,会导致update rows affected = 0 引起id生成失败。

并发模拟如下

在这里插入图片描述在这里插入图片描述
并发执行手机号注册,出现了一个失败一个成功的情况。
解决思路:
把第④和第⑤步做成原子,对查询做排它锁,使用排它锁 for update。
在这里插入图片描述
实际代码如下
在这里插入图片描述

对于并发导致的id生成失败的问题就解决了,但是还存在另一个问题。
在这里插入图片描述
可以看到,并发两个注册,虽然不报数据库主键生成失败的错误了,但是还存在另外的问题,就是重复注册。

第二个问题:重复注册

问题的关键在于

select mobile ... where mobile = 15218621444
if !mobile {
    getNextId
    insert
}

两个进程同时来到第②步,发现不存在手机号,并发走到第③至第⑥的最终插入,由于手机号没做唯一索引,引起了相同手机号注册。
只要通过了第②步,就会进入注册逻辑。
解决思路:
同个手机号并发注册时,同时只有一个进程通过第②步。
一开始我给第②步,加上select mobile … where mobile = 15218621444 for update,结果是不生效的,实验后,发现对于不存在记录的 select for update,数据库不知道你要锁哪一行,也不会阻塞表。
而后又想了这些方案:
(1)用锁表解决,比如select count(1) where 1 … for update。让他阻塞,但是锁表性能不行。
(2)另外一种是用redis缓存锁排队。
(3)还有一种最简单的是加唯一索引。
(4)对于第一种锁表的改造,用冷数据一定能命中查询来致使锁行阻塞。

如select id order by id asc limit 1 … for update ,然后再执行后面的业务逻辑
select mobile … where mobile=15218621454;
if !mobile
{
insert …
}
牺牲一个冷数据的性能来做排它锁。
于是我试验了第四种方案,并得到验证是可行的。
在这里插入图片描述

实际代码如下
在这里插入图片描述在这里插入图片描述
并发执行效果:
在这里插入图片描述
并发执行,只有一个成功。

总结:

对于先查询,后修改的场景,可以使用排它锁保证代码段落原子性。

个人觉得,多个服务之间的调用,服务2是id生成器是生成id并返回,即使服务1事务失败也不需要回滚服务2的事务,所以这个案例不需要用到分布式事务的技术。

关于 for update 的细节我整理了一篇文章如下:
数据库 for update的作用:https://blog.csdn.net/w786572258/article/details/117923902

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值