flush()
和commit()
在SQLAlchemy中有什么区别?
我已经阅读了文档,但并没有更清楚 - 它们似乎假定我已经具备了某些预先了解的知识。
我特别关注它们对内存使用的影响。 我正在从一系列文件(总共约500万行)向数据库加载一些数据,我的会话偶尔崩溃 - 这是一个大型数据库,并且机器内存不足。
我在想是否使用了太多的commit()
而没有足够的flush()
调用 - 但是如果我不真正理解它们之间的区别,那就很难判断!
flush()
和commit()
在SQLAlchemy中有什么区别?
我已经阅读了文档,但并没有更清楚 - 它们似乎假定我已经具备了某些预先了解的知识。
我特别关注它们对内存使用的影响。 我正在从一系列文件(总共约500万行)向数据库加载一些数据,我的会话偶尔崩溃 - 这是一个大型数据库,并且机器内存不足。
我在想是否使用了太多的commit()
而没有足够的flush()
调用 - 但是如果我不真正理解它们之间的区别,那就很难判断!
Session对象基本上是对数据库的更改(更新、插入、删除)的持续事务。这些操作在提交之前不会持久保存到数据库中(如果由于某种原因您的程序在事务过程中中止,则其中未提交的更改将丢失)。
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')>]
session.flush()
之后执行session.query()
,那么我能看到我的更改吗?假设我正在使用MyISAM。 - Frozen Flameflush()
和commit()
时,是否是好的风格,还是应该让Alchemy来处理?我在某些情况下使用了flush()
,因为后续查询需要获取新数据。 - Jensautoflush
(默认为True
)。它会在所有查询之前自动刷新,因此您不必每次都记住。 - Kiran Jonnalagadda这并不是严格意义上的回答原问题,但有些人提到使用 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(尽管对象的查询会填充,有时会导致困惑,例如“为什么在这里可以工作但在那里不能?”但snapshoe已经涵盖了这部分)。
我认为一个相关而重要的方面并未被提及:
为什么不一直提交? - 答案是原子性。
一个花哨的词来说:一系列操作必须全部成功执行,否则它们中的任何一个都将不起作用。
例如,如果您想创建/更新/删除某个对象(A),然后创建/更新/删除另一个对象(B),但如果(B)失败,则要恢复(A)。 这意味着这两个操作是原子的。
因此,如果(B)需要(A)的结果,则应在(A)后调用flush
,在(B)后调用commit
。
此外,如果session.autoflush为True
,除了我上面提到的情况或Jimbo答案中的其他情况,您将不需要手动调用flush
。
当您需要模拟写入时,请使用flush,例如从自动增量计数器中获取主键ID。
john=Person(name='John Smith', parent=None)
session.add(john)
session.flush()
son=Person(name='Bill Smith', parent=john.id)
如果不进行flush操作,john.id
将为null。
正如其他人所说,如果没有commit()
,则这些更改都不会永久地存储到数据库中。
为什么要刷新缓存而不是直接提交?
作为一个刚开始使用数据库和SQLAlchemy的人来说,之前的回答——flush()
将SQL语句发送到数据库中,而commit()
则将它们持久化——并没有让我很清楚。这些定义很有道理,但从定义上很难立即理解为什么你会使用刷新而不是直接提交。
由于提交总是会刷新(https://docs.sqlalchemy.org/en/13/orm/session_basics.html#committing),所以这两个听起来非常相似。我认为需要强调的问题是刷新不是永久的,可以被撤销,而提交是永久的,也就是说你不能要求数据库撤销最后一次提交(我想)。
@snapshoe强调,如果你想查询数据库并获得包括新添加对象的结果,你需要先刷新(或提交,提交将为您刷新)。也许这对一些人很有用,虽然我不确定为什么你会选择刷新而不是提交(除了可以被撤销这个微不足道的答案)。
在另一个例子中,我在本地数据库和远程服务器之间同步文档,如果用户决定取消操作,则所有的添加/更新/删除都应该被撤销(即不进行部分同步,只进行完全同步)。当更新单个文档时,我决定简单地删除旧记录并添加来自远程服务器的更新版本。结果发现,由于sqlalchemy的编写方式,提交时操作顺序不能得到保证。这导致了添加了重复的版本(在尝试删除旧版本之前),从而导致数据库无法满足唯一约束条件。为了解决这个问题,我使用了flush()
以确保顺序正确,但如果后来同步过程失败,我仍然可以撤消这些操作。object1
, 然后添加object2
,那么object1
会在object2
之前被添加到数据库吗?flush()
的一个用途是提供顺序保证(我认为),同时仍然允许您进行“撤消”选项,这是提交操作所不提供的。自动刷新和自动提交
注意,可以使用自动刷新来确保查询作用于已更新的数据库,因为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://dev59.com/22Up5IYBdhLWcg3wdnd6#15305650
换句话说,提交应该会减少内存使用,尽管在这里可能存在内存和性能之间的权衡。换句话说,您可能不想每次进行单个数据库更改时都提交一次(因为性能原因),但是等待时间过长会增加内存使用。
除非您了解什么是数据库 事务,否则现有的答案并没有太多意义。(直到最近我自己也是如此。)
有时,您可能希望运行多个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()
之前,UPDATE语句没有对数据库产生影响。
在某些情况下,您可能希望在发出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
简单定位:
commit
执行实际更改(它们在数据库中变得可见)flush
进行虚拟更改(它们仅对您可见)想象一下,数据库的工作方式就像git分支:
transaction
过程中,您并不是在操作真实的数据库数据。branch
,然后在其中进行操作。commit
命令,那么意味着:“merge
我的数据更改到主数据库数据中”。commit
后才能获取(例如插入到表中,并且您需要插入的 PKID
),那么您可以使用 flush
命令,表示:““计算出未来的 PKID
,并为我保留它”。PKID
值,并确保真实数据符合预期。Commit
必须始终放在最后,以合并到主数据库数据中。__exit__
被调用时发生,就在commit之前。但你也可以手动强制执行。>>> from main import Session, User
>>> session = Session()
>>> usr1 = User(name="John") # No SQL happen here
>>> usr1.name = "Kevin" # Also no SQL happen here
>>> usr1.id is None # ID is DB-side autoincrement, so it undefined
True
>>> session.add(usr1) # Probably you think that SQL INSERT sent, but no!
# User object just "attached" to session
>>> usr1.name = "Robert" # Manipulate over ORM object, but no single SQL sent
>>> session.flush() # Forcing sqlalchemy sync session state to DB
# First real SQL issued only there
INFO sqlalchemy.engine.Engine select pg_catalog.version()
INFO sqlalchemy.engine.Engine [raw sql] {}
INFO sqlalchemy.engine.Engine select current_schema()
INFO sqlalchemy.engine.Engine [raw sql] {}
INFO sqlalchemy.engine.Engine show standard_conforming_strings
INFO sqlalchemy.engine.Engine [raw sql] {}
INFO sqlalchemy.engine.Engine BEGIN (implicit)
INFO sqlalchemy.engine.Engine INSERT INTO users (name) VALUES (%(name)s) RETURNING users.id
INFO sqlalchemy.engine.Engine [generated in 0.00009s] {'name': 'Robert'}
>>> session.commit() # It sent COMMIT to DB
INFO sqlalchemy.engine.Engine COMMIT
# ----- Let's play other way -----
>>> usr2 = User(name="other user") # No SQL
>>> session.add(usr2) # Still no SQL
>>> session\
.query(User)\
.filter_by(name="other user")\
.all() # Sqlalchemy flushing state before do SELECT
INFO sqlalchemy.engine.Engine INSERT INTO users (name) VALUES (%(name)s) RETURNING users.id
INFO sqlalchemy.engine.Engine [cached since 40.72s ago] {'name': 'other user'}
INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name
FROM users
WHERE users.name = %(name_1)s
INFO sqlalchemy.engine.Engine [generated in 0.00016s] {'name_1': 'other user'}
[User(id=6, name=other user)]
>>> session.flush() # So "flush" do nothing - session state already flushed
>>> session.commit() # Commit sent COMMIT, as expected
INFO sqlalchemy.engine.Engine COMMIT
>>> session.close()
commit()将这些更改记录到数据库中。flush()始终作为commit()(1)调用的一部分调用。当您使用Session对象查询数据库时,查询将返回从数据库和正在执行的未记录事务的红色部分中的结果。