在sqlalchemy中完成对象及其关系并避免不必要的查询

2024-10-01 11:41:20 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一些数据库结构;因为大部分与我们无关,所以我只描述一些相关的部分。以lake Item对象为例:

items_table = Table("invtypes", gdata_meta,
                    Column("typeID", Integer, primary_key = True),
                    Column("typeName", String, index=True),
                    Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
                    Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True))

mapper(Item, items_table,
       properties = {"group" : relation(Group, backref = "items"),
                     "_Item__attributes" : relation(Attribute, collection_class = attribute_mapped_collection('name')),
                     "effects" : relation(Effect, collection_class = attribute_mapped_collection('name')),
                     "metaGroup" : relation(MetaType,
                                            primaryjoin = metatypes_table.c.typeID == items_table.c.typeID,
                                            uselist = False),
                     "ID" : synonym("typeID"),
                     "name" : synonym("typeName")})

我想在sqlalchemy/database层实现一些性能改进,并有一些想法: 1) 两次请求同一物品:

^{pr2}$

每个请求生成并发出SQL查询。为了避免这种情况,我对一个item对象使用了两个自定义映射:

itemMapId = {}
itemMapName = {}

@cachedQuery(1, "lookfor")
def getItem(lookfor, eager=None):
    if isinstance(lookfor, (int, float)):
        id = int(lookfor)
        if eager is None and id in itemMapId:
            item = itemMapId[id]
        else:
            item = session.query(Item).options(*processEager(eager)).get(id)
            itemMapId[item.ID] = item
            itemMapName[item.name] = item
    elif isinstance(lookfor, basestring):
        if eager is None and lookfor in itemMapName:
            item = itemMapName[lookfor]
        else:
            # Items have unique names, so we can fetch just first result w/o ensuring its uniqueness
            item = session.query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
            itemMapId[item.ID] = item
            itemMapName[item.name] = item
    return item

我相信sqlalchemy也做类似的对象跟踪,至少通过主键(项目编号). 若确实如此,我可以擦除这两个映射(尽管擦除名称映射将需要对使用这些查询的应用程序进行小的修改),以避免重复功能和使用常用方法。实际的问题是:如果sqlalchemy中有这样的功能,如何访问它?在

2)快速加载关系通常有助于将大量请求保存到数据库。例如,我肯定需要以下一组item=item()属性:

item.group (Group object, according to groupID of our item)
item.group.items (fetch all items from items list of our group)
item.group.items.metaGroup (metaGroup object/relation for every item in the list)

如果我有一些条目ID,但是还没有加载条目,我可以从数据库中请求它,急切地加载我需要的一切:sqlalchemy将在单个查询中联接group、它的条目和相应的元组。如果我使用默认的延迟加载访问它们,sqlalchemy将需要发出1个查询来获取item+1以获取列表中所有项的group+1*#items+1*#items以获取每个项的元组,这是浪费。在

2.1)但是如果我已经获取了Item对象,并且我想要加载的一些属性已经加载了呢?据我所知,当我从数据库中重新获取某个对象时,它已经加载的关系不会被卸载,对吗?在

2.2)如果我已经获取了Item对象,并且想访问它的组,我可以使用项目组ID,应用我需要的任何急切的陈述(“项目”和“项目.元组"). 它应该正确地加载组和它所请求的关系,而不需要接触项目内容。sqlalchemy会正确地将这个获取的组映射到项目组,这样当我访问项目组它不会从底层数据库中获取任何内容?在

2.3)如果我从数据库中提取了以下内容:原始项,项目组还有一部分item.group.items项列出其中一些可能已经加载了元组,完成数据结构的最佳策略与上面的eager list相同:使用(“items”重新获取组项目.元组“)紧急加载,或者分别检查项目列表中的每个项目,如果项目或其元组未加载-加载它们?这似乎取决于具体情况,因为如果所有的东西都在一段时间前就已经加载了,那么发出如此繁重的查询是毫无意义的。sqlalchemy是否提供了一种跟踪某个对象关系是否已加载的方法,并且能够深入查看一个级别吗?在

作为2.3的一个例子-我可以获取ID为83的组,急切地获取“items”和项目.元组". 有没有办法从一个条目(groupID为83)中确定它是否有“group”组.项“和”group.items.metaGroup组“是否加载,使用sqlalchemy工具(在本例中,应该加载所有工具)?在


Tags: 项目对象nameid数据库sqlalchemygroupitems
2条回答

要强制加载lazy属性,只需访问它们。这是最简单的方法,它对关系很好,但是对于Columns(您将为同一表中的每一列获得单独的SQL查询)效率低下。您可以从sqlalchemy.orm.attributes.instance_state(obj).unloaded获取所有已卸载属性(关系和列)的列表。在

示例中没有使用延迟列,但为了完整起见,我将在这里描述它们。处理延迟列的典型方案如下:

  • deferred()装饰选定的列。通过使用group参数将它们组合成一个或多个组。在
  • 如果需要,请在查询中使用undefer()undefer_group()选项。在
  • 访问延迟列放入组将加载此组中的所有列。在

不幸的是,这并不起相反的作用:您可以将列组合成组,而不必使用column_property(Column(…), group=…)来延迟加载它们,但是defer()选项不会影响它们(它只适用于Columns,而不是列属性,至少在0.6.7中是这样)。在

要强制加载延迟列属性,session.refresh(obj, attribute_names=…)由nathanvilaescusa建议的可能是最好的解决方案。我看到的唯一缺点是它首先使属性过期,因此必须确保作为attribute_names参数传递的属性中没有加载的属性(例如,使用与state.unloaded的交集)。在

更新

1)SQLAlchemy会跟踪加载的对象。ORM就是这样工作的:对于每个标识,会话中必须只有一个对象。默认情况下,它的内部缓存是弱的(使用weak_identity_map=False来更改它),因此只要代码中没有对该对象的引用,就会从缓存中删除该对象。当对象已经在会话中时,SQLAlchemy不会对query.get(pk)执行SQL请求。但这只适用于get()方法,因此query.filter_by(id=pk).first()将在会话中使用加载的数据执行SQL请求和刷新对象。在

2)急切地加载关系将导致更少的请求,但并不总是更快。你必须检查你的数据库和数据。在

2.1)从数据库重取数据不会卸载通过关系绑定的对象。在

2.2)item.group是使用query.get()方法加载的,因此如果对象已经在会话中,则不会导致SQL请求。在

2.3)是的,视情况而定。在大多数情况下,最好是希望SQLAlchemy使用正确的策略:)。对于已经加载的关系,您可以检查相关对象的关系是否通过state.unloaded以递归方式加载到任何深度。但是,当关系尚未加载时,您无法知道相关对象及其关系是否已加载:即使关系尚未加载,相关对象也可能已在会话中(想象一下,您先请求项,加载其组,然后请求具有相同组的其他项)。对于您的特定示例,我认为递归地检查state.unloaded没有问题。在

(一) 从Session documentation

[The Session] is somewhat used as a cache, in that it implements the identity map pattern, and stores objects keyed to their primary key. However, it doesn’t do any kind of query caching. ... It’s only when you say query.get({some primary key}) that the Session doesn’t have to issue a query.

2.1)您是正确的,刷新对象时不会修改关系。在

2.2)是的,小组将出现在身份图中。在

2.3)我相信你最好的选择是重新装载整个组.项在单个查询中。根据我的经验,发出一个大的请求通常比几个较小的请求快得多。唯一有意义的是只重新加载特定的组.项是否确实有一个需要装载。虽然在这种情况下,您只执行一个大的查询而不是一个小的查询,所以您实际上并没有减少查询的数量。在

我没有尝试过,但是我相信您应该能够使用sqlalchemy.orm.util.identity_key方法来确定对象是否在sqlalchemy的identiy映射中。我想知道调用identiy\u key(Group,83)返回什么。在

初始问题) 如果我理解正确的话,你有一个从数据库中获取的对象,它的一些关系是预先加载的,你想用一个查询来获取其余的关系?我相信您可以使用Session.refresh()方法传递要加载的关系的名称。在

相关问题 更多 >