我正在使用一种模式在 Redis 中缓存 SQLAlchemy 对象。每当修改和提交实例时,我想清除相关的缓存,以便下次获取时重新加载它。这种清除必须在提交后进行,以避免竞争条件(另一个线程查询缓存、丢失并将过时的数据重新加载到缓存中)。
我已经与这个问题斗争了很长时间,提出了各种有时有效的解决方案,但没有什么万无一失的。这似乎是一个足够简单的问题,应该有一个解决方案。每次向 SQLAlchemy 实例提交更改时,如何触发一些代码?
我尝试将一些 SQLAlchemy 事件拼接在一起以实现我的目标,并取得了不同程度的成功。监听“after_insert”和“after_update”将告诉我何时修改对象,“after_commit”告诉我修改的内容已保存,因此我有一个方案,其中前两个事件将为“after_commit”注册侦听器,其中依次将对象传递给我的缓存清除函数。像这样:
def _register_after_commit(_: Mapper, __: Connection, target: MyClass) -> None:
""" Generic callback that adds this function for a target change without params """
targets.add(target) # Clear cache uses this set to know which instances to clear
event.listen(get_session(), "after_commit", clear_cache)
event.listen(MyClass, "after_insert", _register_after_commit)
event.listen(MyClass, "after_update", _register_after_commit)
Run Code Online (Sandbox Code Playgroud)
这在大多数情况下都有效,但DetachedInstanceError在访问目标上的属性时,我偶尔会得到我需要知道的属性以从缓存中清除它们(例如id)。我读到,发生这种情况是因为提交期间自动过期,这导致 SQLAlchemy 想要刷新所有属性。我无法关闭自动过期功能,也无法删除通过此处的每个对象,其中任何一个都可能最终破坏代码库的其他部分。
我创建了自己的会话类,如下所示:
class SessionWithCallback(scoped_session):
""" A version of orm.Session which can call a method after commit completes """
def __init__(self, session_factory, scopefunc = None) -> None:
super().__init__(session_factory=session_factory, scopefunc=scopefunc)
self._callbacks = {}
def add_callback(self, func, *args, **kwargs) -> None:
"""
Adds a callback to be called after commit, ensuring only a single
instance of the callback for each set of args and kwargs is used
"""
key = f"{func}.{args}.{kwargs}"
self._callbacks[key] = (func, args, kwargs)
def run_callbacks(self) -> None:
"""
Executes all callbacks
"""
for (func, args, kwargs) in self._callbacks.values():
func(*args, **kwargs)
self._callbacks = {}
def commit(self) -> None:
""" Flush and commit the current transaction """
super().commit()
self.run_callbacks()
Run Code Online (Sandbox Code Playgroud)
然后,_register_after_commit它将调用当前会话的函数,而不是使用“after_commit”事件add_callback。当仅使用 SQLAlchemy 运行测试时,这似乎有效,但当与使用这些模型并利用 Flask-SQLAlchemy 的 Flask 应用程序集成时,它就崩溃了。我按照说明自定义会话(覆盖create_sessionSQLAlchemy 实例),但是一旦我提交任何内容,我就会收到一个scoped_session没有 attribute 的异常add_callback。我逐步执行,它以某种方式在内部使用我的类,但它给我的会话不是我的类的实例。令人困惑。
add_callback函数。不过,我仍然需要使这个会话特定且线程安全。在 Flask 中很简单,但 Flask 并不是唯一需要共享此代码的应用程序。| 归档时间: |
|
| 查看次数: |
1420 次 |
| 最近记录: |