SQLAlchemy:flush()和commit()之间有什么区别?

AP2*_*257 386 python sqlalchemy

SQLAlchemy flush()commit()SQLAlchemy 之间有什么区别?

我已经阅读了文档,但没有更明智 - 他们似乎假设我没有预先理解.

我对它们对内存使用的影响特别感兴趣.我正在从一系列文件(总共约500万行)中将一些数据加载到数据库中,并且我的会话偶尔会崩溃 - 它是一个大型数据库和一台内存不足的机器.

我想知道我是否使用了太多但commit()没有足够的flush()电话 - 但是如果没有真正了解其中的差异,那就很难说了!

sna*_*hoe 473

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')>]
Run Code Online (Sandbox Code Playgroud)

  • 使用 `flush()` 和 `commit()` 的风格是好是坏,或者我应该把它留给 Alchemy。在某些情况下我使用了“flush()”,因为后续查询需要获取新数据。 (3认同)
  • 对于不支持myisam等事务的数据库引擎,这也是错误的.由于没有正在进行的事务,因此flush更难以区别于提交. (2认同)

Jac*_*cob 20

正如@snapshoe所说

flush() 将SQL语句发送到数据库

commit() 提交交易.

session.autocommit == False时:

如果你的autoflush == True,commit()将调用flush().

当session.autocommit == True时:

如果您尚未启动事务(您可能没有,因为您可能只使用此模式来避免手动管理事务),则无法调用commit().

在此模式下,您必须调用flush()以保存ORM更改.有效刷新也会提交您的数据.

  • "如果你的autoflush == True,commit()将调用flush()." 不完全正确,或只是误导.无论autoflush设置如何,提交始终都会刷新. (12认同)
  • `autoflush` 参数控制如果在发出查询之前有挂起的写入,sqlalchemy 是否首先发出刷新,并且与控制提交时不可避免的刷新无关。 (3认同)

Rom*_*ent 18

这并没有严格回答最初的问题,但有些人提到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
Run Code Online (Sandbox Code Playgroud)

这是因为autoflush自动填写ID(尽管对象的查询将,这有时会导致混乱,如“为什么这个作品在这里,但不是吗?”但是,snapshoe已涵盖这一部分)。


一个对我来说似乎很重要但并未真正提及的相关方面:

你为什么不一直承诺?- 答案是原子性

一个花哨的说法:一组操作必须全部成功执行,否则它们都不会生效。

例如,如果您想创建/更新/删除某个对象 (A),然后创建/更新/删除另一个 (B),但如果 (B) 失败,您想恢复 (A)。这意味着这两个操作是原子的

因此,如果 (B) 需要 (A) 的结果,您需要flush在 (A)commit之后和(B) 之后调用。

此外,如果session.autoflush is True,除了我上面提到的情况或金回答中的其他情况外,您将不需要flush手动拨打电话。


Jim*_*mbo 16

如果可以提交,为什么要刷新?

作为使用数据库和 sqlalchemy 的新手,之前的答案 -flush()将 SQL 语句发送到数据库并commit()保留它们 - 我不清楚。这些定义是有道理的,但从定义中并不能立即清楚为什么要使用刷新而不是仅仅提交。

由于提交总是刷新(https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing),这些听起来非常相似。我认为要强调的大问题是刷新不是永久性的并且可以撤消,而提交是永久性的,从某种意义上说,您不能要求数据库撤消最后一次提交(我认为)

@snapshoe 强调,如果您想查询数据库并获得包含新添加对象的结果,您需要先刷新(或提交,它将为您刷新)。也许这对某些人有用,尽管我不确定您为什么要刷新而不是提交(除了可以撤消的简单答案)。

在另一个例子中,我在本地数据库和远程服务器之间同步文档,如果用户决定取消,所有添加/更新/删除都应该被撤消(即没有部分同步,只有完全同步)。更新单个文档时,我决定简单地删除旧行并从远程服务器添加更新版本。事实证明,由于 sqlalchemy 的编写方式,无法保证提交时的操作顺序。这导致添加了重复版本(在尝试删除旧版本之前),从而导致数据库无法通过唯一约束。为了解决这个问题,我使用了flush()这个顺序,但如果稍后同步过程失败,我仍然可以撤消。

请参阅我的帖子:在 sqlalchemy 中提交时,是否有添加与删除的顺序

同样,有人想知道提交时是否保持添加顺序,即如果我添加object1然后添加object2,是否object1在将对象添加到会话时 SQLAlchemy 保存顺序之前添加到数据库中object2

同样,这里大概使用 flush() 将确保所需的行为。因此,总而言之,flush 的一个用途是提供顺序保证(我认为),同时仍然允许自己使用 commit 不提供的“撤消”选项。

自动刷新和自动提交

请注意,自动刷新可用于确保查询对更新的数据库起作用,因为 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。自动提交

内存使用情况

现在最初的问题实际上想知道出于内存目的的刷新与提交的影响。由于持久化的能力是数据库提供的(我认为),简单的刷新应该足以卸载到数据库 - 尽管提交不应该受到伤害(实际上可能有帮助 - 见下文),如果你不关心撤消.

sqlalchemy 对已刷新的对象使用弱引用:https : //docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior

这意味着如果您没有将对象显式保存在某处,例如在列表或字典中,sqlalchemy 不会将其保存在内存中。

但是,那么您需要担心数据库方面的事情。据推测,在不提交的情况下刷新会带来一些内存损失来维护事务。再次,我是新手,但这里有一个链接似乎暗示了这一点:https : //stackoverflow.com/a/15305650/764365

换句话说,提交应该减少内存使用,尽管这里可能需要在内存和性能之间进行权衡。换句话说,您可能不想一次提交每个数据库更改(出于性能原因),但等待时间过长会增加内存使用量。


Ben*_*Ben 12

除非您了解数据库事务是什么,否则现有答案没有多大意义。(直到最近我自己的情况也是如此。)

有时您可能希望运行多个 SQL 语句并让它们作为一个整体成功或失败。例如,如果您想执行从账户 A 到账户 B 的银行转账,您需要执行两个查询,例如

UPDATE accounts SET value = value - 100 WHERE acct = 'A'
UPDATE accounts SET value = value + 100 WHERE acct = 'B'
Run Code Online (Sandbox Code Playgroud)

如果第一个查询成功但第二个查询失败,这很糟糕(出于明显的原因)。因此,我们需要一种方法来将这两个查询“视为一个整体”。解决方案是以 BEGIN 语句开始,以 COMMIT 语句或 ROLLBACK 语句结束,例如

BEGIN
UPDATE accounts SET value = value - 100 WHERE acct = 'A'
UPDATE accounts SET value = value + 100 WHERE acct = 'B'
COMMIT
Run Code Online (Sandbox Code Playgroud)

这是单笔交易

在 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 
Run Code Online (Sandbox Code Playgroud)

如果您监视各种查询的执行时间,您将看到更新不会到达数据库,直到您调用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 
Run Code Online (Sandbox Code Playgroud)


Ada*_*hes 6

当您需要模拟写入时使用刷新,例如从自动递增计数器获取主键 ID。

john=Person(name='John Smith', parent=None)
session.add(john)
session.flush()

son=Person(name='Bill Smith', parent=john.id)
Run Code Online (Sandbox Code Playgroud)

如果不刷新,john将永远不会从数据库中获取 ID,因此无法在代码中表示父/子关系。

就像其他人所说的那样,如果commit()没有,这些都不会永久保存到数据库中。


ela*_*no7 5

对于简单的定向:

  • commit进行真正的更改(它们在数据库中变得可见)
  • flush进行虚构的更改(它们仅对您可见)

想象一下数据库的工作方式就像 git 分支:

  • 首先,您必须了解,在执行过程中transaction 您并没有操作真正的数据库数据。
  • 相反,你会得到类似新的东西branch,然后你就可以玩了。
  • 如果在某个时刻您编写了该命令commit,则意味着:“merge我的数据更改为主数据库数据”。
  • 但是,如果您需要一些未来的数据,只有在之后才能获取commit(例如,插入到表中,并且您需要插入PKID),那么您可以使用该flush命令,意思是:“计算我的未来PKID并为我保留它。然后,您可以PKID在代码中进一步使用该值,并确保真实数据符合预期。
  • Commit必须始终位于最后,以合并到主数据库数据中。