ChatGPT解决这个技术问题 Extra ChatGPT

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

SQLAlchemy 中的 flush()commit() 有什么区别?

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

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

我想知道我是否使用了太多的 commit() 而没有足够的 flush() 调用 - 但如果没有真正了解区别是什么,很难说!

赞成“没有人更聪明”...没有人喜欢 sqlalchemy 文档。 :(

I
Ilja Everilä

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)。它会在所有查询之前自动刷新,因此您不必每次都记住。
R
Romain Vincent

这并没有严格回答最初的问题,但有些人提到使用 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(尽管对象的查询会,这有时会导致混淆,例如“为什么这在这里有效但在那里无效?”但 snapshoe 已经涵盖了这部分)。

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

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

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

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

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

此外,如果 session.autoflush is True,除了我上面提到的情况或 Jimbo 的答案中的其他情况外,您不需要手动调用 flush


A
Adam Hughes

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

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

son=Person(name='Bill Smith', parent=john.id)

如果没有刷新,john.id 将为空。

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


J
Jimbo

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

作为刚接触数据库和 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 然后添加 object2object1 是否在 object2 Does SQLAlchemy save order when adding objects to session? 之前添加到数据库

同样,这里大概使用 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.autocommit

内存使用情况

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

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

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

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

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


autocommit 是人们通常与数据库交互的简单而直观的方式。没有事务,或者更准确地说,每个操作本身就是一个事务,它自动开始并提交(因此为 autocommit)。想想如何在不发出 begin/commit 语句的情况下通过 cli 客户端更新表。实际上,您的查询将包含在隐式事务中。建议不要在应用中使用它,因为它破坏了 RDBMS 最强大的卖点之一,即事务原子性-一致性-隔离-持久性 (ACID) 的承诺。
B
Ben

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

有时您可能希望运行多个 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 

K
Kolya Sotnichenko

commit() 将这些更改记录在数据库中。 flush () 总是作为 commit () (1) 调用的一部分调用。当您使用 Session 对象查询数据库时,查询会从数据库和它正在执行的未记录事务的红色部分返回结果。


这个答案并没有真正为已经存在的答案添加任何东西..?