在SQLAlchemy中flush()和commit()之间有什么区别?
我读了这些文件,但还是不明白——他们似乎假设了一个我没有的预先理解。
我对它们对内存使用的影响特别感兴趣。我正在从一系列文件(总共大约500万行)中加载一些数据到数据库中,我的会话偶尔会崩溃——这是一个很大的数据库,而且机器没有太多内存。
我想知道我是否使用了太多的commit()而没有足够的flush()调用-但如果没有真正理解其中的区别,就很难判断!
在SQLAlchemy中flush()和commit()之间有什么区别?
我读了这些文件,但还是不明白——他们似乎假设了一个我没有的预先理解。
我对它们对内存使用的影响特别感兴趣。我正在从一系列文件(总共大约500万行)中加载一些数据到数据库中,我的会话偶尔会崩溃——这是一个很大的数据库,而且机器没有太多内存。
我想知道我是否使用了太多的commit()而没有足够的flush()调用-但如果没有真正理解其中的区别,就很难判断!
当前回答
简单定位:
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.
其他回答
Session对象基本上是对数据库进行更改(更新、插入、删除)的持续事务。这些操作在提交之前不会持久化到数据库中(如果您的程序在会话事务中由于某种原因中止,其中任何未提交的更改都将丢失)。
会话对象向session.add()注册事务操作,但直到调用session.flush()才将它们传递给数据库。
Session.flush()向数据库传递一系列操作(插入、更新、删除)。数据库将它们作为事务中的挂起操作来维护。更改不会永久保存到磁盘上,也不会对其他事务可见,直到数据库接收到当前事务的COMMIT(这就是session.commit()所做的)。
Session.commit()将这些更改提交(持久化)到数据库。
Flush()总是作为commit()(1)调用的一部分被调用。
当使用Session对象查询数据库时,查询将返回来自数据库和来自其持有的未提交事务的刷新部分的结果。默认情况下,会话对象自动刷新其操作,但这可以被禁用。
希望这个例子能让你更清楚:
#---
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()将这些更改记录在数据库中。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
这是一个单独的事务。
在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
当需要模拟写入时使用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中。
简单定位:
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.