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

Dar*_*nix 6 python sqlalchemy eager-loading

我有一些数据库结构; 因为大部分内容与我们无关,我只会描述一些相关内容.以湖的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 /数据库层中实现一些性能改进,并且有几个想法:1)两次请求相同的项目:

item = session.query(Item).get(11184)
item = None (reference to item is lost, object is garbage collected)
item = session.query(Item).get(11184)

每个请求都会生成并发出SQL查询.为了避免它,我使用2个自定义地图作为项目对象:

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会进行类似的对象跟踪,至少通过主键(item.ID).如果是这样,我可以擦除两个地图(虽然擦除名称映射将需要对使用这些查询的应用程序进行少量修改)不重复功能和使用库存方法.实际问题是:如果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将在单个查询中加入组,其项目和相应的元组.如果我使用默认延迟加载来访问它们,sqlalchemy将需要发出1个查询以获取项目+ 1以获取列表中所有项目的组+ 1*#项目+ 1*#项目以获取每个项目的元组,这很浪费.

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

2.2)如果我已经获取了Item对象,并且想要访问它的组,我可以使用item.groupID getGroup,应用我需要的任何急切语句("items"和"items.metaGroup").它应该正确加载组及其请求的关系,而不是触摸项目的东西.sqlalchemy是否正确地将这个获取的组映射到item.group,这样当我访问item.group时它不会从底层数据库中获取任何内容?

2.3)如果我从数据库中获取了以下内容:原始项目,item.group和item.group.items列表中的一些项目,其中一些项目可能已加载metaGroup,那么完成数据结构的最佳策略是什么与上面的热切列表相同:重新获取具有("items","items.metaGroup")组的组,或者单独检查项目列表中的每个项目,如果项目或其元组未加载 - 加载它们?它似乎取决于具体情况,因为如果一切都已经加载了 - 发出如此繁重的查询是毫无意义的.sqlalchemy是否提供了一种方法来跟踪是否加载了某个对象关系,并且能够比仅仅一个级别更深入地查看?

作为2.3的说明 - 我可以获取ID为83的组,急切地获取"items"和"items.metaGroup".有没有办法从一个项目(其groupID为83)确定,是否使用sqlalchemy工具加载了"group","group.items"和"group.items.metaGroup"(在这种情况下全部他们应该加载)?

Den*_*ach 6

要强制加载延迟属性,只需访问它们.这是最简单的方法,它适用于关系,但不如Columns 有效(您将为同一个表中的每个列获得单独的SQL查询).您可以从中获取所有已卸载属性(关系和列)的列表sqlalchemy.orm.attributes.instance_state(obj).unloaded.

您不在示例中使用延迟列,但为了完整起见,我将在此处对其进行描述.处理延迟列的典型方案如下:

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

不幸的是,这不起作用:您可以将列组合成组,而不会默认加载它们column_property(Column(…), group=…),但defer()选项不会影响它们(它Column仅适用于s,而不适用于列属性,至少在0.6.7中).

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

更新

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

2)急切加载关系会导致请求减少,但并不总是更快.您必须检查数据库和数据.

2.1)从数据库中重新获取数据不会卸载通过关系绑定的对象.

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

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