背景

有如下一张用户表

id uid name is_deleted
1 111 xiaoming 0
2 222 xiaohong 0

为了更好地做记录,同时保证安全,代码不做物理删除,只做逻辑删除,将is_deleted字段置为1,即表示删除。

登陆依赖别人的SSO,如果登陆了之后访问过我的网站的话,那么我就会把这个人记录到自己的系统里面,同时做一些别的操作,uid字段是由SSO系统生成的,每个人唯一。

问题

当两个请求同时访问我的系统时,同时去查询数据库,两次查询都没有查到当前用户,于是这两个请求的逻辑就都走到插入新用户逻辑,同时,因为增加了is_deleted字段,我无法对uid字段加唯一索引,一个用户可以被删除多次,也无法对uidis_deleted两个字段做复合唯一索引,所以这两个请求的插入动作都能成功执行。这时,表里面就有了两条完全相同的用户记录了,问题发生。

问题原因

如果没有is_deleted字段,那么其实不存在这个问题,给uid字段加一个唯一索引,第二次插入的时候必然会报错,所以究其原因,我们需要一把锁,一把在这张表之外的锁。

解决办法

  1. 新建一张表,以uid为主键或者唯一索引,在需要插入用户表之前,先插入这张新建的表,如果插入成功了,那么再往业务表里面插入用户信息。
  2. 使用tair``redis等,在准备插入用户表前,先使用uid作为key插一条记录并加上锁,就能够保证只有一个请求能够抢到锁,抢到后再进行用户信息的插入。

缺陷

  1. 几种方法都比较重,都需要新建一张表,甚至引入一种新的存储。
  2. 假设同时两个请求抢锁,没抢到锁的请求处理会比较麻烦,可能需要定时隔一段时间去检测用户信息是否已经插入,逻辑较重。