如何防止SQLAlchemy对过期对象重新运行查询?
我在处理Flask请求中的过期SQLAlchemy对象时遇到了一些困惑。假设我做了以下操作:
from models import Foo, Bar
@app.route("/page")
def page():
foos = Foo.query.all()
for foo in foos:
b = Bar(foo.data)
db.session.add(b)
db.session.commit()
return render_template('page.html', foos=foos)
然后在page.html
中:
{% for foo in foos %}
{{ foo. name }}
{% endfor %}
因为session.commit()
把foos
这个集合标记为过期,所以SQLAlchemy会在模板循环中对每个foo执行一次查询。如果我知道foos
实际上没有发生变化,有什么方法可以防止执行len(foos)
的查询呢?同样,如果foos
确实发生了变化,有什么方法可以用一次查询来刷新数据,而不是执行多次查询?
2 个回答
我有一种稍微不同的方法,不过我不推荐在99%的情况下使用。(但我还是分享一下)
我对通过SqlAlchemy获取的数据进行了激进的缓存。为了让实时的SqlAlchemy对象和缓存的数据看起来差不多,我做了以下几步(参考 https://gist.github.com/jvanasco/01af92e100769d52f7b8)
当数据进入缓存时,我把它转成一个原始的字典(也就是说,去掉所有SqlAlchemy的信息。我只想要表格里的信息)
当我从缓存中提取数据时,我把它转成一个“对象化字典”。这个字典可以通过点号来访问属性——就像SqlAlchemy对象一样。
从缓存中提取数据的过程也可以指定一些属性作为延迟加载的函数(这是在我从缓存中提取东西时写的)。这样,我可以把“用户账户”对象的照片属性作为一个函数,来从缓存中提取特定的照片。
通过这种方法,我的只读部分使用和可写部分相同的模板——唯一的区别是,如果你查看视图,一个部分的对象是字典的版本,而另一个部分则是真正的SqlAlchemy对象。
我不推荐在99%的情况下使用这种方法。但在那1%的情况下,当你想要持久化缓存数据时,我发现这是最好的解决方案。
如果你确定没有任何foos被更新,那为什么还要执行db.session.commit()
呢?如果有时候需要,那就加一些逻辑,只在有更新的时候才触发提交。
你可以在db.session.commit()
下面加一句foos = Foo.query.all()
。这样就只会发出一次查询来获取所有数据,而不是每一行都发一次。
正如你所说,提交数据会让它们的状态变为过期,所以需要重新查询。也许你可以刷新会话,而不是重新查询,更多信息可以参考SQLAlchemy文档,里面提到你可以使用session.refresh(object)
。
更新:使用两个会话
你可以使用第二个会话,先用它来查询Foo
,然后用另一个会话来处理Bars
。这样在提交时foos就不会被修改,你就不需要再去查询它了。
这里有个粗略的例子:
from flask.ext.sqlalchemy import Session
@app.route('/example/')
def home():
session_two = Session(bind=db.engine.connect())
foos = session_two.query(Foo).all()
for foo in foos:
db.session.add(Bar(foo))
db.session.commit()
return render_template_string('''
{% for foo in foos %}
{{ foo.name }}
{% endfor %}
''', foos=foos)
另外,我在想是否可以用一个会话来处理,配置为expire_on_commit=False
,具体可以参考文档:
“commit()的另一个行为是,默认情况下,它会在提交完成后使所有实例的状态过期。这是为了确保当下次访问这些实例时,无论是通过属性访问还是在查询结果集中,它们都能接收到最新的状态。要禁用这种行为,可以将sessionmaker配置为expire_on_commit=False。”
使用Session.expunge
根据需要将对象从会话中移除。
@app.route('/')
def home():
foos = Foo.query.all()
for foo in foos:
db.session.add(Bar(foo))
db.session.expunge(foo)
db.session.commit()
return render_template_string('''
{% for foo in foos %}
{{ foo.name }}
{% endfor %}
''', foos=foos)