如何在SQLAlchemy,flask,pyhon中处理唯一数据

sai*_*tam 3 python sqlite sqlalchemy unique flask

通常,您如何在Flask中处理唯一的数据库条目?我的数据库模型中有以下专栏:

bank_address = db.Column(db.String(42), unique=True)
Run Code Online (Sandbox Code Playgroud)

问题是,即使在我无法检查它是否已经存在于数据库中之前,也会收到错误消息:

检查它是否唯一,然后将其写入db:

if request.method == 'POST':
    if user.bank_address != request.form['bank_address_field']:
        user.bank_address = request.form['bank_address_field']
        db.session.add(user)
        db.session.commit()
Run Code Online (Sandbox Code Playgroud)

我得到的错误:

sqlalchemy.exc.IntegrityError:(sqlite3.IntegrityError)唯一约束失败:user.bank_address_field [SQL:'UPDATE user SET bank_address_field =?在哪里user.id =?']

Jam*_*hra 7

您不应该捕获并抑制,IntegrityError因为当其他类型的约束(例如外键约束)失败时,可能会引发它。

如今,有一种更好的方法来处理这个错误。SQLite 和 PostgreSQL 都支持ON CONFLICT DO NOTHINGON CONFLICT DO UPDATE。以下是他们各自的 SQLAlchemy 文档:

不要使用session.add(),而是使用insert()SQLAlchemy 方言中的函数。它应该大致如下所示:

# if you are using SQLite
from sqlalchemy.dialects.sqlite import insert

# if you are using PostgreSQL
from sqlalchemy.dialects.postgresql import insert

values = dict() # your values here

stmt = (
    insert(User)
    .values(**values)
    .on_conflict_do_nothing(index_elements=[User.bank_address])
)

db.session.execute(stmt)
Run Code Online (Sandbox Code Playgroud)


Mar*_*ers 6

您可以执行以下两项操作之一:

  • 使用该字段查询用户:

    if User.query.filter(User.bank_address == request.form['bank_address_field']).first():
        # error, there already is a user using this bank address
    
    Run Code Online (Sandbox Code Playgroud)

    但是,这有很大的问题,请参阅下文。

  • 捕获异常:

    from sqlalchemy.exc import IntegrityError
    
    try:
        db.session.commit()
    except IntegrityError:
        db.session.rollback()
        # error, there already is a user using this bank address or other
        # constraint failed
    
    Run Code Online (Sandbox Code Playgroud)

    IntegrityError可以从哪里导入sqlalchemy.exc。一旦引发IntegrityError,无论您是否捕获到该错误,您正在使用的会话都会失效。要继续使用该会话,您需要发出db.session.rollback()

后者更好,因为它不受比赛条件的影响。想象两个用户试图同时注册相同的银行地址:

  • 用户A提交了该User.query.filter().first()退货,None因为还没有人使用该地址。
  • 用户B几乎同时提交了User.query.filter().first()退货,None因为还没有人使用该地址。
  • 用户A的银行地址已成功写入数据库
  • 用户B的银行地址无法写入数据库,因为完整性检查失败,因为用户A刚刚记录了该地址。

因此,只需捕获异常即可,因为数据库事务可确保在测试约束以及添加或更新用户之前,数据库首先锁定表。

您也可以将整个表锁定在Flask中,但是Python与数据库的通讯要慢得多。如果站点繁忙,则不希望数据库更新缓慢,最终将导致许多用户等待锁被清除。您希望将锁定保持在最小范围内,并且越短越好,并且越接近锁定的实际数据,就越早可以再次释放锁定。数据库非常擅长于这种锁定,并且(非常自然地)非常接近其数据,因此将锁定保留给数据库,而是依靠异常。