【问题标题】:SqlAlchemy cache with dogpile带有 dogpile 的 SqlAlchemy 缓存
【发布时间】:2014-04-24 22:18:43
【问题描述】:

我有三个具有继承和关系的模型,我想缓存对这些模型的查询。

class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    type = Column(String(50))
    __mapper_args__ = {
        'polymorphic_identity': 'object',
        'polymorphic_on': type
        }

class Man(Person):
    __tablename__ = 'man'
    id = Column(Integer, ForeignKey('person.id'), primary_key=True)
    age = Column(String(100), nullable=False)
    __mapper_args__ = {'polymorphic_identity': 'man'}

class Config(Base):
    __tablename__ = "config"
    id = Column(Integer, primary_key=True)
    person = Column(Integer, ForeignKey('person.id'))
    address = Column(String)
    person_ref = relationship(Person)

还有很多其他模型继承自Personal。 例如,我需要通过 Config 关系访问 Man 属性。 通常我会这样做:

config = session.query(Config).join(Config.person_ref).filter(Person.type == 'man').first()
print config.person_ref.age

如何使用 dogpile 缓存这样的查询? 我可以缓存查询到Config,但我不能缓存查询到Man的属性,每次都发出SQL。 我尝试使用 with_polymorphic,但它只能在没有连接负载的情况下使用。 (不明白为什么)

config = session.query(Config).options(FromCache("default")).first()
people = session.query(Person).options(FromCache("default")).with_polymorphic('*').get(config.person)

但我需要 joinload 来过滤类型。

【问题讨论】:

  • 定义“不能”。堆栈跟踪?没有结果?每次都发出SQL?不清楚。
  • 每次都发出 SQL。
  • 不确定,您需要查看调用查询时生成的缓存键。使用 pdb.set_trace() 逐步了解正在发生的事情。缓存并不简单,这就是为什么这不是内置功能的原因;通过将其作为食谱来鼓励用户逐步完成它。
  • @zzzeek 你认为它应该如何工作?当我使用 joined 时,它只加入 Person 表,当我尝试访问 Man 属性时,SQL 发出,因为继承对象的属性延迟加载.
  • 如果你想缓存延迟加载的属性,你有两个选择:1.使用joinedload()或2.使用在dogpile示例中也说明的RelationshipCache特性。

标签: python sqlalchemy dogpile.cache


【解决方案1】:

为了确保加载“man”表,of_type() 可用于任何子类型模式。我们可以改为使用 with_polymorphic() 连接到一个完整的多态可选。有关详细信息,请参阅http://docs.sqlalchemy.org/en/latest/orm/inheritance.html#creating-joins-to-specific-subtypes 的示例。只要您想要的数据出现在一个 SELECT 查询中,那么该数据就会在通过 FromCache 缓存的内容中。确实,缓存配方目前不包含一个系统,通过该系统可以在事后缓存附加的连接继承属性的延迟加载。

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from examples.dogpile_caching.caching_query import query_callable, FromCache, RelationshipCache
from hashlib import md5
from dogpile.cache.region import make_region

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    type = Column(String(50))
    __mapper_args__ = {
        'polymorphic_identity': 'object',
        'polymorphic_on': type
        }

class Man(Person):
    __tablename__ = 'man'
    id = Column(Integer, ForeignKey('person.id'), primary_key=True)
    age = Column(String(100), nullable=False)
    __mapper_args__ = {'polymorphic_identity': 'man'}

class SomethingElse(Person):
    __tablename__ = 'somethingelse'

    id = Column(Integer, ForeignKey('person.id'), primary_key=True)
    age = Column(String(100), nullable=False)
    __mapper_args__ = {'polymorphic_identity': 'somethingelse'}

class Config(Base):
    __tablename__ = "config"
    id = Column(Integer, primary_key=True)
    person = Column(Integer, ForeignKey('person.id'))
    address = Column(String)
    person_ref = relationship(Person)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

def md5_key_mangler(key):
    """Receive cache keys as long concatenated strings;
    distill them into an md5 hash.

    """
    return md5(key.encode('ascii')).hexdigest()

regions = {}
regions['default'] = make_region(
            key_mangler=md5_key_mangler
            ).configure(
                'dogpile.cache.memory_pickle',
            )

Session = scoped_session(
                sessionmaker(
                    bind=e,
                    query_cls=query_callable(regions)
                )
            )

sess = Session()
sess.add(Config(person_ref=SomethingElse(age='45', name='se1')))
sess.add(Config(person_ref=Man(age='30', name='man1')))
sess.commit()

all_types = with_polymorphic(Person, "*", aliased=True)

conf = sess.query(Config).options(joinedload(Config.person_ref.of_type(all_types)), FromCache("default")).first()
sess.commit()
sess.close()

print "_____NO MORE SQL!___________"


conf = sess.query(Config).options(joinedload(Config.person_ref.of_type(all_types)), FromCache("default")).first()
print conf.person_ref.age

【讨论】:

  • 因为我不明白我无法像 man = session.query(Config).options(joinedload(Config.person_ref)).filter(Person.type == 'man').first() 这样缓存查询。正如我所说的,我有很多从 Person 继承的对象,我必须做with_polymorphic('*'),我认为这不好。
  • 该查询没有意义。你只查询 Config,而不是 Person,所以你不能像那样过滤 Person.type - 它会产生一个笛卡尔积。 joinload() 是查询的一个单独方面,请阅读docs.sqlalchemy.org/en/latest/orm/…。上面的示例和文档说明了将joinedload对象限制为特定类型的正确方法。
  • 对不起,我的错。查询是session.query(Config).join(Config.person_ref).options(joinedload(Config.person_ref)).filter(Person.type == 'man').first(),我可以使用 FromCache 对其进行缓存,但是如果我访问 Man 的属性,则会发出新的 SQL。在实际应用中,我没有按 type 过滤事件,而是按一些 Config 属性过滤并打印出 person_ref 的所有属性(我没有不知道它的类型)。我看到的唯一解决方案是使用 with_polymorphic('*')。你能告诉我哪里错了吗?
  • 是的,它的工作原理,但如果你像我一样在sess.close() 之前执行sess.commit(),sql SELECT somethingelse.id AS somethingelse_id, person.id AS person_id, person.name AS person_name, person.type AS person_type, somethingelse.age AS somethingelse_age FROM person JOIN somethingelse ON person.id = somethingelse.id WHERE person.id = ? 2014-04-24 11:03:23,975 INFO sqlalchemy.engine.base.Engine (1,) 会发出。我还必须删除 CachingQuery.__iter__ 中的所有对象,如方法文档中所述。
  • 这是缓存后端的产物,因为它不会生成对象的副本,因此第二个查询返回包含过期属性的相同副本。使用 memory_pickle 后端更新示例。
猜你喜欢
  • 2014-12-31
  • 2015-02-06
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
  • 2012-04-29
  • 2015-03-10
  • 1970-01-01
相关资源
最近更新 更多