Завершение объекта с его отношениями и избегание ненужных запросов в sqlalchemy - PullRequest
6 голосов
/ 26 апреля 2011

У меня есть структура базы данных;поскольку большая часть этого не имеет значения для нас, я опишу только некоторые соответствующие части.Давайте представим объект 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) Запрос одного и того же элемента дважды:

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)

Если у меня есть какой-то идентификатор элемента и еще не загружен элемент, я могу запросить его из базы данных, загружая всеМне нужно: sqlalchemy присоединится к группе, ее элементы и соответствующие метагруппы в рамках одного запроса.Если бы я получал к ним доступ с ленивой загрузкой по умолчанию, sqlalchemy должен был бы выполнить 1 запрос, чтобы получить элемент + 1, чтобы получить группу + 1 * # элементов для всех элементов в списке + 1 * # элементов, чтобы получить метагруппу каждого элемента,что расточительно.

2.1) Но что, если у меня уже есть объект Item, и некоторые свойства, которые я хочу загрузить, уже загружены?Насколько я понимаю, когда я повторно выбираю какой-либо объект из базы данных - его уже загруженные отношения не становятся выгруженными, я прав?

2.2) Если у меня есть объект Item, выбранный, и я хочу получить доступ к егоgroup, я могу просто получить getGroup, используя item.groupID, применяя любые необходимые заявления («items» и «items.metaGroup»).Он должен правильно загрузить группу и ее запрошенные отношения без касания материала элемента.Будет ли sqlalchemy правильно сопоставлять эту извлеченную группу с item.group, чтобы при доступе к item.group он ничего не получал из базовой базы данных?

2.3) Если у меня из базы данных извлечены следующие вещи: originalitem, item.group и некоторая часть элементов из списка item.group.items, некоторые из которых могут иметь загруженную metaGroup, что было бы наилучшей стратегией для завершения структуры данных до того же, что и в приведенном выше списке: повторно выберите группу с помощью ("items", "items.metaGroup") стремятся загрузить или проверить каждый элемент из списка элементов в отдельности, а если элемент или его метагруппа не загружены - загрузить их?Кажется, это зависит от ситуации, потому что, если все уже было загружено некоторое время назад, выдавать такой тяжелый запрос бессмысленно.Предоставляет ли sqlalchemy способ отслеживать, загружено ли какое-то отношение объекта, с возможностью заглянуть глубже, чем просто на один уровень?

В качестве иллюстрации к 2.3 - я могу получить группу с идентификатором 83, охотно выбирая "элементы"и "items.metaGroup".Есть ли способ определить из элемента (который имеет идентификатор группы из 83), загружены ли у него «group», «group.items» и «group.items.metaGroup», с помощью инструментов sqlalchemy (в данном случае всеиз них должны быть загружены)?

Ответы [ 2 ]

6 голосов
/ 28 апреля 2011

Для принудительной загрузки ленивых атрибутов просто получите к ним доступ. Это самый простой способ, и он прекрасно работает для отношений, но не столь эффективен для 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=…), предложенное Натаном Вильяэскусом, вероятно, является лучшим решением. Единственный недостаток, который я вижу, заключается в том, что он сначала истекает атрибуты, поэтому вы должны убедиться, что среди переданных в качестве аргумента attribute_names атрибутов не загружено (например, с помощью пересечения с state.unloaded).

Обновление

1) SQLAlchemy отслеживает загруженные объекты. Вот как работает ORM: в сеансе должен быть единственный объект для каждого идентификатора. Его внутренний кеш по умолчанию слаб (используйте weak_identity_map=False, чтобы изменить это), поэтому объект удаляется из кеша, как только в вашем коде нет ссылок на него. SQLAlchemy не будет выполнять запрос SQL для query.get(pk), когда объект уже находится в сеансе. Но это работает только для метода 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 рекурсивно.

3 голосов
/ 28 апреля 2011

1) Из документации Session :

[Сессия] в некоторой степени используется в качестве кэша, в этом он реализует карту идентичности шаблон и хранит объекты, связанные с их первичный ключ. Тем не менее, это не делать любые виды кэширования запросов. ... Это только когда вы говорите query.get ({некоторые первичные ключ}), что сеанс не должен оформить запрос.

2.1) Вы правы, отношения не изменяются при обновлении объекта.

2.2) Да, группа будет в идентификационной карте.

2.3) Я полагаю, что вам лучше всего попытаться перезагрузить все group.items в одном запросе. Исходя из моего опыта, обычно гораздо быстрее выполнить один большой запрос, чем несколько меньших. Единственный раз, когда имеет смысл перезагружать только определенный group.item, это был именно один из них, который нужно было загрузить. Хотя в этом случае вы выполняете один большой запрос вместо одного маленького, так что вы фактически не уменьшаете количество запросов.

Я не пробовал, но я считаю, что вы должны иметь возможность использовать метод sqlalchemy.orm.util.identity_key , чтобы определить, находится ли объект в карте идентификаторов sqlalchemy. Мне было бы интересно узнать, что возвращает вызов identiy_key (Group, 83).

Начальный вопрос) Если я правильно понимаю, у вас есть объект, который вы извлекли из базы данных, куда были загружены некоторые из его отношений, и вы хотели бы получить остальные отношения одним запросом? Я полагаю, что вы можете использовать метод Session.refresh () , передавая имена отношений, которые вы хотите загрузить.

...