数据库 race condition

查看 108|回复 6
作者:lllei   
最近在写一个 fastapi 的后端,对 web 服务中的数据库的并发有些问题,希望大家解惑。
参考了 官方教程 SQL-Database。
其中,添加用户的逻辑如下:
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    db_user = crud.get_user_by_email(db, email=user.email)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered")
    return crud.create_user(db=db, user=user)
其中 curd.create_user 实现如下:
def create_user(db: Session, user: schemas.UserCreate):
    fake_hashed_password = user.password + "notreallyhashed"
    db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user
我想问的是:
这样不会有并发问题吗?我的意思是他是先检查一个用户邮箱是否存在,不存在则增加,那如果同时有两个呢?
我测验结果是如果并发,会有两个同时加入。
那么我应该如何通过什么手段解决这个问题呢?是在代码片段上加锁吗?那样不会拖慢速度吗?

user, db_user, Email, schemas

Flourite   
要加锁,要么用 redis 要么用 db ,代码加锁?如果你有多个进程呢?
0x19921213   
1. 设置 email 为唯一键
2. 数据库乐观锁
lllei
OP
  
我意识到以上问题可以通过 SQL 的 unique key 来解决。
那么如果是现在的需求的让一道题的分数减少为原来的 $\dfrac{1}{10}$,又该怎么办呢?
lllei
OP
  
@0x19921213 乐观锁吗,好,我去了解下:)
lllei
OP
  
@Flourite hh ,我也觉得不现实,所以想来问一问需要用到什么知识
kdd0063   
你这不就是 write skew 吗? write skew 如果在 RDBMS 的层面解决,必须要把隔离级别拉到 serializable 然后通过事务提交你的这个 CTA ( check then action ,本质上和 cas 等价)操作。如果在应用层面,也是类似的思路,只能通过独占锁或者独占调度来解决,如果你的后端应用是分布式部署的,还得上分布式锁。
总之,幻读和 write skew 如果想完全杜绝,其实是对事务隔离和可见性提出了很高的要求,目前没有非常高效的解决方案。想要高性能就得牺牲一致性与可见性,至少目前这个桎梏在整个分布式数据系统上下文里还没有被突破。
您需要登录后才可以回帖 登录 | 立即注册

返回顶部