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

标签 python sqlalchemy eager-loading

我有一些数据库结构;由于大部分内容与我们无关,我将仅描述一些相关部分。让我们以湖 Item 对象为例:

items_table = Table("invtypes", gdata_meta,
Column("typeID", Integer, primary_key = True),
列("typename",字符串,索引=真),
列(“marketGroupID”,整数,外键(“invmarketgroups.marketGroupID”)),
列(“groupID”,整数,外键(“invgroups.groupID”),索引=真))

映射器(项目,项目表,
属性= {“组”:关系(组,backref =“项目”),
“_Item__attributes”:relation(Attribute, collection_class = attribute_mapped_collection('name')),
“效果”:关系(效果,collection_class = attribute_mapped_collection('name')),
“元组”:关系(元类型,
primaryjoin = metatypes_table.c.typeID == items_table.c.typeID,
使用列表 = 假),
“ID”:同义词(“typeID”),
“名称”:同义词("typename")})

我想在 sqlalchemy/database 层实现一些性能改进,并且有几个想法:
1) 两次请求相同的项目:

item = session.query(Item).get(11184)
item = None(对 item 的引用丢失,对象被垃圾回收)
item = session.query(Item).get(11184)

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

itemMapId = {}
itemMapName = {}

@cachedQuery(1, "查找")
def getItem(寻找,渴望=无):
if isinstance(lookfor, (int, float)):
id = int(查找)
如果eager 是None 并且itemMapId 中的id:
item = itemMapId[id]
别的:
item = session.query(Item).options(*processEager(eager)).get(id)
itemMapId[item.ID] = item
itemMapName[item.name] = item
elif isinstance(lookfor, basestring):
如果eager 是None 并在itemMapName 中查找:
item = itemMapName[查找]
别的:
# 项目有唯一的名字,所以我们可以只获取第一个结果而不确保它的唯一性
item = session.query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
itemMapId[item.ID] = item
itemMapName[item.name] = item
归还元素

我相信 sqlalchemy 至少通过主键 (item.ID) 进行类似的对象跟踪。如果是这样,我可以删除两个映射(尽管删除名称映射需要对使用这些查询的应用程序进行细微修改),以免重复功能并使用常用方法。实际问题是:如果 sqlalchemy 中有这样的功能,如何访问它?

2)急切加载关系通常有助于节省对数据库的大量请求。说,我肯定需要以下一组 item=Item() 属性:

item.group (Group 对象,根据我们 item 的 groupID)
item.group.items(从我们组的项目列表中获取所有项目)
item.group.items.metaGroup(列表中每个项目的元组对象/关系)

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

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

2.2) 如果我获取了 Item 对象,并想访问它的组,我可以使用 item.groupID 来获取组,应用我需要的任何急切语句(“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”。有没有一种方法可以使用 sqlalchemy 工具(在这种情况下所有的其中应该加载)?

最佳答案

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

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

  • deferred() 装饰选定的列.使用 group 将它们组合成一组或几组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 的工作方式:对于每个身份, session 中必须有唯一的对象。默认情况下,它的内部缓存很弱(使用 weak_identity_map=False 更改此设置),因此一旦您的代码中没有对它的引用,该对象就会从缓存中删除。 SQLAlchemy 不会对 query.get(pk) 执行 SQL 请求当对象已经在 session 中时。但这适用于 get()仅方法,所以 query.filter_by(id=pk).first()将使用加载的数据在 session 中执行 SQL 请求和刷新对象。

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

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

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

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

    关于python - 完成对象及其关系并避免在 sqlalchemy 中进行不必要的查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5795492/

    相关文章:

    c# - Linq to NHibernate - 渴望加载孙子而不是 child

    linq-to-sql - 如何映射 LINQ To SQL 以启用预加载,返回 EntitySet 或 ICollection?

    python - 无法调整 matplotlib 窗口大小

    python - Pandas N 元语法到列

    python - 我可以检索 macaddr8 列作为数字吗?

    python - Flask 和 SQLAlchemy,应用程序未在实例上注册

    python - SQLalchemy 每个类别的前 3 个结果

    entity-framework - Entity Framework 4.1默认急切加载

    Python合并pdf而不压缩

    python - 查询多个 SQLAlchemy 多对多关系