通常如何在Flask中处理唯一的数据库条目?我的数据库模型中有以下列:
bank_address = db.Column(db.String(42), unique=True)
问题是,即使在我无法检查它是否已经存在于数据库中之前,我也会收到错误消息:
检查它是否唯一,然后将其写入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()
我得到的错误:
sqlalchemy.exc.IntegrityError:(sqlite3.IntegrityError)唯一 约束失败:user.bank_address_field [SQL:'UPDATE user SET bank_address_field =?在哪里user.id =?']
答案 0 :(得分:6)
您可以执行以下两项操作之一:
对具有该字段的用户进行查询:
if User.query.filter(User.bank_address == request.form['bank_address_field']).first():
# error, there already is a user using this bank address
这是一个大问题,但是,请参见下文。
捕获异常:
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
其中IntegrityError
可从sqlalchemy.exc
导入。一旦引发IntegrityError,无论您是否捕获到该错误,您正在使用的会话都会失效。要继续使用该会话,您需要发出db.session.rollback()
。
后者更好,因为它不受比赛条件的限制。想象两个用户试图同时注册相同的银行地址:
User.query.filter().first()
返回None
,因为还没有人使用该地址。User.query.filter().first()
返回None
,因为还没有人使用该地址。所以只是捕获异常,因为数据库事务保证在测试约束以及添加或更新用户之前,数据库首先锁定表。
您也可以将整个表锁定在Flask中,但是Python与数据库的通信要慢得多。如果站点繁忙,则不希望数据库更新缓慢,最终将导致许多用户等待锁被清除。您希望将锁定保持在最小范围内,并且越短越好,并且越接近锁定的实际数据,就越早可以再次释放锁定。数据库非常擅长于这种锁定,并且(非常自然地)非常接近其数据,因此将锁定保留在数据库上,而是依靠异常。
答案 1 :(得分:2)
您不应该捕获并取消 IntegrityError
,因为它可以在其他类型的约束失败时引发 - 例如外键约束。
如今,有更好的方法来处理此错误。 SQLite 和 PostgreSQL 都支持 ON CONFLICT DO NOTHING
和 ON CONFLICT DO UPDATE
。以下是它们各自的 SQLAlchemy 文档:
sqlalchemy.dialects.sqlite.Insert.on_conflict_do_nothing
sqlalchemy.dialects.postgresql.Insert.on_conflict_do_nothing
不要使用 session.add()
,而是使用 SQLAlchemy 方言中的 insert()
函数。它应该大致如下:
# 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)