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

SQLAlchemy 1.1文档

状态管理

Quickie介绍对象状态

了解实例在会话中可能具有的状态很有帮助:

  • Transient - 不在会话中的实例,不保存到数据库;即它没有数据库标识。这样一个对象与ORM唯一的关系就是它的类有一个和它关联的mapper()

  • Pending - when you add() a transient instance, it becomes pending. 它仍然没有被实际刷新到数据库,但会在下次刷新时发生。

  • 持久性 - 会话中存在的实例,在数据库中有记录。通过刷新来获得持久性实例,以便挂起的实例变为持久性,或通过查询数据库查找现有实例(或将其他会话的持久实例移动到本地会话中)。

  • 已删除 - 在刷新中已删除的实例,但交易尚未完成。处于这种状态的对象本质上与“未决”状态相反;当会话的事务提交时,对象将移到分离状态。或者,当会话的事务回滚时,被删除的对象将返回为持久状态。

    版本1.1中已更改:“已删除”状态是与“持久”状态不同的新添加的会话对象状态。

  • Detached - 与数据库中的记录对应或先前对应的实例,但当前不在任何会话中。分离的对象将包含数据库标识标记,但是因为它与会话没有关联,所以不知道这个数据库标识是否实际存在于目标数据库中。分离的对象可以正常使用,除非它们无法加载先前标记为“已过期”的未加载属性或属性。

要深入了解所有可能的状态转换,请参阅描述每个转换的Object Lifecycle Events部分,以及如何以编程方式跟踪每个转换。

获取对象的当前状态

可以使用inspect()系统随时查看任何映射对象的实际状态:

>>> from sqlalchemy import inspect
>>> insp = inspect(my_object)
>>> insp.persistent
True

会话属性

Session本身就像一个集合式的集合。所有存在的项目可以使用迭代器接口访问:

for obj in session:
    print(obj)

可以使用常规的“包含”语义来测试存在性:

if obj in session:
    print("Object is present")

会话还跟踪所有新创建的(即待处理的)对象,自上次加载或保存以来已经发生变化的所有对象(即“脏”)以及所有被标记为已删除的对象:

# pending objects recently added to the Session
session.new

# persistent objects which currently have changes detected
# (this collection is now created on the fly each time the property is called)
session.dirty

# persistent objects that have been marked as deleted via session.delete(obj)
session.deleted

# dictionary of all persistent objects, keyed on their
# identity key
session.identity_map

(Documentation: Session.new, Session.dirty, Session.deleted, Session.identity_map).

会话引用行为

会话中的对象是弱引用的这意味着当它们在外部应用程序中被取消引用时,它们也会从Session中超出范围,并且受到Python解释器的垃圾回收。对此的例外包括挂起的对象,标记为已删除的对象或挂起更改的持久对象。完全刷新后,这些集合都是空的,所有的对象又被弱引用。

要使Session中的对象保持强引用状态,通常只需要一个简单的方法。外部管理的强引用行为的示例包括将对象加载到与其主键相关的本地字典中,或者将对象加载到列表或集中,以保留需要保留引用的时间段。如果需要的话,可以将这些集合与Session关联,方法是将它们放入Session.info字典中。

基于事件的方法也是可行的。当所有对象保持在persistent状态时,为所有对象提供“强引用”行为的简单方法如下:

from sqlalchemy import event

def strong_reference_session(session):
    @event.listens_for(session, "pending_to_persistent")
    @event.listens_for(session, "deleted_to_persistent")
    @event.listens_for(session, "detached_to_persistent")
    @event.listens_for(session, "loaded_as_persistent")
    def strong_ref_object(sess, instance):
        if 'refs' not in sess.info:
            sess.info['refs'] = refs = set()
        else:
            refs = sess.info['refs']

        refs.add(instance)


    @event.listens_for(session, "persistent_to_detached")
    @event.listens_for(session, "persistent_to_deleted")
    @event.listens_for(session, "persistent_to_transient")
    def deref_object(sess, instance):
        sess.info['refs'].discard(instance)

Above, we intercept the SessionEvents.pending_to_persistent(), SessionEvents.detached_to_persistent(), SessionEvents.deleted_to_persistent() and SessionEvents.loaded_as_persistent() event hooks in order to intercept objects as they enter the persistent transition, and the SessionEvents.persistent_to_detached() and SessionEvents.persistent_to_deleted() hooks to intercept objects as they leave the persistent state.

The above function may be called for any Session in order to provide strong-referencing behavior on a per-Session basis:

from sqlalchemy.orm import Session

my_session = Session()
strong_reference_session(my_session)

它也可能被称为任何sessionmaker

from sqlalchemy.orm import sessionmaker

maker = sessionmaker()
strong_reference_session(maker)

合并¶ T0>

merge() transfers state from an outside object into a new or already existing instance within a session. 它还将传入的数据与数据库的状态进行协调,产生将用于下一次刷新的历史流,或者可以使得产生状态的简单“转移”而不产生改变历史或访问数据库。用法如下:

merged_object = session.merge(existing_object)

给定一个实例时,遵循以下步骤:

  • 它检查实例的主键。如果存在,它将尝试在本地标识映射中查找该实例。如果load=True标志处于缺省状态,它还会检查数据库中是否存在本地主键。

  • 如果给定的实例没有主键,或者在给定主键的情况下没有找到实例,则创建新的实例。

  • 然后将给定实例的状态复制到位于/新创建的实例上。对于源实例中存在的属性,该值将传输到目标实例。对于源上不存在的映射属性,属性在目标实例上过期,丢弃其现有值。

    如果load=True标志处于默认状态,则此复制过程将发出事件并为源对象上存在的每个属性加载目标对象的卸载集合,以便可以调整传入状态数据库中有什么如果load作为False传递,则传入的数据将直接“戳记”而不会产生任何历史记录。

  • merge级联(参见Cascades)所示,操作级联到相关的对象和集合。

  • 新实例返回。

使用merge(),给定的“源”实例不会被修改,也不会与目标Session关联,并且可以与其他任何数量的Session对象。merge() is useful for taking the state of any kind of object structure without regard for its origins or current session associations and copying its state into a new session. 这里有一些例子:

  • 从文件读取对象结构并希望将其保存到数据库的应用程序可能会解析文件,建立结构,然后使用merge()将其保存到数据库中,以确保文件中的数据用来表示结构中每个元素的主键。之后,当文件发生变化时,可以重新运行相同的进程,产生一个稍微不同的对象结构,然后再次被mergedSession将自动更新数据库以反映这些更改,使用主键从数据库加载每个对象,然后使用给定的新状态更新其状态。

  • 应用程序正在将对象存储在内存缓存中,同时被许多Session对象共享。merge() is used each time an object is retrieved from the cache to create a local copy of it in each Session which requests it. 缓存的对象保持分离状态;只有它的状态被移动到本身对个人Session对象本地的拷贝中。

    在高速缓存用例中,通常使用load=False标志来消除协调对象状态与数据库的开销。There’s also a “bulk” version of merge() called merge_result() that was designed to work with cache-extended Query objects - see the section Dogpile Caching.

  • 应用程序希望将一系列对象的状态转换为由工作线程或其他并发系统维护的Sessionmerge() makes a copy of each object to be placed into this new Session. 在操作结束时,父线程/进程维护它所启动的对象,线程/工作者可以继续进行这些对象的本地拷贝。

    在“线程/进程之间的传输”用例中,应用程序可能也希望使用load=False标志,以避免数据传输时出现开销和冗余SQL查询。

合并提示

merge() is an extremely useful method for many purposes. 但是,它处理瞬态/分离对象与持久对象之间复杂的边界,以及状态的自动传输。在这里可以呈现的各种各样的场景经常需要对对象状态更仔细的方法。与合并有关的常见问题通常涉及有关传递给merge()的对象的一些意外状态。

让我们使用User和Address对象的规范例子:

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    addresses = relationship("Address", backref="user")

class Address(Base):
    __tablename__ = 'address'

    id = Column(Integer, primary_key=True)
    email_address = Column(String(50), nullable=False)
    user_id = Column(Integer, ForeignKey('user.id'), nullable=False)

假设User对象具有一个Address,它已经是持久的:

>>> u1 = User(name='ed', addresses=[Address(email_address='ed@ed.com')])
>>> session.add(u1)
>>> session.commit()

我们现在创建a1,这是会话之外的一个对象,我们希望在现有的Address之上合并:

>>> existing_a1 = u1.addresses[0]
>>> a1 = Address(id=existing_a1.id)

如果我们这样说,会发生一个惊喜:

>>> a1.user = u1
>>> a1 = session.merge(a1)
>>> session.commit()
sqlalchemy.orm.exc.FlushError: New instance <Address at 0x1298f50>
with identity key (<class '__main__.Address'>, (1,)) conflicts with
persistent instance <Address at 0x12a25d0>

这是为什么 ?我们不小心,我们的瀑布。a1.user赋值给级联到User.addresses的backref的持久化对象,并使我们的a1对象处于挂起状态,就好像我们已经添加它。现在我们在会话中有两个 Address对象:

>>> a1 = Address()
>>> a1.user = u1
>>> a1 in session
True
>>> existing_a1 in session
True
>>> a1 is existing_a1
False

上面,我们的a1在会话中已经挂起。随后的merge()操作本质上什么都不做。Cascade can be configured via the cascade option on relationship(), although in this case it would mean removing the save-update cascade from the User.addresses relationship - and usually, that behavior is extremely convenient. The solution here would usually be to not assign a1.user to an object already persistent in the target session.

relationship()cascade_backrefs=False选项也将阻止Address通过a1添加到会话.user = u1赋值。

级联操作的进一步细节在Cascades

意外状态的另一个例子:

>>> a1 = Address(id=existing_a1.id, user_id=u1.id)
>>> assert a1.user is None
>>> True
>>> a1 = session.merge(a1)
>>> session.commit()
sqlalchemy.exc.IntegrityError: (IntegrityError) address.user_id
may not be NULL

Here, we accessed a1.user, which returned its default value of None, which as a result of this access, has been placed in the __dict__ of our object a1. 通常,此操作不会创建更改事件,因此在刷新期间,user_id属性优先。但是当我们将Address对象合并到会话中时,操作等同于:

>>> existing_a1.id = existing_a1.id
>>> existing_a1.user_id = u1.id
>>> existing_a1.user = None

Where above, both user_id and user are assigned to, and change events are emitted for both. user关联优先,而user_id没有应用,导致失败。

大多数merge()问题可以通过首先检查来检查 - 会话中的对象是否过早?

>>> a1 = Address(id=existing_a1, user_id=user.id)
>>> assert a1 not in session
>>> a1 = session.merge(a1)

或者在对象上有我们不想要的状态?检查__dict__是检查以下内容的快速方法:

>>> a1 = Address(id=existing_a1, user_id=user.id)
>>> a1.user
>>> a1.__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x1298d10>,
    'user_id': 1,
    'id': 1,
    'user': None}
>>> # we don't want user=None merged, remove it
>>> del a1.user
>>> a1 = session.merge(a1)
>>> # success
>>> session.commit()

清除日期¶ T0>

清除从会话中删除一个对象,发送持久实例到分离状态,暂挂实例到瞬态:

session.expunge(obj1)

要移除所有项目,请调用expunge_all()(此方法以前称为clear())。

刷新/过期

Expiring means that the database-persisted data held inside a series of object attributes is erased, in such a way that when those attributes are next accessed, a SQL query is emitted which will refresh that data from the database.

当我们谈到数据到期时,我们通常会谈论一个处于persistent状态的对象。例如,如果我们加载一个对象如下:

user = session.query(User).filter_by(name='user1').first()

上面的User对象是持久的,并且有一系列属性存在;如果我们查看它的__dict__,我们会看到加载状态:

>>> user.__dict__
{
  'id': 1, 'name': u'user1',
  '_sa_instance_state': <...>,
}

其中idname引用数据库中的那些列。_sa_instance_state is a non-database-persisted value used by SQLAlchemy internally (it refers to the InstanceState for the instance. 虽然与本节没有直接关系,但是如果我们想要了解它,我们应该使用inspect()函数来访问它)。

此时,我们的User对象中的状态与加载的数据库行的状态匹配。但是在使用诸如Session.expire()之类的方法使对象过期时,我们看到状态被删除:

>>> session.expire(user)
>>> user.__dict__
{'_sa_instance_state': <...>}

我们看到,虽然内部“状态”仍然挂起,但与idname列对应的值已消失。如果我们要访问这些列中的一个,并且正在观察SQL,我们会看到:

>>> print(user.name)
SELECT user.id AS user_id, user.name AS user_name FROM user WHERE user.id = ? (1,)
user1

上面,在访问过期的属性user.name时,ORM启动了一个lazy load来从数据库中检索最近的状态,方法是为用户行发出一个SELECT这个用户提到的。之后,再次填充__dict__

>>> user.__dict__
{
  'id': 1, 'name': u'user1',
  '_sa_instance_state': <...>,
}

注意

当我们在__dict__中查看时,为了看到SQLAlchemy对对象属性所做的一些操作,我们不应该修改__dict__的内容。直接,至少就SQLAlchemy ORM所维护的属性而言(SQLA领域之外的其他属性都可以)。这是因为SQLAlchemy使用descriptors来跟踪我们对一个对象的修改,当我们直接修改__dict__时,ORM将无法跟踪我们改变了一些。

expire()refresh()的另一个关键行为是丢弃对象上的所有未刷新的更改。也就是说,如果我们要修改User上的属性:

>>> user.name = 'user2'

but then we call expire() without first calling flush(), our pending value of 'user2' is discarded:

>>> session.expire(user)
>>> user.name
'user1'

可以使用expire()方法将实例的所有ORM映射的属性标记为“过期”:

# expire all ORM-mapped attributes on obj1
session.expire(obj1)

也可以传递一个字符串属性名称列表,引用特定的属性标记为过期:

# expire only attributes obj1.attr1, obj1.attr2
session.expire(obj1, ['attr1', 'attr2'])

refresh()方法有一个相似的接口,但不是过期,而是立即为对象的行发出立即的SELECT:

# reload all attributes on obj1
session.refresh(obj1)

refresh()也接受一个字符串属性名称列表,但不像expire(),至少有一个名称是列映射属性的名称:

# reload obj1.attr1, obj1.attr2
session.refresh(obj1, ['attr1', 'attr2'])

Session.expire_all()方法允许我们一次性调用Session中包含的所有对象的Session.expire()

session.expire_all()

什么实际上加载

标有expire()或加载refresh()的对象发出的SELECT语句根据以下几个因素而变化:

  • 过期属性的负载仅由列映射属性触发虽然任何类型的属性都可以标记为过期,包括relationship()映​​射的属性,但是访问过期的relationship()属性只会为该属性发出一个负载,使用标准的面向关系的延迟加载。面向列的属性(即使过期)不会作为此操作的一部分加载,而是会在访问任何面向列的属性时加载。
  • relationship()映​​射的属性不会加载,以响应正在访问的到期的基于列的属性。
  • 关于关系,对于未经过列映射的属性,refresh()expire()更具限制性。调用refresh()并传递仅包含关系映射属性的名称列表实际上会引发错误。在任何情况下,非急切加载relationship()属性将不会包含在任何刷新操作中。
  • relationship() attributes configured as “eager loading” via the lazy parameter will load in the case of refresh(), if either no attribute names are specified, or if their names are inclued in the list of attributes to be refreshed.
  • 配置为deferred()的属性通常不会在过期属性加载期间或刷新期间加载。当直接访问时,或者如果访问该组中的未加载属性的延迟属性的“组”,卸载的deferred()属性将自动加载。
  • 对于在访问时加载的过期属性,联合继承表映射将发出一个SELECT,通常只包含那些存在卸载属性的表。这里的操作足够复杂,只能加载父表或子表,例如,如果最初过期的列的子集只包含这些表中的一个或另一个。
  • 当在已连接的继承表映射上使用refresh()时,发出的SELECT类似于在目标对象的类上使用Session.query()时的SELECT。这通常是所有那些设置为映射的一部分的表。

何时过期或刷新

每当会话引用的事务结束时,Session自动使用到期功能。Meaning, whenever Session.commit() or Session.rollback() is called, all objects within the Session are expired, using a feature equivalent to that of the Session.expire_all() method. 其基本原理是交易的结束是一个划分点,在这个划分点上没有可用的上下文来知道数据库的当前状态,因为任何数量的其他事务都可能影响到它。只有当一个新的事务开始时,我们才能再次访问数据库的当前状态,此时可能发生了任何数量的更改。

当需要强制对象从数据库中重新加载其数据时,会使用Session.expire()Session.refresh()那些知道当前的数据状态可能是陈旧的情况。原因可能包括:

  • some SQL has been emitted within the transaction outside of the scope of the ORM’s object handling, such as if a Table.update() construct were emitted using the Session.execute() method;
  • 如果应用程序试图获取已知在并发事务中已被修改的数据,并且还知道隔离规则实际上允许该数据可见。

第二个要点有一个重要的警告,即“还知道隔离规则实际上允许这些数据是可见的”。这意味着不能认为发生在另一个数据库连接上的更新在本地是可见的。在很多情况下,它不会。这就是为什么如果希望使用expire()refresh()来查看正在进行的事务之间的数据,那么理解隔离行为是非常重要的。

也可以看看

Session.expire()

Session.expire_all()

Session.refresh()

isolation - glossary explanation of isolation which includes links to Wikipedia.

The SQLAlchemy Session In-Depth - a video + slides with an in-depth discussion of the object lifecycle including the role of data expiration.