【问题标题】:How to prevent UUID primary key for new SQLAlchemy objects being created with the same value如何防止使用相同值创建新 SQLAlchemy 对象的 UUID 主键
【发布时间】:2019-09-18 21:16:33
【问题描述】:

我有一个 for 循环,它创建一个新的“行”对象,在将对象提交到 Postgres 数据库中的表之前用数据填充属性。我要插入的表(以及对象)采用 UUID 的主键,只有在第一次提交后所有新创建的行对象中的循环的第一次迭代后,这个值保持不变。

我对解决方案有点迷茫,但是我认为这可能与我处理数据库会话的方式有关。在写这篇文章时,我还注意到我在 invite_usersinvite_user 函数中使用了相同的变量名 (new_user)。虽然 Python 会认为它们在不同的范围内(我认为),但我想知道 SQLALchemy 会话是否会?

请注意,我已经删除了很多我认为在问题上下文中多余的代码 - 主要是更多列等。invite_user 函数也用于其他地方,因此 invite_users 仅用于批量“邀请”。

这是一个 sn-p 显示表类定义的开始和Column 定义:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects import postgresql
import uuid

Base = declarative_base()

class User(Base):
    __tablename__ = "users"

    id = Column(postgresql.UUID(as_uuid=True), default=uuid.uuid4(), primary_key=True)
    email = Column(String, unique=True)

这是我用来创建和销毁会话的函数:

from contextlib import contextmanager
from sqlalchemy import create_engine

@contextmanager
def db_session(db_url):
    engine = create_engine(db_url, convert_unicode=True)
    connection = engine.connect()
    db_session = scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine))
    yield db_session
    db_session.close()
    connection.close()

下面是遍历 JSON 对象数组的函数的一部分:

def invite_users(json_dict):
    exceptions = {}

    with db_session(environ['CONNECTION_STRING']) as session:
        for new_user in json_dict['users']:
            try:
                invite_user(
                    session,
                    info['email_address']
                )
            # I'm catching exceptions and storing them to handle them later
            except Exception as e:
                exceptions[user_info['email_address']] = e
                pass

这里是 invite_user 函数,它将行添加到会话并尝试提交它:

from project.database import * # this contains the User table class above
from project.exceptions import *

def invite_user(session, email):

    new_user = User(
        email=email
    )

    session.add(new_user)

    try:
        session.commit()
    except exc.IntegrityError as e:
        session.rollback()
        raise DuplicateViolation(f"User already invited") from None

所以我遍历字典 (json_dict['emails']) 中的电子邮件地址。然后,我将每封电子邮件连同当前数据库session 一起传递给邀请用户。我这样做是为了避免为每个invite_user 调用创建一个会话,因为从性能角度来看,它似乎比在invite_user 函数中创建一个新的session 更明智,因为这将导致它们中的许多被创建和销毁。

我认为我的 Column 定义足以处理每次提交 User 行对象时新 UUID 的生成。但是,如果我通过invite_users 函数传递多个电子邮件地址,第一个用户会被添加一个新的 UUID,而第二个用户会被分配相同的 UUID。如果我通过一个电子邮件地址,一切都很好

我不想依赖在数据库中查询现有行。我完全依靠数据库约束来防止重复,异常处理用于向用户报告错误。

【问题讨论】:

    标签: python postgresql sqlalchemy


    【解决方案1】:

    对于 Column 的 default 参数状态的 sqlalchemy docs

    表示此列默认值的标量、Python 可调用或 ColumnElement 表达式,如果在插入的 VALUES 子句中未指定此列,则将在插入时调用该表达式。

    因此,与其提供将创建标量(常量)值的uuid.uuid4(),不如只提供可调用的uuid.uuid4,以便为每次插入调用它。

    【讨论】:

    • 目前无法测试,但会在今天晚些时候确认。珍惜时间。
    • 完美。发挥了魅力。感谢您指出了这一点。感觉这是一个相当容易犯的错误,但很难发现。
    • 是否用 uuid.uuid4 替换 uuid.uuid4() 确保我们不会有任何重复?我们不应该查询数据库以检查新生成的 uuid 是否已经存在吗?
    • 在这种情况下使用 uuid.uuid4() 会导致在导入时(模型)生成一个 uuid,并一直使用到应用程序关闭。使用uuid.uuid4 会在每次创建模型实例时生成一个新的 uuid。没有检查重复项,但在这种情况下,它是一个主键,因此重复项会导致完整性违规。理论上,uuid4 冲突的可能性极低,假设有良好的随机性来源。例如,请参阅讨论 here
    猜你喜欢
    • 2018-03-14
    • 2015-05-06
    • 2020-02-11
    • 1970-01-01
    • 2015-05-09
    • 2022-10-25
    • 2021-09-06
    • 2021-07-03
    • 2016-07-01
    相关资源
    最近更新 更多