我有一些数据库结构;因为大部分内容与我们无关,我只会描述一些相关内容。我们以湖项目对象为例:
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列表中的部分项目,其中一些项目可能已加载元组,哪些是最佳策略数据结构与上面的eager list相同:重新获取具有(“items”,“items.metaGroup”)组的组,或者单独检查项目列表中的每个项目,如果项目或其metaGroup未加载 - 加载它们?它似乎取决于具体情况,因为如果一切都已经加载了 - 发出如此繁重的查询是毫无意义的。 sqlalchemy是否提供了一种方法来跟踪是否加载了某个对象关系,并且能够比仅仅一个级别更深入地查看?
作为2.3的说明 - 我可以获取ID为83的组,急切地获取“items”和“items.metaGroup”。有没有办法从一个项目(其groupID为83)确定,是否使用sqlalchemy工具加载了“group”,“group.items”和“group.items.metaGroup”(在这种情况下全部他们应该加载??
答案 0 :(得分:6)
要强制加载延迟属性,只需访问它们即可。这是最简单的方法,它适用于关系,但对于Column
来说效率不高(您将在同一个表中为每个列获得单独的SQL查询)。您可以从sqlalchemy.orm.attributes.instance_state(obj).unloaded
获取所有已卸载属性(关系和列)的列表。
您不在示例中使用延迟列,但为了完整起见,我将在此处对其进行描述。处理延迟列的典型方案如下:
deferred()
装饰选定的列。使用group
参数deferred()
将它们合并为一个或多个组。undefer()
和undefer_group()
选项。不幸的是,这不起作用:您可以将列组合成组而不会默认使用column_property(Column(…), group=…)
延迟加载,但defer()
选项不会影响它们(它适用于{{1只有,而不是列属性,至少在0.6.7中。
强制加载Nathan Villaescusa建议的延迟列属性Column
可能是最好的解决方案。我看到的唯一缺点是它首先使属性失效,因此你必须确保在session.refresh(obj, attribute_names=…)
参数之间没有加载属性(例如通过使用与attribute_names
的交集)。
<强>更新强>
1)SQLAlchemy会跟踪加载的对象。这就是ORM的工作原理:会话中必须有每个身份的唯一对象。默认情况下,它的内部缓存很弱(使用state.unloaded
来更改它),因此只要代码中没有对象,就会从缓存中清除对象。当对象已经在会话中时,SQLAlchemy不会对weak_identity_map=False
执行SQL请求。但这仅适用于query.get(pk)
方法,因此get()
将在会话中使用加载的数据执行SQL请求和刷新对象。
2)急切加载关系会导致请求减少,但并不总是更快。您必须检查数据库和数据。
2.1)从数据库中重新获取数据不会卸载通过关系绑定的对象。
2.2)query.filter_by(id=pk).first()
使用item.group
方法加载,因此如果对象已经在会话中,则不会导致SQL请求。
2.3)是的,这取决于具体情况。对于大多数情况,最好的是希望SQLAlchemy将使用正确的策略:)。对于已经加载的关系,您可以检查相关对象的关系是否通过query.get()
加载,并递归到任何深度。但是当尚未加载关系时,您无法知道相关对象及其关系是否已经加载:即使尚未加载关系,相关对象[s]可能已经在会话中(只是想象您请求第一项,加载其组,然后请求具有相同组的其他项目)。对于您的特定示例,我认为只需递归检查state.unloaded
即可。
答案 1 :(得分:3)
1) 来自Session documentation:
[会话]在某种程度上用作缓存 它实现了身份地图 模式,并存储键入的对象 他们的主要钥匙。但事实并非如此 做任何类型的查询缓存。 ... 这只是 当你说query.get({some primary 密钥})会话不必 发出查询。
2.1)你是对的,刷新对象时不会修改关系。
2.2)是的,该小组将在身份图中。
2.3)我相信你最好的选择是尝试在一个查询中重新加载整个group.items。根据我的经验,发出一个大请求通常比几个较小的请求快得多。唯一一次只重新加载特定的group.item才有意义,其中只有一个需要加载。虽然在这种情况下你正在做一个大型查询而不是一个小查询,所以你实际上并没有减少查询次数。
我没有尝试过,但我相信您应该能够使用sqlalchemy.orm.util.identity_key方法来确定对象是否在sqlalchemy的标识图中。我很想知道调用identiy_key(Group,83)返回的内容。
初步问题) 如果我理解正确,你有一个从数据库中获取的对象,其中某些关系是eagerloaded,你想用单个查询获取其余的关系?我相信您可以使用传递要加载的关系名称的Session.refresh()方法。