flush()和commit()的区别是什么?

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

我读了这些文件,但还是不明白——他们似乎假设了一个我没有的预先理解。

我对它们对内存使用的影响特别感兴趣。我正在从一系列文件(总共大约500万行)中加载一些数据到数据库中,我的会话偶尔会崩溃——这是一个很大的数据库,而且机器没有太多内存。

我想知道我是否使用了太多的commit()调用,而没有足够的flush()调用-但没有真正理解其中的区别,这是很难判断的!

204612 次浏览

Session对象基本上是对数据库进行更改(更新、插入、删除)的持续事务。这些操作在提交之前不会持久化到数据库中(如果您的程序在会话事务中由于某种原因中止,其中任何未提交的更改都将丢失)。

会话对象向session.add()注册事务操作,但直到调用session.flush()才将它们传递给数据库。

session.flush()向数据库传递一系列操作(插入、更新、删除)。数据库将它们作为事务中的挂起操作来维护。更改不会永久保存到磁盘上,也不会对其他事务可见,直到数据库接收到当前事务的COMMIT(这就是session.commit()所做的)。

session.commit()将这些更改提交(持久化)到数据库。

flush()总是,作为对commit() (1)调用的一部分。

当使用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')>]

如果你能承诺,为什么要冲?

作为一个使用数据库和sqlalchemy的新手,前面的答案——flush()将SQL语句发送到DB,而commit()将它们持久化——对我来说并不清楚。这些定义是有意义的,但从定义中不能立即清楚为什么要使用刷新而不是仅仅提交。

由于提交总是刷新(https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing),这听起来非常相似。我认为需要强调的一个大问题是刷新不是永久的,可以被撤销,而提交是永久的,在某种意义上,你不能要求数据库撤销上次提交(我认为)。

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

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

可以在在sqlalchemy中提交时是否有添加和删除的顺序上看到我的帖子

类似地,有人想知道提交时是否保持添加顺序,即如果我添加object1,然后添加object2object1是否在object2之前添加到数据库 # EYZ0 < / p >

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

自动刷新和自动提交

注意,autoflush可用于确保查询作用于已更新的数据库,因为sqlalchemy将在执行查询之前刷新。# EYZ0

自动提交是另一个我不完全理解的东西,但听起来它的使用是不鼓励的: # EYZ0 < / p >

内存使用情况

原来的问题实际上是想知道flush和commit对内存的影响。由于数据库提供了持久化或不持久化的能力(我认为),简单地刷新应该足以将数据卸载到数据库中——尽管如果您不关心撤消,提交应该不会造成伤害(实际上可能会有所帮助——参见下文)。

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

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

但是,您还需要担心数据库方面的问题。假定在未提交的情况下刷新会带来一些内存损失,以维护事务。再说一次,我是新手,但这里有一个链接,似乎正是这样建议的:https://stackoverflow.com/a/15305650/764365

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

这并没有严格地回答最初的问题,但有些人已经提到,使用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会自动填充id(尽管对对象的查询会自动填充id,但有时会引起混淆,比如“为什么在这里可以,而在那里不行?”但是snapshoe已经涵盖了这一部分)。


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

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

华丽的说法是:一个操作集合必须成功执行所有,否则它们都不会生效。

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

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

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

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

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


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

如果不刷新,john.id将为null。

正如其他人所说,如果没有commit(),这些都不会永久地持久化到DB中。

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

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

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

# EYZ0。

在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,而您希望在commit之前获取它)。在这些情况下,您可以显式地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

简单定位:

  • commit使真正的改变(它们在数据库中可见)
  • flush使虚构的变化(他们只对你可见)

想象一下数据库像git- branches一样工作。

  • 首先,您必须了解在transaction 你不是操作实际数据库数据期间。
  • 相反,您会得到一个类似于新的branch的东西,并在那里进行操作。
  • 如果在某一点上您编写了命令commit,这意味着:&;merge我的数据更改为主DB数据"。
  • 但如果你需要一些未来的数据,你只能在commit之后获得(例如,插入到一个表中,你需要插入PKID),然后你使用flush命令,意思是:“为我计算未来PKIDand reserve for me"
  • 然后,您可以在代码中进一步使用PKID值,并确保实际数据符合预期。
  • Commit必须始终出现在最后,以便合并到主DB数据中。