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

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

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

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


当前回答

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

有时,您可能希望运行多条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,而您希望在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做出真正的更改(它们在数据库中可见) 同花顺会产生虚构的更改(仅对您可见)

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

First you have to understand that during a transaction you are not manipulating the real database data. Instead, you get something like a new branch, and there you play around. If at some point you write the command commit, that means: "merge my data-changes into main DB data". But if you need some future data, that you can get only after commit (ex. insert into a table, and you need the inserted PKID), then you use the flush command, meaning: "calculate me the future PKID, and reserve it for me". Then you can use that PKID value further in you code and be sure that the real data will be as expected. Commit must always come at the end, to merge into main DB data.

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

有时,您可能希望运行多条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,而您希望在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()总是作为commit()(1)调用的一部分被调用。当您使用Session对象查询数据库时,查询将返回来自数据库和它正在执行的未记录事务的红色部分的结果。

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

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

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

不用脸红,约翰。Id将为null。

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

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


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

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

说得好听点:一组操作必须全部成功执行,否则它们都不会生效。

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

因此,如果(B)需要(a)的结果,则希望在(a)之后调用flush,并在(B)之后提交。

同样,if session。autoflush是True,除了我上面提到的情况或Jimbo回答中的其他情况,您将不需要手动调用flush。