配置版本计数器

优质
小牛编辑
142浏览
2023-12-01

这个 Mapper 支持管理 version id column ,它是一个单表列,每次递增或更新其值时, UPDATE 出现映射表。每次ORM发出 UPDATEDELETE 以确保内存中保留的值与数据库值匹配。

警告

因为版本控制特性依赖于 在记忆中 对象的记录,该功能仅适用于 Session.flush() 进程,其中ORM将内存中的单个行刷新到数据库。它确实 not 执行多行更新或删除时使用 Query.update()Query.delete() 方法,因为这些方法只发出更新或删除语句,否则不能直接访问受影响的行的内容。

此功能的目的是检测两个并发事务在大致相同的时间修改同一行的时间,或者提供一种保护,防止在系统中使用“过时”行,该行可能在不刷新的情况下(例如,如果一个事务集 expire_on_commit=False 用一个 Session ,可以重新使用以前事务中的数据)。

Concurrent transaction updates

在检测事务中的并发更新时,通常是数据库的事务隔离级别低于 repeatable read 否则,事务将不会暴露于由与本地更新值冲突的并发更新创建的新行值。在这种情况下,SQLAlchemy版本控制功能通常不适用于事务冲突检测,尽管它仍然可以用于跨事务过时检测。

强制可重复读取的数据库通常要么锁定目标行以防并发更新,要么使用某种形式的多版本并发控制,以便在提交事务时发出错误。sqlAlchemy的version_id_col是另一种选择,它允许对事务中可能没有此隔离级别集的特定表进行版本跟踪。

参见

Repeatable Read Isolation Level -PostgreSQL的Repeatable Read实现,包括错误情况描述。

简单版本计数

跟踪版本的最简单方法是向映射表中添加一个整型列,然后将其建立为 version_id_col 在映射器选项中:

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    version_id = Column(Integer, nullable=False)
    name = Column(String(50), nullable=False)

    __mapper_args__ = {
        "version_id_col": version_id
    }

注解

它是 强烈推荐version_id 列不能为空。版本控制功能 不支持 版本控制列中的空值。

上面, User 映射使用列跟踪整数版本 version_id . 当对象类型为 User 第一次冲洗时, version_id 列的值将为“1”。然后,稍后的表更新将始终以类似于以下方式发出:

UPDATE user SET version_id=:version_id, name=:name
WHERE user.id = :user_id AND user.version_id = :user_version_id
{"name": "new name", "version_id": 2, "user_id": 1, "user_version_id": 1}

上面的update语句正在更新不仅匹配的行 user.id = 1 ,它还要求 user.version_id = 1 ,其中“1”是已知要在此对象上使用的最后一个版本标识符。如果其他事务已单独修改了行,则此版本ID将不再匹配,并且UPDATE语句将报告没有匹配的行;这是SQLAlchemy测试的条件,即只有一行与UPDATE(或DELETE)语句匹配。如果零行匹配,则表示我们的数据版本已过时,并且 StaleDataError 提高了。

自定义版本计数器/类型

其他类型的值或计数器可用于版本控制。常见类型包括日期和guid。当使用备用类型或计数器方案时,SQLAlchemy使用 version_id_generator 参数,它接受可调用的版本生成。此可调用项将传递当前已知版本的值,并预期返回后续版本。

例如,如果我们想跟踪 User 使用随机生成的guid初始化,我们可以这样做(请注意,一些后端支持本机guid类型,但我们在这里用一个简单的字符串说明)::

import uuid

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    version_uuid = Column(String(32), nullable=False)
    name = Column(String(50), nullable=False)

    __mapper_args__ = {
        'version_id_col':version_uuid,
        'version_id_generator':lambda version: uuid.uuid4().hex
    }

持久性引擎将调用 uuid.uuid4() 每一次 User 对象接受插入或更新。在这种情况下,我们的版本生成函数可以忽略 version ,作为 uuid4() 函数生成没有任何前提值的标识符。如果我们使用的是顺序版本控制方案,例如数字或特殊字符系统,那么我们可以使用给定的 version 以帮助确定后续值。

参见

后端不可知guid类型

服务器端版本计数器

这个 version_id_generator 也可以配置为依赖于数据库生成的值。在这种情况下,数据库需要一些方法来在一行执行INSERT和UPDATE时生成新的标识符。对于更新情况,通常需要更新触发器,除非所讨论的数据库支持其他本机版本标识符。PostgreSQL数据库特别支持名为的系统列 xmin 其提供更新版本控制。我们可以利用PostgreSQL xmin 列来版本我们的 User 类如下所示::

from sqlalchemy import FetchedValue

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    xmin = Column("xmin", String, system=True, server_default=FetchedValue())

    __mapper_args__ = {
        'version_id_col': xmin,
        'version_id_generator': False
    }

通过上述映射,ORM将依赖于 xmin 用于自动提供版本ID计数器的新值的列。

创建引用系统列的表

在上述场景中,如 xmin 是PostgreSQL提供的系统列,我们使用 system=True 将其标记为系统提供的列的参数,从 CREATE TABLE 声明。此列的数据类型是名为的内部PostgreSQL类型 xid 它的作用主要类似于字符串,所以我们使用 String 数据类型。

ORM通常不会在发出插入或更新时主动获取数据库生成值的值,而是将这些列保留为“已过期”并在下次访问时获取,除非 eager_defaults mapper() 设置标志。但是,当使用服务器端版本列时,ORM需要主动获取新生成的值。这是为了设置版本计数器 之前 任何并发事务都可以再次更新它。最好在insert或update语句中同时使用 RETURNING 否则,如果随后发出select语句,则仍然存在一个潜在的争用条件,版本计数器可能会在获取之前发生更改。

当目标数据库支持返回时,为 User 类将如下所示:

INSERT INTO "user" (name) VALUES (%(name)s) RETURNING "user".id, "user".xmin
{'name': 'ed'}

在上面的位置,ORM可以在一条语句中获取任何新生成的主键值以及服务器生成的版本标识符。当后端不支持返回时,必须为发出额外的选择 每一个 插入和更新的效率要低得多,还可能会导致版本计数器丢失:

INSERT INTO "user" (name) VALUES (%(name)s)
{'name': 'ed'}

SELECT "user".version_id AS user_version_id FROM "user" where
"user".id = :param_1
{"param_1": 1}

它是 强烈推荐 该服务器端版本计数器仅在绝对必要时使用,并且仅在支持的后端上使用 RETURNING ,例如PostgreSQL、Oracle、SQL Server(尽管SQL Server有 major caveats 当使用触发器时)、火鸟。

0.9.0 新版功能: 支持服务器端版本标识符跟踪。

程序或条件版本计数器

什么时候? version_id_generator 如果设置为false,我们还可以通过编程(并且有条件地)在对象上设置版本标识符,方法与分配任何其他映射属性相同。例如,如果我们使用uuid示例,但是 version_id_generatorFalse ,我们可以根据自己的选择设置版本标识符:

import uuid

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    version_uuid = Column(String(32), nullable=False)
    name = Column(String(50), nullable=False)

    __mapper_args__ = {
        'version_id_col':version_uuid,
        'version_id_generator': False
    }

u1 = User(name='u1', version_uuid=uuid.uuid4())

session.add(u1)

session.commit()

u1.name = 'u2'
u1.version_uuid = uuid.uuid4()

session.commit()

我们可以更新 User 对象,但不增加版本计数器;计数器的值将保持不变,并且update语句仍将检查以前的值。这对于仅某些更新类对并发问题敏感的方案可能很有用:

# will leave version_uuid unchanged
u1.name = 'u3'
session.commit()

0.9.0 新版功能: 支持编程和条件版本标识符跟踪。