Как проинструктировать ORM SQLAlchemy для одновременного выполнения нескольких запросов при загрузке отношений?

Я использую ORM SQLAlchemy. У меня есть модель, которая имеет множественные отношения "многие ко многим":

User
User <--MxN--> Organization
User <--MxN--> School
User <--MxN--> Credentials

Я использую эти таблицы , поэтому есть также таблицы User_to_Organization, User_to_School и User_to_Credentials, которые я непосредственно не использую.

Теперь, когда я пытаюсь загрузить одного пользователя (используя его идентификатор PK) и его отношения (и связанные с ним модели), используя объединенную загрузку, я получаю ужасную производительность (15 + секунд). Я предполагаю, что это связано с этой проблемой:

При использовании нескольких уровней глубины с объединенной загрузкой или подзапросами загрузка коллекций внутри коллекций будет умножать общее количество строк, выбранных декартово. Обе формы активной загрузки всегда соединяются с исходным родительским классом.

Если я введу другой уровень или два в иерархию:

Organization <--1xN--> Project
School <--1xN--> Course
Project <--MxN--> Credentials
Course <--MxN--> Credentials

Завершение запроса занимает 50 секунд, хотя общий объем записей в каждой таблице довольно мал.

Используя ленивую загрузку, мне необходимо вручную загрузить каждую взаимосвязь, и на сервер многократные поездки.

например. Операции, выполняемые последовательно как запросы:

  • Получить пользователя
  • Получить пользовательские организации
  • Получить пользовательские школы
  • Получить учетные данные пользователя
  • Для каждой организации получите свои проекты
  • Для каждой школы получите свои курсы
  • Для каждого проекта получите свои учетные данные
  • Для каждого курса получите свои учетные данные

Тем не менее, все это заканчивается менее чем за 200 мс.

Мне было интересно, есть ли вообще возможность использовать ленивую загрузку, но выполняйте параллельные запросы загрузки. Например, используя модуль concurrent, asyncio или используя gevent.

например. Шаг 1 (параллельно):

  • Получить пользователя
  • Получить пользовательские организации
  • Получить пользовательские школы
  • Получить учетные данные пользователя

Шаг 2 (параллельно):

  • Для каждой организации получите свои проекты
  • Для каждой школы получите свои курсы

Шаг 3 (параллельно):

  • Для каждого проекта получите свои учетные данные
  • Для каждого курса получите свои учетные данные

На самом деле, на данный момент может также работать загрузка типа подзапроса, то есть возвращать Organization и OrganizationID/Project/Credentials в два отдельных запроса:

например. Шаг 1 (параллельно):

  • Получить пользователя
  • Получить пользовательские организации
  • Получить пользовательские школы
  • Получить учетные данные пользователя

Шаг 2 (параллельно):

  • Получить организации
  • Получить школы
  • Получить проекты организаций, присоединиться к учетным данным
  • Получите курсы школ, присоединитесь к Credentials

Ответ 1

Первое, что вам нужно сделать, это проверить, действительно ли выполняются запросы на db. Я бы не стал предполагать, что SQLAlchemy делает то, что вы ожидаете, если вы не знакомы с ним. Вы можете использовать echo=True в своей конфигурации двигателя или посмотреть некоторые журналы журналов (не уверен, как это сделать с помощью mysql).

Вы упомянули, что используете разные стратегии загрузки, поэтому, я думаю, вы прочитали документы по этому поводу ( http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html). Для того, что вы делаете, я бы, вероятно, рекомендовал загрузку подзапроса, но это полностью зависит от количества строк/столбцов, с которыми вы имеете дело. По моему опыту, это хорошая общая отправная точка.

Одно замечание: вам может понадобиться что-то вроде:

db.query(Thing).options(subqueryload('A').subqueryload('B')).filter(Thing.id==x).first()

С filter.first скорее, чем get, так как последний случай не будет повторно выполнять запросы в соответствии с вашей стратегией загрузки, если первичный объект уже находится в карте идентификации.

Наконец, я не знаю ваших данных, но эти цифры звучат довольно ужасно для чего-либо, кроме огромного набора данных. Убедитесь, что у вас есть правильные индексы, указанные во всех ваших таблицах.

Возможно, вы уже прошли через все это, но на основе информации, которую вы предоставили, похоже, вам нужно сделать больше работы, чтобы сузить проблему. Это db-схема, или это запросы SQLA выполняются?

В любом случае, я бы сказал, "нет" для запуска нескольких запросов в разных соединениях. Любая попытка сделать это может привести к несовместимым данным, возвращающимся в ваше приложение, и если вы думаете, что у вас есть проблемы сейчас.....: -)

Ответ 2

MySQL не имеет parallelism в одном соединении. Для ORM для этого потребуется несколько подключений к MySQL. Как правило, накладные расходы, связанные с попыткой сделать это, "не стоит".

Чтобы получить user, его Organizations, Schools и т.д. можно сделать (в mysql) с помощью одного запроса:

SELECT user, organization, ...
    FROM Users
    JOIN Organizations ON ...
    etc.

Это значительно эффективнее, чем

SELECT user FROM ...;
SELECT organization ... WHERE user = ...;
etc.

(Это не "parallelism".)

Или, может быть, ваши "шаги" не совсем "правильные"?...

SELECT user, organization, project
    FROM Users
    JOIN Organizations ...
    JOIN Projects ...

Это за один шаг позволяет всем пользователям вместе со всеми их организациями и проектами.

Но есть ли "пользователь", связанный с "проектом"? Если нет, то это неправильный подход.

Если ORM не предоставляет механизм для генерации запросов, подобных тем, что он "мешает".