【问题标题】:How to implement "relationship" caching system in a similar query?如何在类似查询中实现“关系”缓存系统?
【发布时间】:2021-11-28 08:31:31
【问题描述】:

我注意到当有这样的模型时:

class User(Model):
    id = ...
    books = relationship('Book')

第一次调用user.books 时,SQLAlchemy 会查询数据库(例如当lazy='select' 时,这是默认设置),但随后调用user.books 时不会调用数据库。结果似乎已被缓存。

在使用查询方法时,我希望拥有与 SQLAlchemy 相同的功能,例如:

class User:
    def get_books(self):
        return Book.query.filter(Book.user_id == self.id).all()

但是在这样做时,如果我调用 3 次 get_books(),SQLAlchemy 会调用数据库 3 次(将 ECHO 属性设置为 True 时)。

如何更改 get_books() 以使用 SQLAlchemy 的缓存系统?

我坚持提到“来自 SQLAlchemy”,因为我相信他们处理刷新/删除/刷新系统,然后如果发生其中一种情况,则将更改重新查询到数据库。相反,如果我要在模型中简单地创建一个缓存属性,那么:

def get_books(self):
    if self._books is None:
        self._books = Book.query.filter(Book.user_id == self.id).all()
    return self._books

这不适用于 SQLAlchemy 中的刷新/刷新/清除。

那么,如何更改 get_books() 以使用 SQLAlchemy 中的缓存系统?

【问题讨论】:

    标签: python-3.x sqlalchemy


    【解决方案1】:

    编辑 1

    我意识到下提供的解决方案并不完美,因为它为当前对象缓存。如果您有同一个用户的两个实例,并在这两个实例上调用 get_books,则会进行两个查询,因为缓存仅适用于实例,而不是全局应用,这与 SQLAlchemy 相反。

    原因很简单 - 我相信 - 但仍不清楚如何在我的情况下应用它:对象是在类级别定义的,而不是实例 (books = relationship()),他们在内部构建自己的查询,所以他们可以根据查询缓存。

    在我给出的解决方案中,memoize_getter 不知道所做的查询,因此,无法将其缓存为多个实例的相同值,因此对另一个实例进行的任何相同调用都会查询数据库。


    原答案:

    我一直在尝试理解 SQLAlchemy 的代码(哇,这太密集了!),我想我明白了!

    至少在设置为“lazy='select'”(默认)时,关系是 InstrumentedAttribute,其中包含执行以下操作的 get 函数:

    def __get__(self, instance, owner):
        if instance is None:
            return self
    
        dict_ = instance_dict(instance)
        if self._supports_population and self.key in dict_:
            return dict_[self.key]
        else:
            try:
                state = instance_state(instance)
            except AttributeError as err:
                util.raise_(
                    orm_exc.UnmappedInstanceError(instance),
                    replace_context=err,
                )
            return self.impl.get(state, dict_)
    

    因此,尊重 SQLAlchemy 的基本缓存系统将类似于:

    from sqlalchemy.orm.base import instance_dict
    
    def get_books(self):
        dict_ = instance_dict(self)
        if 'books' not in dict_:
            dict_['books'] = Book.query.filter(Book.user_id == self.id).all()
    
        return dict_['books']
    

    现在,我们可以把恶习推得更远一点,做一个……装饰器(哦,甜心):

    def memoize_getter(f):
        @functools.wraps(f)
        def decorator(instance, *args, **kwargs):
            property_name = f.__name__.replace('get_', '')
            dict_ = instance_dict(instance)
            if property_name not in dict_:
                dict_[property_name] = f(instance, *args, **kwargs)
    
            return dict_[property_name]
    
        return decorator
    

    因此将原始方法转换为:

    class User:
        @memoize_getter
        def get_books(self):
            return Book.query.filter(Book.user_id == self.id).all()
    

    如果有人有更好的解决方案,我非常感兴趣!

    【讨论】:

      猜你喜欢
      • 2019-05-10
      • 1970-01-01
      • 1970-01-01
      • 2013-10-04
      • 1970-01-01
      • 1970-01-01
      • 2014-06-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多