我创建了一个类
Result
,它接受 sqlalchemy 语句,存储该语句,执行该语句,并存储执行结果。
如果直接在
__init__()
中执行该语句,下面的脚本将产生预期的结果:
1 Test JE 1
2 Test JE 2
但是如果该语句在
set_result()
中执行,下面的脚本输出
No results.
Using
pdb
显示
_cleanup()
在
set_result()
之后调用:
--Call--
> /usr/lib/python3/dist-packages/sqlalchemy/orm/state.py(420)_cleanup()
为什么
_cleanup()
在
set_result()
之后被调用但不是在
__init__()
?
脚本之后
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import Column, String, Integer, select
Base = declarative_base()
class JournalEntry(Base):
__tablename__ = 'JournalEntry'
pid = Column(Integer, primary_key=True)
desc = Column(String, nullable=False)
def __repr__(self):
return f"{self.pid} {self.desc}"
class db:
engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
Session = sessionmaker(engine)
class Result:
"""Helper for printing query results."""
def __init__(self, stmt):
self.stmt = stmt
with db.Session() as session:
self.result = session.scalars(self.stmt).all()
#self.result = self.set_result()
def set_result(self):
with db.Session() as session:
self.result = session.scalars(self.stmt).all()
breakpoint()
# objects in self.result get cleaned
def __repr__(self):
try:
resultstr = ""
for obj in self.result:
resultstr += f"{obj}\n"
return resultstr
except TypeError:
return "No results."
if __name__ == '__main__':
je1 = JournalEntry(desc="Test JE 1")
je2 = JournalEntry(desc="Test JE 2")
with db.Session.begin() as session:
session.add_all([je1, je2])
gj = Result(select(JournalEntry))
print(gj)
SQLAlchemy 在
set_result()
后调用
_cleanup()
的原因在于你如何在两种情况下管理数据库会话的生命周期,以及 Python 中垃圾回收的工作原理。
让我们来分解一下:
-
init 中的会话: 当你在
__init__
方法中执行查询时,你正在with db.Session() as session:
块内执行查询。这个块会创建一个数据库会话,并将其作为session
。 关键点在于,这个session
对象会在with
块结束后超出作用域,这也会自动关闭会话并清理结果。 然而,由于Result
对象仍然存在,并且其self.result
属性持有对结果对象的引用,因此这些对象在被垃圾回收之前不会被清理。 -
set_result 中的会话: 当你在
set_result
方法中执行查询时,你也在with db.Session() as session:
块内执行查询。 然而,与__init__
不同的是,set_result
方法在with
块结束后不会立即返回。 这意味着,当set_result
返回时,数据库会话仍然处于打开状态,并且结果对象仍然绑定到该会话。 -
垃圾回收和弱引用: SQLAlchemy 使用弱引用来跟踪与会话关联的对象。当一个对象不再被任何强引用引用时,Python 的垃圾回收器可以自由地回收它。由于在
set_result
中,会话在方法返回后仍然处于打开状态,因此结果对象仍然具有一个来自会话的强引用。 当set_result
返回并且没有其他对结果对象的引用时,它们就会成为垃圾回收的目标。 此时,SQLAlchemy 会介入并调用_cleanup()
来断开结果对象与会话的连接,并释放任何资源。
简而言之:
-
在
__init__
中,会话在结果被使用之前关闭,因此结果在Result
对象的生命周期内保持有效。 -
在
set_result
中,会话在方法返回后保持打开状态。当set_result
返回并且没有其他对结果对象的引用时,它们就会成为垃圾回收的目标,从而触发 SQLAlchemy 的_cleanup()
机制。
如何解决:
为了解决这个问题,你可以:
-
在
Result
对象的生命周期内保持会话打开: 这可以通过将db.Session()
实例存储为Result
类的一个属性来实现。但是,这需要你手动管理会话的生命周期,并确保在不再需要Result
对象时关闭会话。 -
在
set_result
返回之前显式地从会话中分离结果: 可以使用session.expunge_all()
方法来实现。这将断开结果对象与会话的连接,并防止它们在set_result
返回后被清理。 -
使用
yield_per
或stream_results
: 对于大型结果集,你可以使用 SQLAlchemy 的yield_per
或stream_results
功能来逐行或逐块迭代结果。这将允许你在处理结果时保持低内存占用,并避免_cleanup()
问题。
希望这能解释清楚!
标签:python,python-3.x,sqlalchemy From: 78790799