在SQLAlchemy中flush()和commit()之间有什么区别?
我读了这些文件,但还是不明白——他们似乎假设了一个我没有的预先理解。
我对它们对内存使用的影响特别感兴趣。我正在从一系列文件(总共大约500万行)中加载一些数据到数据库中,我的会话偶尔会崩溃——这是一个很大的数据库,而且机器没有太多内存。
我想知道我是否使用了太多的commit()而没有足够的flush()调用-但如果没有真正理解其中的区别,就很难判断!
在SQLAlchemy中flush()和commit()之间有什么区别?
我读了这些文件,但还是不明白——他们似乎假设了一个我没有的预先理解。
我对它们对内存使用的影响特别感兴趣。我正在从一系列文件(总共大约500万行)中加载一些数据到数据库中,我的会话偶尔会崩溃——这是一个很大的数据库,而且机器没有太多内存。
我想知道我是否使用了太多的commit()而没有足够的flush()调用-但如果没有真正理解其中的区别,就很难判断!
当前回答
如果你能承诺,为什么要冲?
作为一个使用数据库和sqlalchemy的新手,之前的答案——flush()将SQL语句发送到DB并commit()持久化它们——对我来说并不清楚。这些定义是有意义的,但从定义中不能立即清楚为什么要使用刷新而不是仅仅提交。
由于提交总是刷新(https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing),这些听起来非常相似。我认为需要强调的一个大问题是刷新不是永久的,可以被撤销,而提交是永久的,在某种意义上,你不能要求数据库撤销上次提交(我认为)。
@snapshoe强调,如果你想查询数据库并获得包含新添加对象的结果,你需要先刷新(或提交,这将为你刷新)。也许这对一些人来说是有用的,尽管我不确定为什么你想要flush而不是commit(除了它可以被撤消的琐碎答案)。
In another example I was syncing documents between a local DB and a remote server, and if the user decided to cancel, all adds/updates/deletes should be undone (i.e. no partial sync, only a full sync). When updating a single document I've decided to simply delete the old row and add the updated version from the remote server. It turns out that due to the way sqlalchemy is written, order of operations when committing is not guaranteed. This resulted in adding a duplicate version (before attempting to delete the old one), which resulted in the DB failing a unique constraint. To get around this I used flush() so that order was maintained, but I could still undo if later the sync process failed.
在sqlalchemy中提交时是否有添加和删除的顺序
类似地,有人想知道提交时是否保持添加顺序,即如果我添加object1,然后添加object2, object1是否在object2之前添加到数据库 SQLAlchemy在向会话添加对象时保存顺序吗?
同样,这里假定使用flush()将确保所需的行为。总之,flush的一个用途是提供顺序保证(我认为),同时仍然允许自己使用commit没有提供的“撤消”选项。
自动刷新和自动提交
注意,autoflush可用于确保查询作用于已更新的数据库,因为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
内存使用情况
原来的问题实际上是想知道flush和commit对内存的影响。由于数据库提供了持久化或不持久化的能力(我认为),简单地刷新应该足以将数据卸载到数据库中——尽管如果您不关心撤消,提交应该不会造成伤害(实际上可能会有所帮助——参见下文)。
Sqlalchemy对已刷新的对象使用弱引用:https://docs.sqlalchemy.org/en/13/orm/session_state_management.html#session-referencing-behavior
这意味着如果您没有显式地保留某个对象,例如在列表或字典中,sqlalchemy将不会将其保存在内存中。
但是,您还需要担心数据库方面的问题。假定在未提交的情况下刷新会带来一些内存损失,以维护事务。再说一次,我对这个不熟悉,但这里有一个链接,似乎正好说明了这一点:https://stackoverflow.com/a/15305650/764365
换句话说,提交应该减少内存使用,尽管这里可能存在内存和性能之间的权衡。换句话说,您可能不想每次提交一个数据库更改(出于性能原因),但是等待太久会增加内存使用。
其他回答
除非您了解数据库事务是什么,否则现有的答案没有多大意义。(直到最近,我自己也是这样。)
有时,您可能希望运行多条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
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')>]
当需要模拟写入时使用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.
Commit()将这些更改记录在数据库中。Flush()总是作为commit()(1)调用的一部分被调用。当您使用Session对象查询数据库时,查询将返回来自数据库和它正在执行的未记录事务的红色部分的结果。