版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

配置版本计数器

Mapper支持管理一个版本id列,它是一个单独的表列,每增加一个UPDATE映射表发生。每当ORM针对行发出UPDATEDELETE时,都会检查该值,以确保内存中保存的值与数据库值匹配。

警告

由于版本控制功能依赖于比较对象的内存记录中的,该功能仅适用于Session.flush()进程,内存行到数据库。当使用Query.update()Query.delete()方法执行多行UPDATE或DELETE时,不会生效,因为这些方法只发出UPDATE或DELETE语句,否则不能直接访问受影响行的内容。

此功能的目的是检测两个并发事务何时大致同时修改同一行,或者在系统中防止使用可能正在重新使用数据的“过时”行先前的事务没有刷新(例如,如果用Session设置expire_on_commit=False,则可以重新使用来自先前事务的数据)。

并发事务更新

当检测到事务中的并发更新时,通常情况下,数据库的事务隔离级别低于可重复读取的级别;否则,事务将不会暴露给由与本地更新值冲突的并发更新创建的新行值。在这种情况下,SQLAlchemy版本控制功能对于事务内冲突检测通常不会有用,尽管它仍然可以用于交叉事务过时检测。

执行可重复读取的数据库通常会将目标行锁定为并发更新,或者正在使用某种形式的多版本并发控制,以便在提交事务时发出错误。SQLAlchemy的version_id_col是一个替代方案,它允许对事务中的特定表进行版本跟踪,否则可能没有设置此隔离级别。

也可以看看

可重复读取隔离级别 - Postgresql的可重复读取实现,包括错误条件的描述。

简单版本计数

跟踪版本最直接的方法是在映射表中添加一个整数列,然后在映射器选项中将其建立为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
    }

上面,User映​​射使用列version_id跟踪整数版本。当首先刷新User类型的对象时,version_id列将被赋予值“1”。然后,表中的UPDATE将总是以类似于以下的方式发出:

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}

The above UPDATE statement is updating the row that not only matches user.id = 1, it also is requiring that user.version_id = 1, where “1” is the last version identifier we’ve been known to use on this object. 如果其他地方的事务独立地修改了行,这个版本ID将不再匹配,并且UPDATE语句将报告没有行匹配;这是SQLAlchemy测试的条件,只有一行匹配我们的UPDATE(或DELETE)语句。如果零行匹配,则表明我们的数据版本是陈旧的,并引发StaleDataError

自定义版本计数器/类型

其他类型的值或计数器可用于版本控制。常见的类型包括日期和GUID。在使用替代类型或计数器方案时,SQLAlchemy使用version_id_generator参数为此方案提供了一个钩子,该参数接受可调用的版本生成。这个可调用函数传递当前已知版本的值,并且预期会返回后续版本。

例如,如果我们想要使用一个随机生成的GUID跟踪我们的User类的版本,我们可以做到这一点(请注意,一些后端支持本地GUID类型,但我们在这里使用一个简单的字符串):

import uuid

class User(Base):
    __tablename__ = 'user'

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

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

每当User对象受到INSERT或UPDATE时,持久化引擎将调用uuid.uuid4()在这种情况下,我们的版本生成函数可以忽略version的传入值,因为uuid4()函数生成没有任何先决条件值的标识符。如果我们使用数字或特殊字符系统等顺序版本方案,我们可以使用给定的version来帮助确定后续值。

服务器端版本计数器

The version_id_generator can also be configured to rely upon a value that is generated by the database. 在这种情况下,数据库将需要一些生成新标识符的方法,当一行受INSERT和UPDATE处理时。对于UPDATE情况,通常需要更新触发器,除非有问题的数据库支持其他本地版本标识符。Postgresql数据库特别支持一个名为xmin的系统列,它提供UPDATE版本控制。我们可以使用Postgresql xmin列对我们的User类进行版本化,如下所示:

class User(Base):
    __tablename__ = 'user'

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

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

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

创建引用系统列的表

In the above scenario, as xmin is a system column provided by Postgresql, we use the system=True argument to mark it as a system-provided column, omitted from the CREATE TABLE statement.

ORM通常不会在发出INSERT或UPDATE时主动获取数据库生成的值的值,而是将这些列保留为“过期”,并在下次访问时将其提取出来,除非eager_defaults mapper()标志被设置。但是,当使用服务器端版本列时,ORM需要主动获取新生成的值。这是为了使版本计数器在之前设置任何并发事务可以再次更新它。这个抓取最好在INSERT或UPDATE语句中同时使用RETURNING完成,否则如果之后发出SELECT语句,则在获取版本计数器之前,版本计数器可能会发生变化,这仍然存在潜在的争用情况。

当目标数据库支持RETURNING时,我们的User类的INSERT语句如下所示:

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

如上所述,ORM可以在一个语句中获取任何新生成的主键值以及服务器生成的版本标识符。当后端不支持RETURNING时,必须为每个 INSERT和UPDATE发出一个额外的SELECT,效率低得多,并且还引入了错过版本计数器的可能性:

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}

It is strongly recommended that server side version counters only be used when absolutely necessary and only on backends that support RETURNING, e.g. Postgresql, Oracle, SQL Server (though SQL Server has major caveats when triggers are used), Firebird.

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

编程或条件版本计数器

When version_id_generator is set to False, we can also programmatically (and conditionally) set the version identifier on our object in the same way we assign any other mapped attribute. 例如,如果我们使用了我们的UUID例子,但是将version_id_generator设置为False,我们可以在我们选择的版本中设置版本标识符:

import uuid

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    version_uuid = Column(String(32))
    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语句仍将检查以前的值。这对于只有某些类别的UPDATE对并发性问题敏感的方案可能是有用的:

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

New in version 0.9.0: Support for programmatic and conditional version identifier tracking.