一般来说,对给定对象执行单个查询比执行多个查询更好。假设我有一堆“儿子”对象,每个对象都有一个“父亲”。我获取了所有“儿子”对象:
sons = Son.all()
然后,我想获取该组儿子的所有父亲。我会这样做:
father_keys = {} for son in sons: father_keys.setdefault(son.father.key(), None)
然后我可以这样做:
fathers = Father.get(father_keys.keys())
现在,这假设 son.father.key() 实际上不会获取对象。我错了吗?我有一堆代码假设 object.related_object.key() 实际上不会从数据存储中获取 related_object。
我这样做对吗?
你在做的事情在大多数情况下是正确的,但关键在于理解 son.father.key() 的行为,特别是在不同的框架或 ORM(例如 Django、Google Datastore、SQLAlchemy 等)中。让我详细解释一下。
son.father.key()
在大多数 ORM 或数据存储框架中,son.father.key() 返回的是一个对象的“键”或“ID”,而不是完整的父对象本身。通常,这种方法是轻量级的操作,它仅获取对象的唯一标识符,而不会触发实际的数据库查询以加载完整的 Father 对象。
Father
例如,假设你使用的是 Google Cloud Datastore 或类似的 NoSQL 数据存储,son.father.key() 只会获取父对象的唯一标识符(例如,Key 类型),并不会触发数据库查询来加载整个父对象。这个行为通常是符合预期的。
Key
如果你访问 son.father,例如 son.father.some_property,这通常会触发数据库查询,尤其是当 son.father 还没有被加载(即是一个“延迟加载”对象)时。所以,假设你只是获取 son.father.key(),它不会触发查询,但是如果你尝试访问父对象的其他属性,就会触发查询。
son.father
son.father.some_property
你当前的做法是:
Son
key()
father_keys.setdefault(son.father.key(), None)
Father.get(father_keys.keys())
这种方法是合适的,因为你首先通过 key() 获取所有父亲对象的标识符,然后使用这些标识符在数据库中一次性查询所有父亲。这大大减少了查询次数,比为每个儿子单独查询父亲要高效得多。
setdefault()
你使用 setdefault() 是为了确保每个 key() 只出现一次。这是一个合理的做法,因为 setdefault() 会忽略已存在的键。你还需要确保在 father_keys 中不会包含 None(即没有父亲的儿子),否则你可能会在后续的 Father.get() 查询中遇到问题。
father_keys
None
Father.get()
可以稍微优化你的代码,使其更简洁,并确保避免无效的 None 值:
father_keys = set(son.father.key() for son in sons if son.father) fathers = Father.get(father_keys)
这里: - 我使用了集合推导式来确保每个父亲的 key() 只出现一次。 - 我添加了 if son.father 来确保只有当 son 确实有 father 时,才会把 key() 添加到集合中。
if son.father
son
father
你的思路是正确的,如果你只访问 son.father.key(),并且这个方法不会触发加载父对象的数据,那么你的代码是高效且正确的。你通过一次查询获取所有父亲,避免了为每个儿子执行单独查询的低效做法。
不过,如果你后续访问 son.father 或其属性时需要触发查询,可能需要注意延迟加载的行为。因此,确保你只依赖于 key() 或通过批量查询来加载父对象,能有效避免过多的数据库查询。