SQLAlchemy:flush() 和 commit() 有啥区别?
Posted
技术标签:
【中文标题】SQLAlchemy:flush() 和 commit() 有啥区别?【英文标题】:SQLAlchemy: What's the difference between flush() and commit()?SQLAlchemy:flush() 和 commit() 有什么区别? 【发布时间】:2011-05-11 05:30:28 【问题描述】:SQLAlchemy 中的flush()
和commit()
有什么区别?
我已经阅读了文档,但并不明智 - 他们似乎假设了我没有的预先理解。
我对它们对内存使用的影响特别感兴趣。我正在从一系列文件(总共大约 500 万行)中将一些数据加载到数据库中,并且我的会话偶尔会失败 - 这是一个大型数据库和一台内存不多的机器。
我想知道我是否使用了太多的 commit()
而不是足够的 flush()
调用 - 但如果没有真正了解区别是什么,很难说!
【问题讨论】:
【参考方案1】:Session 对象基本上是对数据库进行更改(更新、插入、删除)的持续事务。这些操作在提交之前不会持久化到数据库中(如果您的程序在会话中的事务中由于某种原因中止,则其中任何未提交的更改都将丢失)。
会话对象向session.add()
注册事务操作,但在调用session.flush()
之前不会将它们传送到数据库。
session.flush()
向数据库传达一系列操作(插入、更新、删除)。数据库将它们作为事务中的待处理操作进行维护。在数据库接收到当前事务的 COMMIT(session.commit()
所做的)之前,这些更改不会永久保存到磁盘上,或者对其他事务可见。
session.commit()
将这些更改提交(保留)到数据库。
flush()
总是在调用 commit()
(1) 时被调用。
当您使用 Session 对象查询数据库时,查询将返回来自数据库和它所持有的未提交事务的已刷新部分的结果。默认情况下,Session对象autoflush
他们的操作,但是这个可以被禁用。
希望这个例子能更清楚地说明这一点:
#---
s = Session()
s.add(Foo('A')) # The Foo('A') object has been added to the session.
# It has not been committed to the database yet,
# but is returned as part of a query.
print 1, s.query(Foo).all()
s.commit()
#---
s2 = Session()
s2.autoflush = False
s2.add(Foo('B'))
print 2, s2.query(Foo).all() # The Foo('B') object is *not* returned
# as part of this query because it hasn't
# been flushed yet.
s2.flush() # Now, Foo('B') is in the same state as
# Foo('A') was above.
print 3, s2.query(Foo).all()
s2.rollback() # Foo('B') has not been committed, and rolling
# back the session's transaction removes it
# from the session.
print 4, s2.query(Foo).all()
#---
Output:
1 [<Foo('A')>]
2 [<Foo('A')>]
3 [<Foo('A')>, <Foo('B')>]
4 [<Foo('A')>]
【讨论】:
还有一件事:你知道调用 commit() 是增加还是减少使用的内存? 这对于不支持事务的数据库引擎(例如 myisam)也是错误的。由于没有正在进行的事务,因此刷新与提交的区别就更少了。 @underrun 那么如果我在session.flush()
之后执行session.query()
,我会看到我的更改吗?鉴于我正在使用 MyISAM。
使用flush()
和commit()
的风格是好是坏,还是我应该把它留给Alchemy。我在某些情况下使用了flush()
,因为后续查询需要获取新数据。
@Jens 使用autoflush
(默认为True
)。它会在所有查询之前自动刷新,因此您不必每次都记住。【参考方案2】:
这并没有严格回答最初的问题,但有些人提到使用session.autoflush = True
您不必使用session.flush()
...这并不总是正确的。
如果你想在事务中间使用新创建对象的id,你必须调用session.flush()
。
# Given a model with at least this id
class AModel(Base):
id = Column(Integer, primary_key=True) # autoincrement by default on integer primary key
session.autoflush = True
a = AModel()
session.add(a)
a.id # None
session.flush()
a.id # autoincremented integer
这是因为autoflush
确实 NOT 自动填充 id(尽管对象的查询会,这有时会导致混淆,例如“为什么这在这里有效但在那里无效?”但是@ 987654321@已经覆盖了这部分)。
一个对我来说似乎很重要但没有真正提及的相关方面:
你为什么不一直提交? - 答案是原子性。
一个花哨的词:一组操作必须全部成功执行,否则它们都不会生效。
例如,如果您想创建/更新/删除某个对象 (A),然后创建/更新/删除另一个 (B),但如果 (B) 失败,您希望恢复 (A)。这意味着这两个操作是原子的。
因此,如果 (B) 需要 (A) 的结果,您需要在 (A) 之后调用 flush
,在 (B) 之后调用 commit
。
另外,如果session.autoflush is True
,除了我上面提到的情况或Jimbo的回答中的其他情况,您不需要手动调用flush
。
【讨论】:
【参考方案3】:如果可以提交,为什么要刷新?
作为刚接触数据库和 sqlalchemy 的人,以前的答案——flush()
将 SQL 语句发送到数据库,commit()
将它们持久化——对我来说并不清楚。这些定义是有道理的,但从定义中并不清楚为什么要使用刷新而不是仅仅提交。
由于提交总是刷新 (https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing),这些听起来非常相似。我认为要强调的大问题是刷新不是永久性的并且可以撤消,而提交是永久性的,因为您不能要求数据库撤消最后一次提交(我认为)
@snapshoe 强调,如果您想查询数据库并获得包含新添加对象的结果,您需要先刷新(或提交,这将为您刷新)。也许这对某些人有用,尽管我不确定您为什么要刷新而不是提交(除了可以撤消的琐碎答案)。
在另一个示例中,我在本地数据库和远程服务器之间同步文档,如果用户决定取消,所有添加/更新/删除都应该撤消(即没有部分同步,只有完全同步)。在更新单个文档时,我决定简单地删除旧行并从远程服务器添加更新版本。事实证明,由于 sqlalchemy 的编写方式,无法保证提交时的操作顺序。这导致添加了重复版本(在尝试删除旧版本之前),从而导致数据库未能通过唯一约束。为了解决这个问题,我使用了flush()
,以便保持顺序,但如果稍后同步过程失败,我仍然可以撤消。
请参阅我的帖子:Is there any order for add versus delete when committing in sqlalchemy
同样,有人想知道提交时是否保持添加顺序,即如果我添加object1
然后添加object2
,object1
是否会在object2
之前添加到数据库中
Does SQLAlchemy save order when adding objects to session?
同样,这里大概使用 flush() 将确保所需的行为。所以总而言之,flush 的一个用途是提供顺序保证(我认为),同时仍然允许自己使用提交不提供的“撤消”选项。
自动刷新和自动提交
注意,自动刷新可用于确保查询作用于更新的数据库,因为 sqlalchemy 将在执行查询之前刷新。 https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autoflush
自动提交是我不完全理解的其他东西,但听起来不鼓励使用它: https://docs.sqlalchemy.org/en/13/orm/session_api.html#sqlalchemy.orm.session.Session.params.autocommit
内存使用情况
现在最初的问题实际上是想了解刷新与提交对内存的影响。由于持久化或不持久化的能力是数据库提供的(我认为),简单的刷新应该足以卸载到数据库 - 尽管如果你不关心撤消提交不应该受到伤害(实际上可能有帮助 - 见下文) .
sqlalchemy 对已刷新的对象使用弱引用:https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior
这意味着如果您没有明确地将对象保存在某个位置,例如在列表或字典中,sqlalchemy 不会将其保存在内存中。
但是,您需要担心数据库方面的事情。据推测,在不提交的情况下刷新会带来一些内存损失来维护事务。同样,我对此并不陌生,但这里的链接似乎正好表明了这一点:https://***.com/a/15305650/764365
换句话说,提交应该减少内存使用,尽管这里可能需要在内存和性能之间进行权衡。换句话说,您可能不想一次提交一个数据库更改(出于性能原因),但是等待太久会增加内存使用量。
【讨论】:
autocommit
是人们通常与数据库交互的简单而直观的方式。没有事务,或者更准确地说,每个操作本身就是一个事务,它自动开始并提交(因此autocommit
)。想一想如何在不发出 begin/commit
语句的情况下通过 cli 客户端更新表。实际上,您的查询将包含在隐式事务中。建议不要在应用中使用它,因为它破坏了 RDBMS 最强大的卖点之一,即事务原子性-一致性-隔离-持久性 (ACID) 的承诺。【参考方案4】:
当您需要模拟写入时使用刷新,例如从自动递增计数器获取主键 ID。
john=Person(name='John Smith', parent=None)
session.add(john)
session.flush()
son=Person(name='Bill Smith', parent=john.id)
如果不刷新,john.id
将为空。
正如其他人所说,没有commit()
,这些都不会永久保存到数据库中。
【讨论】:
【参考方案5】:除非您了解什么是数据库事务,否则现有的答案没有多大意义。 (直到最近我都是这样。)
有时您可能希望运行多个 SQL 语句并让它们成功或失败作为一个整体。例如,如果您想执行从账户 A 到账户 B 的银行转账,您需要执行两个查询,例如
UPDATE accounts SET value = value - 100 WHERE acct = 'A'
UPDATE accounts SET value = value + 100 WHERE acct = 'B'
如果第一个查询成功但第二个查询失败,这很糟糕(原因很明显)。因此,我们需要一种“整体”处理这两个查询的方法。解决方案是从 BEGIN 语句开始,以 COMMIT 语句或 ROLLBACK 语句结束,例如
BEGIN
UPDATE accounts SET value = value - 100 WHERE acct = 'A'
UPDATE accounts SET value = value + 100 WHERE acct = 'B'
COMMIT
这是单笔交易。
在 SQLAlchemy 的 ORM 中,这可能看起来像
# BEGIN issued here
acctA = session.query(Account).get(1) # SELECT issued here
acctB = session.query(Account).get(2) # SELECT issued here
acctA.value -= 100
acctB.value += 100
session.commit() # UPDATEs and COMMIT issued here
如果您监控何时执行各种查询,您将看到更新不会到达数据库,直到您调用session.commit()
。
在某些情况下,您可能希望在发出 COMMIT 之前执行 UPDATE 语句。 (也许数据库向对象发出一个自动递增的 id 并且您想在提交之前获取它)。在这些情况下,您可以显式地flush()
会话。
# BEGIN issued here
acctA = session.query(Account).get(1) # SELECT issued here
acctB = session.query(Account).get(2) # SELECT issued here
acctA.value -= 100
acctB.value += 100
session.flush() # UPDATEs issued here
session.commit() # COMMIT issued here
【讨论】:
【参考方案6】:commit() 将这些更改记录在数据库中。 flush () 总是作为 commit () (1) 调用的一部分调用。当您使用 Session 对象查询数据库时,查询会从数据库和它正在执行的未记录事务的红色部分返回结果。
【讨论】:
这个答案并没有真正为已经存在的答案添加任何东西..?以上是关于SQLAlchemy:flush() 和 commit() 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
ob_flush()和flush()和ob_implicit_flush(true)