会话/查询
我正在用会话加载数据,但没有看到我在其他地方所做的更改。
关于这种行为的主要问题是,会话的作用就像事务在 可串行化的 隔离状态,即使不是(通常不是)。实际上,这意味着会话不会更改在事务范围内已经读取的任何数据。
如果不熟悉术语“隔离级别”,则首先需要阅读此链接:
简而言之,可序列化隔离级别通常意味着一旦在事务中选择一系列行,您将获得 相同的数据 每次重新发出该选择时返回。如果您处于下一个较低的隔离级别“可重复读取”,您将看到新添加的行(不再看到已删除的行),但是对于您已经 已经 加载后,您将看不到任何更改。只有当您处于较低的隔离级别(例如“已提交读取”)时,才能看到一行数据更改其值。
有关使用SQLAlchemy ORM时控制隔离级别的信息,请参阅 设置事务隔离级别/DBAPI AUTOCOMMIT .
为了大大简化事情, Session
它本身的工作方式是完全独立的事务,并且不会覆盖它已经读取的任何映射属性,除非您告诉它。尝试重新读取已在正在进行的事务中加载的数据的用例是 罕见的 在许多情况下没有效果的用例,因此这被认为是异常,而不是规范;为了在这个异常中工作,提供了几种方法,允许在正在进行的事务的上下文中重新加载特定的数据。
当我们谈论 Session
你的 Session
仅用于事务中。概述如下: 管理交易 .
一旦我们知道了我们的隔离级别是什么,并且我们认为我们的隔离级别设置在足够低的级别,这样如果我们重新选择一行,我们应该在 Session
,我们怎么看?
三种方式,从最常见到最少:
我们只需结束我们的交易,并在下一次访问时使用我们的
Session
通过呼叫Session.commit()
(注意,如果Session
是在使用较少的“自动提交”模式下,会有一个呼叫Session.begin()
同样如此。绝大多数应用程序和用例不存在无法在其他事务中“看到”数据的任何问题,因为它们坚持这种模式,这是 短期交易 . 见 我什么时候做一个 Session ,什么时候提交,什么时候关闭? 想一想。我们告诉我们
Session
当我们下一次查询已经读取的行时,可以使用Session.expire_all()
或Session.expire()
或立即在对象上使用refresh
. 见 刷新/过期 有关详细信息。我们可以运行整个查询,同时通过使用“填充现有”将它们设置为在读取行时明确覆盖已加载的对象。这是一个执行选项,请参阅 填充现有 。
但请记住, 如果隔离级别为可重复读取或更高,ORM将看不到行中的更改,除非我们启动一个新事务。 .
“由于在刷新过程中出现以前的异常,此会话的事务已回滚。”(或类似)
这是一个错误,当 Session.flush()
引发异常,回滚事务,但在 Session
在没有显式调用的情况下调用 Session.rollback()
或 Session.close()
.
它通常对应于在 Session.flush()
或 Session.commit()
并且不能正确地处理异常。例如::
from sqlalchemy import create_engine, Column, Integer from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base Base = declarative_base(create_engine('sqlite://')) class Foo(Base): __tablename__ = 'foo' id = Column(Integer, primary_key=True) Base.metadata.create_all() session = sessionmaker()() # constraint violation session.add_all([Foo(id=1), Foo(id=1)]) try: session.commit() except: # ignore error pass # continue using session without rolling back session.commit()
使用 Session
应与以下结构类似:
try: <use session> session.commit() except: session.rollback() raise finally: session.close() # optional, depends on use case
除了冲洗以外,很多事情都会导致在尝试中失败。应用程序应确保将某些“框架”系统应用于面向ORM的流程,以便连接和事务资源具有确定的边界,并且在出现任何故障条件时,可以显式回滚事务。
这并不意味着在整个应用程序中都应该有try/except块,这将不是一个可扩展的体系结构。相反,一种典型的方法是,当第一次调用面向ORM的方法和函数时,从最上面调用函数的过程将位于一个块内,该块在一系列操作成功完成时提交事务,并在操作结束时回滚事务。操作因任何原因失败,包括失败的刷新。也有一些方法使用函数修饰器或上下文管理器来实现类似的结果。所采用的方法在很大程度上取决于所编写的应用程序的类型。
有关如何组织使用 Session
请看 我什么时候做一个 Session ,什么时候提交,什么时候关闭? .
但是为什么flush()坚持发布回滚?
如果 Session.flush()
可以部分完成,然后不回滚,但是这超出了它的当前功能,因为它的内部簿记必须进行修改,以便可以随时停止,并且与刷新到数据库的内容完全一致。虽然这在理论上是可能的,但由于许多数据库操作在任何情况下都需要回滚,因此增强功能的实用性大大降低。Postgres尤其具有一旦失败就不允许继续进行事务的操作::
test=> create table foo(id integer primary key); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo" CREATE TABLE test=> begin; BEGIN test=> insert into foo values(1); INSERT 0 1 test=> commit; COMMIT test=> begin; BEGIN test=> insert into foo values(1); ERROR: duplicate key value violates unique constraint "foo_pkey" test=> insert into foo values(2); ERROR: current transaction is aborted, commands ignored until end of transaction block
SQLAlchemy提供的解决这两个问题的方法是通过 Session.begin_nested()
. 使用 Session.begin_nested()
,您可以为可能在事务中失败的操作设置框架,然后在维护封闭事务的同时“回滚”到失败前的点。
但是为什么一个自动调用回滚不够?为什么我必须再次回滚?
由flush()引起的回滚不是完整事务块的结尾;当它在play中结束数据库事务时,从 Session
从观点来看,仍有一个事务处于非活动状态。
给定一个块,如:
sess = Session() # begins a logical transaction try: sess.flush() sess.commit() except: sess.rollback()
上面,当 Session
首先创建,假设不使用“自动提交模式”,则在 Session
.这个事务是“合乎逻辑的”,因为在调用SQL语句之前,它实际上不使用任何数据库资源,此时将启动连接级别和DBAPI级别的事务。但是,无论数据库级事务是否为其状态的一部分,逻辑事务都将一直保持原样,直到使用 Session.commit()
, Session.rollback()
或 Session.close()
.
当 flush()
上述操作失败,代码仍在try/commit/except/rollback块所框架的事务中。如果 flush()
如果要完全回滚逻辑事务,则意味着当我们到达 except:
阻止 Session
将处于干净状态,准备在所有新事务上发出新的SQL,并调用 Session.rollback()
可能是顺序不对。特别是, Session
会在此时开始一项新的交易, Session.rollback()
会有错误的行为。而不是允许SQL操作在正常使用要求回滚即将发生的地方继续执行新事务,而是 Session
相反,在显式回滚实际发生之前拒绝继续。
换句话说,调用代码将 总是 调用 Session.commit()
, Session.rollback()
或 Session.close()
与当前事务块相对应。 flush()
保留 Session
在这个事务块中,使上述代码的行为是可预测的和一致的。
如何创建一个始终为每个查询添加特定筛选器的查询?
请参阅以下网址的食谱 FilteredQuery 。
我的查询返回的对象数与查询.count()告诉我-为什么?
这个 Query
对象在被要求返回ORM映射对象的列表时,将 基于主键对对象进行重复数据消除 . 也就是说,如果我们使用 User
映射描述于 对象关系教程(1.x API) ,我们有一个SQL查询,如下所示:
q = session.query(User).outerjoin(User.addresses).filter(User.name == 'jack')
上面,教程中使用的示例数据在 addresses
为 users
具有名称的行 'jack'
,主键值5。如果我们问上面的查询 Query.count()
,我们会得到答案的 2 ::
>>> q.count() 2
但是,如果我们逃跑 Query.all()
或者迭代查询,我们会返回 一个元素 ::
>>> q.all() [User(id=5, name='jack', ...)]
这是因为当 Query
对象返回完整的实体,它们是 重复数据消除 . 如果我们改为请求单个列返回,则不会发生这种情况:
>>> session.query(User.id, User.name).outerjoin(User.addresses).filter(User.name == 'jack').all() [(5, 'jack'), (5, 'jack')]
有两个主要原因 Query
将重复数据消除:
使连接的紧急加载正常工作 - 加入的热切装载 它的工作原理是使用关联对相关表查询行,然后将这些联接中的行路由到lead对象上的集合中。对于主键中的每一行,它都要按照主键的顺序来执行。例如,可以将这种模式进一步处理成多行的子对象,这样就可以继续处理这种模式
User(id=5)
. depulation允许我们以查询对象的方式接收对象,例如User()
名称为的对象'jack'
对我们来说是一个目标User.addresses
收藏急切地装载,如lazy='joined'
上relationship()
或者通过joinedload()
选项。为了保持一致性,无论是否建立joinedload,都将应用重复数据消除,因为急切加载背后的关键原理是这些选项永远不会影响结果。消除关于身份图的混乱 -诚然,这是不那么关键的原因。作为
Session
利用 identity map ,即使我们的SQL结果集有两行主键为5,但只有一行User(id=5)
对象内部Session
必须在其标识(即,其主键/类组合)上进行唯一维护。如果有人在询问User()
对象,以在列表中多次获取同一对象。一个有序集可能会更好地表示什么Query
在返回完整对象时寻求返回。
问题 Query
重复数据消除仍然存在问题,主要原因是 Query.count()
方法是不一致的,当前的状态是,在最近的版本中,joined-eager-loading首先被“subquery-eager-loading”策略和最近的“select-in-eager-loading”策略所取代,这两种策略通常更适合于集合预加载。随着这一演进的继续,SQLAlchemy可能会在 Query
,这也可能涉及新的api,以便更直接地控制这种行为,也可能会改变joined eager loading的行为,以创建更一致的使用模式。
我已经创建了一个针对外部联接的映射,当查询返回行时,不会返回任何对象。为什么不呢?
外部联接返回的行可能包含部分主键的空值,因为主键是两个表的组合。这个 Query
对象忽略没有可接受主键的传入行。基于 allow_partial_pks
旗上 mapper()
,如果值至少有一个非空值,或者如果值没有空值,则接受主键。见 allow_partial_pks
在 mapper()
.
我在用 joinedload()
或 lazy=False
当我试图添加一个where、order by、limit等(依赖于(外部)连接)时,创建一个join/outer连接,而sqlAlchemy没有构造正确的查询。
由joined-eager加载生成的联接仅用于完全加载相关集合,并且设计为不影响查询的主要结果。由于它们是匿名别名,因此不能直接引用。
有关此行为的详细信息,请参见 加入渴望装载的禅宗 .
查询没有 __len__()
为什么不呢?
Python __len__()
应用于对象的magic方法允许 len()
用于确定集合长度的内置项。很直观,SQL查询对象会链接 __len__()
到 Query.count()
方法,它发出 SELECT COUNT . 这不可能的原因是,将查询作为列表进行计算将导致两个SQL调用,而不是一个:
class Iterates(object): def __len__(self): print("LEN!") return 5 def __iter__(self): print("ITER!") return iter([1, 2, 3, 4, 5]) list(Iterates())
输出:
ITER! LEN!
如何在ORM查询中使用文本SQL?
参见:
在会话中使用SQL表达式 -使用
Session
直接使用文本SQL。
我打电话来 Session.delete(myobject)
它不会从父集合中删除!
见 删除引用的对象和标量关系的注释 有关此行为的描述。
为什么不是我的 __init__()
加载对象时调用?
见 构造函数和对象初始化 有关此行为的描述。
如何使用SA的ORM删除层叠?
对于当前加载在 Session
. 对于未加载的行,默认情况下,它将发出select语句来加载这些行并更新/删除这些行;换句话说,它假定没有配置on delete cascade。要将SQLAlchemy配置为在删除级联时与之协作,请参见 在具有ORM关系的DELETE cascade中使用外键 .
我将实例上的“foo-id”属性设置为“7”,但“foo”属性仍然是 None
-难道它不应该给foo加载id_7吗?
ORM的构造方式不支持由外键属性更改驱动的关系的即时填充-相反,它的设计方式与之相反-外键属性由ORM在后台处理,最终用户自然设置对象关系。因此,建议的设置方法 o.foo
就是这么做-设置它!::
foo = Session.query(Foo).get(7) o.foo = foo Session.commit()
当然,操纵外键属性是完全合法的。但是,当前将外键属性设置为新值不会触发 relationship()
其中涉及到。这意味着对于以下序列:
o = Session.query(SomeClass).first() assert o.foo is None # accessing an un-set attribute sets it to None o.foo_id = 7
o.foo
初始化为 None
当我们第一次访问它时。设置 o.foo_id = 7
将值“7”设为挂起,但未发生刷新-因此 o.foo
仍然是 None
::
# attribute is already set to None, has not been # reconciled with o.foo_id = 7 yet assert o.foo is None
为了 o.foo
基于外键突变进行加载通常是在提交之后自然实现的,这两种方式都会刷新新的外键值并使所有状态都过期:
Session.commit() # expires all attributes foo_7 = Session.query(Foo).get(7) assert o.foo is foo_7 # o.foo lazyloads on access
最简单的操作是单独终止属性-这可以对任何 persistent 对象使用 Session.expire()
::
o = Session.query(SomeClass).first() o.foo_id = 7 Session.expire(o, ['foo']) # object must be persistent for this foo_7 = Session.query(Foo).get(7) assert o.foo is foo_7 # o.foo lazyloads on access
注意,如果对象不是持久的,而是存在于 Session
它被称为 pending . 这意味着对象的行尚未插入到数据库中。对于这样的对象,设置 foo_id
在插入行之前没有意义;否则还没有行::
new_obj = SomeClass() new_obj.foo_id = 7 Session.add(new_obj) # accessing an un-set attribute sets it to None assert new_obj.foo is None Session.flush() # emits INSERT # expire this because we already set .foo to None Session.expire(o, ['foo']) assert new_obj.foo is foo_7 # now it loads
非持久对象的属性加载
上面“挂起”行为的一个变体是如果我们使用标志 load_on_pending
在 relationship()
. 设置此标志时,惰性加载程序将为 new_obj.foo
在插入进行之前;这的另一个变体是使用 Session.enable_relationship_loading()
方法,它可以将对象“附加”到 Session
以这样一种方式,不管对象处于任何特定状态,多对一关系都按照外键属性加载。这两种技术都是 不建议一般使用 ;它们是为了适应用户遇到的特定编程场景而添加的,这些场景涉及到ORM通常对象状态的重新调整用途。
食谱 ExpireRelationshipOnFKChange 提供了一个使用SQLAlChemy事件的示例,以便将外键属性的设置与多对一关系协调起来。
如何浏览与给定对象相关的所有对象?
具有与之相关的其他对象的对象将与 relationship()
在映射器之间设置的构造。此代码片段将迭代所有对象,并修正循环:
from sqlalchemy import inspect def walk(obj): deque = [obj] seen = set() while deque: obj = deque.pop(0) if obj in seen: continue else: seen.add(obj) yield obj insp = inspect(obj) for relationship in insp.mapper.relationships: related = getattr(obj, relationship.key) if relationship.uselist: deque.extend(related) elif related is not None: deque.append(related)
该功能可演示如下:
Base = declarative_base() class A(Base): __tablename__ = 'a' id = Column(Integer, primary_key=True) bs = relationship("B", backref="a") class B(Base): __tablename__ = 'b' id = Column(Integer, primary_key=True) a_id = Column(ForeignKey('a.id')) c_id = Column(ForeignKey('c.id')) c = relationship("C", backref="bs") class C(Base): __tablename__ = 'c' id = Column(Integer, primary_key=True) a1 = A(bs=[B(), B(c=C())]) for obj in walk(a1): print(obj)
输出:
<__main__.A object at 0x10303b190> <__main__.B object at 0x103025210> <__main__.B object at 0x10303b0d0> <__main__.C object at 0x103025490>
是否有一种方法可以自动只具有唯一的关键字(或其他类型的对象),而不进行关键字查询并获取对包含该关键字的行的引用?
当人们阅读文档中的多对多示例时,他们会受到这样一个事实的打击:如果您创建了相同的示例 Keyword
两次,它被放入数据库两次。有点不方便。
这 UniqueObject 食谱就是为了解决这个问题而创建的。
为什么post-update除了第一个更新外还发出更新?
后更新功能,记录在 指向自身/相互依赖的行的行 ,涉及到除了通常为目标行发出的insert/update/delete之外,还会根据对特定关系绑定外键的更改发出update语句。虽然此update语句的主要目的是与该行的insert或delete进行配对,以便它可以对外键引用进行post-set或pre-unset,以便用相互依赖的外键中断循环,但当前它还绑定为第二个更新,当目标行本身受到更新时发出。在这种情况下,post-update发出的更新是 通常 不必要,而且经常显得浪费。
然而,一些试图消除这种“更新/更新”行为的研究表明,不仅需要在更新后的整个实施过程中,而且需要在与更新后工作无关的领域中,对工作单元过程进行重大更改,因为在非更新后,操作顺序需要颠倒。_在某些情况下,更新端会反过来影响其他情况,例如正确处理引用的主键值的更新(请参见 #1063 作为概念证明)。
答案是,“post_update”用于中断两个相互依赖的外键之间的循环,并且要使此循环中断仅限于插入/删除目标表,意味着需要对其他地方的更新语句的顺序进行自由化,从而在其他边缘情况下导致中断。