如何处理 SQLAlchemy、flask、python 中的唯一数据

Posted

技术标签:

【中文标题】如何处理 SQLAlchemy、flask、python 中的唯一数据【英文标题】:How to handle unique data in SQLAlchemy, flask, pyhon 【发布时间】:2019-02-04 03:16:22 【问题描述】:

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

bank_address = db.Column(db.String(42), unique=True)

问题是,在我检查它是否已经在数据库中之前,我得到了一个错误:

检查是否唯一,然后写入数据库:

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 银行地址字段=? WHERE user.id = ?']

【问题讨论】:

我不明白问题出在哪里。您尝试写入数据库,数据库拒绝,因为存在这样的条目。这就是唯一索引的工作方式(以及它们必须如何工作:假设您检查记录是否存在,DB 告诉您不存在,另一个用户联系您的服务器并创建具有相同值的记录,然后您去插入您的值。 .. 现在你有一个重复,即使你检查了。唯一性检查必须与插入同时进行。) 【参考方案1】:

你可以做以下两件事之一:

使用该字段查询用户:

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()

后者更好,因为它不受竞争条件的影响。想象一下两个用户同时尝试注册同一个银行地址:

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

所以只捕获异常,因为数据库事务保证数据库在测试约束和添加或更新用户之前首先锁定表。

您也可以在 Flask 中锁定整个表,但是 Python 与数据库的对话要慢得多。如果您有一个繁忙的站点,您不希望数据库更新很慢,最终会有很多用户等待锁定清除。您希望将锁定保持在最低限度,并且尽可能短,并且越接近您锁定的实际数据,您可以越早再次释放锁定。数据库非常擅长这种锁定,并且(自然)非常接近它们的数据,因此将锁定留给数据库并依赖异常。

【讨论】:

你好。我正在尝试实现这一点,但我正在使用flask_sqlalchemy 我似乎无法捕捉到该错误。我怎么能在flask_sqlalchemy 中做到这一点? @VictorMHerasmePerez:我的答案使用 Flask-SQLAlchemy; db.session<Model>.query 特定于该扩展,它们不是标准的 SQLAlchemy 功能。【参考方案2】:

您不应该捕获和抑制IntegrityError,因为它可以在其他类型的约束失败时引发——例如外键约束。

现在,有一种更好的方法来处理此错误。 SQLite 和 PostgreSQL 都支持ON CONFLICT DO NOTHINGON 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)

【讨论】:

以上是关于如何处理 SQLAlchemy、flask、python 中的唯一数据的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 ProcessPool 中的 SQLAlchemy 连接?

在flask中提交数据时如何处理错误

flask session session已过期,再发送ajax请求如何处理?

flask如何处理并发

Flask-SQLAlchemy中的分页操作

flask 源码浅析(flask 如何处理请求(多线程,多进程,IO多路复用))