如何禁用SQLAlchemy缓存?

29

在使用 sqlalchemy 时,我遇到了缓存问题。

我使用 sqlalchemy 将数据插入 MySQL 数据库。然后,我有另一个应用程序处理这些数据,并对其进行直接更新。

但是,sqlalchemy 总是返回旧的数据而不是更新后的数据。我认为 sqlalchemy 缓存了我的请求,因此我该如何禁用它呢?


1
相关的吗?https://dev59.com/_nLYa4cB1Zd3GeqPUi5U - Prof. Falken
8个回答

56
除了通常在事务中本地的SQLAlchemy Identity Map之外,人们认为有一种“缓存”存在的原因是,他们观察到了事务隔离的影响。SQLAlchemy的session默认以事务模式工作,这意味着它会等待调用session.commit()方法才将数据持久化到数据库中。在此期间,在其他地方正在进行的事务中,将无法看到此数据。
然而,由于事务的隔离性质,还有一个额外的细节。其他正在进行的事务不仅不能看到您的事务数据,直到提交之前,有些情况下甚至在它们自己提交或回滚之前也无法看到(这与您在此处使用close()产生的效果相同)。具有平均隔离程度的事务将保留其已加载的状态,并将该状态保持在本地事务中,即使实际数据已更改,也将继续提供给您-在事务隔离术语中称为可重复读取。
参考链接:http://en.wikipedia.org/wiki/Isolation_%28database_systems%29

5
SQLAlchemy的会话默认以事务方式工作---你能向我们展示一种停止默认设置的方法吗?我不需要解释,只需要一行代码来完全禁用事务。特别是对于愚蠢的SELECT调用。 以下是翻译后的内容:SQLAlchemy的会话默认以事务模式工作 --- 你能否提供一种停用默认设置的方法?请给出一行代码以完全禁用事务。特别是对于愚蠢的SELECT调用,无需解释。 - est
3
实际上,SQLAlchemy 中有缓存机制(至少在2021年是这样的)。 我在使用“session.execute”命令时遇到了缓存问题。 你可以在这里找到有关缓存的信息(在页面上搜索“cached since”字符串)https://github.com/sqlalchemy/sqlalchemy/blob/master/doc/build/core/connections.rst - Anar Salimkhanov
2
@AnarSalimkhanov 请注意,您所提到的缓存仅是一个语句编译缓存。从您链接的文档中可以看出:它 “仅缓存传递给数据库的SQL字符串,而不缓存查询返回的数据。它绝不是一个数据缓存,也不会影响特定SQL语句返回的结果,也不意味着与获取结果行相关联的任何内存使用。” - amain
@amain 嗯...有趣。因为我曾经遇到过缓存问题。尽管数据库已更新,但我仍然会收到旧的响应数据,直到我禁用它。现在我无法测试它,因为它是在我的旧项目中,而我不记得它在哪里了) - Anar Salimkhanov

25

这个问题一直让我很沮丧,但我终于解决了。

我有一个运行在旧 PHP 站点旁边的 Flask/SQLAlchemy 应用程序。PHP 站点会向数据库写入数据,而 SQLAlchemy 却不知道任何更改。

我尝试设置 sessionmaker 的 autoflush=True,但没有成功。 我尝试在查询之前使用 db_session.flush()、db_session.expire_all() 和 db_session.commit(),但都无效。仍然显示旧数据。

最后我找到了 SQLAlchemy 文档中的这一部分:http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#transaction-isolation-level

设置 isolation_level 很有效。现在我的 Flask 应用程序正在与 PHP 应用程序“通信”。以下是代码:

engine = create_engine(
    "postgresql+pg8000://scott:tiger@localhost/test",
    isolation_level="READ UNCOMMITTED"
)

当SQLAlchemy引擎使用"READ UNCOMMITTED"隔离级别启动时,它将执行"脏读取"操作,这意味着它将直接从数据库读取未提交的更改。

希望这可以帮助。


以下是评论区AaronD提供的可能解决方案:

from flask.ext.sqlalchemy import SQLAlchemy

class UnlockedAlchemy(SQLAlchemy):
    def apply_driver_hacks(self, app, info, options):
        if "isolation_level" not in options:
            options["isolation_level"] = "READ COMMITTED"
    return super(UnlockedAlchemy, self).apply_driver_hacks(app, info, options)

1
如果您正在使用Flask-SQLAlchemy,可以子类化flask.ext.sqlalchemy.SQLAlchemy并覆盖apply_driver_hacks函数以设置隔离级别,同时仍保留所有Flask集成。此外,也许隔离级别READ COMMITTED就足够了,只要两个应用程序在进行写入后立即提交而不是等待很长时间即可。这样,您就不必担心脏读问题-每次读取时它都会为您提供新的DB快照。 - Aaron D
@AaronD,你能否发布一下你提到的子类化flask.ext.sqlalchemy.SQLAlchemy的代码? - Nick Woodhams
2
我在我的代码中有这个:class UnlockedAlchemy(SQLAlchemy): def apply_driver_hacks(self, app, info, options): if not "isolation_level" in options: options["isolation_level"] = "READ COMMITTED" return super(UnlockedAlchemy, self).apply_driver_hacks(app, info, options) - Aaron D
1
救命稻草!我正在使用 engine_from_config 从文件中读取 sqlalchemy 配置,我只需在配置文件中添加:sqlalchemy.isolation_level = READ UNCOMMITTED,现在外部更改已正确地反映在我的应用程序中 :-) - ozbob
2
这没有意义。如果更新数据库的事务已经被 PHP 网站正确提交,为什么需要将隔离级别设置为“READ UNCOMMITTED”?这更像是你的 PHP 网站如何更新数据库的问题。 - Alan
显示剩余2条评论

4

除了 zzzeek 的出色回答之外,

我也遇到了类似的问题。通过使用短生命周期的会话,我解决了这个问题。

with closing(new_session()) as sess:
    # do your stuff

对于每个任务、任务组或请求(在 Web 应用程序的情况下)我使用一个新的会话。这解决了我的“缓存”问题。

这个材料对我来说非常有用:

何时构建会话,何时提交它,何时关闭它


1
上面的链接指向会话文档。标题意味着它应该指向这里:http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html#session-faq-whentocreate - mozey
http://docs.sqlalchemy.org/en/latest/orm/session_state_management.html#when-to-expire-or-refresh - phyatt

3
在我的Flask应用程序中出现了这种情况,我的解决方法是在每次请求后使会话中的所有对象过期。
from flask.signals import request_finished
def expire_session(sender, response, **extra):
    app.db.session.expire_all()
request_finished.connect(expire_session, flask_app)

非常顺利地完成了。


2

我尝试过使用session.commit(), session.flush(),但都没有成功。

在查看了sqlalchemy源代码后,我发现了禁用缓存的解决方法。
create_engine中设置query_cache_size=0即可。

create_engine(connection_string, convert_unicode=True, echo=True, query_cache_size=0)

值得注意的是,问题和其他答案讨论了表面上的“数据缓存”,即检索到的数据与数据库中最新数据不匹配。 query_cache_size 控制 SQLAlchemy 的最近生成的 SQL 查询字符串的缓存大小。它对查询结果没有影响,除了可能使它们变慢。当然,它会影响内存使用。 - snakecharmerb
1
实际上,尽管上面的对话很明智,但这是对问题标题的直接回答。并且源代码的注释说:“将其设置为零以禁用缓存”。
而且它确实有效:
禁用缓存的实验
默认缓存的实验
- Dmitry
@Dmitry 不,那个设置只是禁用了“语句”缓存,与“结果”的缓存无关。 - snakecharmerb

-1

import sqlalchemy as db
db.create_engine(db_string, query_cache_size=0, pool_size=30, max_overflow=0, pool_pre_ping=True, echo=True, echo_pool=True)

我们可以在SQLAlchemy版本大于1.3.13的情况下解决这个问题,在此版本之下,它不支持禁用语句缓存。

-1

首先,SQLAlchemy 没有缓存机制。 根据你从数据库获取数据的方法,在其他人更新数据库后,应该进行一些测试,看看是否可以获取新数据。

(1) use connection:
connection = engine.connect()
result = connection.execute("select username from users")
for row in result:
    print "username:", row['username']
connection.close()
(2) use Engine ...
(3) use MegaData...

请按照以下步骤操作:http://docs.sqlalchemy.org/en/latest/core/connections.html 另一个可能的原因是您的MySQL数据库没有持续更新。请重新启动MySQL服务并进行检查。

谢谢回复。我已经解决了。当我使用scoped_session时,我只是忘记了session.close... - Zeyi Fan

-4

我认为是这样。我打开了 echo = True 但是没有得到任何有用的东西。 - Zeyi Fan
我使用MySQLdb更新数据,而不使用SQLAlchemy。我确保数据已在MySQL中更新。 - Zeyi Fan
1
尝试在您的sessionmaker中将autocommit设置为True(bind=self.engine,autocommit=True)。 - Voislav Sauca
1
感谢您的回复。我已经解决了这个问题。 我只是在使用 scoped_session 时忘记了 session.close()。有点糊涂。。 - Zeyi Fan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接