Возможные решения для медленных левых соединений в сложном запросе доктрины

У меня есть метод репозитория Symfony, который извлекает довольно сложный набор данных, который затем будет помещен в файл CSV классом менеджера экспорта. Я не хочу размещать весь код обработки задания экспорта, но мне удалось выделить точку, в которой запрос замедляется, поэтому мой вопрос касается любой другой альтернативы для ускорения этого запроса, а не самого кода. Таким образом, полученные данные представляют собой некоторые данные «сайта», которые имеют несколько «членств», которые затем имеют «пользователя». Итак, проблема в том, что когда мой запрос пытается присоединить информацию о пользователе к сайту, это замедляет выполнение. Это выглядит так:

$qb->leftJoin('s.memberships', 'ex_sm', 'WITH', 'ex_sm.revokedAt IS NULL');
$qb->leftJoin('ex_sm.user', 'ex_jappr', 'WITH', 'ex_sm.approverJobReactiveWeight IS NOT NULL');  

Есть несколько вещей (я пробовал или думал, может помочь), чтобы упомянуть:

  • Я проверил таблицы, все связанные столбцы имеют индекс и имеют один и тот же тип данных int.
  • Я прочитал статью о проблемах с производительностью DQL, где упоминалось, что чрезмерное использование вызовов DQL Left Join может привести к снижению производительности, поскольку они повторно отображают один и тот же объект сущности снова и снова. Возможное решение, упомянутое там, заключалось в том, чтобы получить основной набор данных, а затем, прокручивая коллекцию, добавить дополнительные (объединяющие поля данных) к каждому элементу непосредственно из класса сущности поля. Это, возможно, могло бы сработать (не уверен, какое влияние это окажет), проблема в том, что у меня действительно сложный унаследованный код, и я не хочу касаться логики менеджера экспорта, потому что это потребует слишком много тестирования. Менеджер экспорта ожидает класс построителя запросов, поэтому мне нужно найти решение в самом запросе.
  • Проблема определенно вызвана соединением, а не предложением «WITH» или дополнительными условиями. Я попытался вызвать запрос с помощью простого вызова leftJoin, тот же результат.
  • Я знаю, что методы leftJoin можно вызывать в цепочке друг к другу, код выглядит так, потому что некоторые из этих вызовов используются в операторах if.
  • Я провожу 2 дня, пробуя всевозможные вещи, найденные здесь и на других сайтах.

Существует 6 различных типов пользователей, на данный момент я только что вызвал скрипт, извлекающий тот, что указан выше, и возврат данных занял 33 минуты. Речь идет о 512 сайтах, что не является огромным набором данных. Итак, мой вопрос: существует ли другой способ DQL или Doctrine для упрощения или уменьшения количества вызовов leftJoins в таком сложном запросе и как-то улучшить производительность?

Обновлять: Я думаю, что проблема связана с индексами, поэтому я подробно расскажу об отношениях: Сущность «членство» происходит из таблицы с именем «доступ», отношение к пользователю в ее модели выглядит следующим образом:

/**
 * The user this membership encapsulates.
 *
 * @ORM\ManyToOne(targetEntity = "User", inversedBy = "siteMemberships", cascade = {"persist"})
 * @ORM\JoinColumn(name = "security_identity_id", referencedColumnName = "id")
 *
 * @var User
 */
protected $user; 

Вот скриншот индексов, присвоенных столбцам «security_identity_id».

И связанный пользователь происходит из таблицы «пользователь», имеющей это отношение, указывающее на членство

/**
 * @ORM\OneToMany(targetEntity = "SiteMembership", mappedBy = "user", cascade = {"persist"}, fetch = "EXTRA_LAZY")
 */
protected $siteMemberships;

Первичный ключ — это «id» в сущности. Надеюсь, это даст лучшее представление о проблеме. Я не эксперт по sql, но перепробовал все, что нашел и смог понять до сих пор.

Обновлять: Вот выполненный запрос:

SELECT s0_.name AS name_0, s0_.id AS id_1, GROUP_CONCAT(DISTINCT u1_.name SEPARATOR ', ') AS sclr_2 FROM site s0_ 
  LEFT JOIN access a2_ ON s0_.id = a2_.entity_id 
  AND a2_.type IN ('site_member') 
  AND (a2_.revoked_at IS NULL) 
  LEFT JOIN user u1_ ON a2_.security_identity_id = u1_.id 
  AND (a2_.approver_job_reactive_weight IS NOT NULL)

Это возвращает первую запись сайта с присоединенным к ней членством и пользовательскими правами. Но даже фетиш этого одного единственного ряда занимает более 2 минут.

Вот информация о создании таблицы таблицы доступа (членства).

'CREATE TABLE `access` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `buddy_id` int(11) DEFAULT NULL,
  `security_identity_id` int(11) DEFAULT NULL,
  `revoked_at` datetime DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `type` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `approver_job_reactive_weight` int(11) DEFAULT NULL,
  `entity_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `access_idx` (`type`,`security_identity_id`,`entity_id`,`buddy_id`),
  KEY `IDX_6692B54395CE8D6` (`buddy_id`),
  KEY `IDX_6692B54DF9183C9` (`security_identity_id`),
  KEY `IDX_6692B5481257D5D` (`entity_id`),
  KEY `idx_revoked_id_approver_type` (`revoked_at`,`entity_id`,`approver_job_reactive_weight`,`approver_job_planned_weight`,`type`),
  KEY `idx_user_site_access` (`revoked_at`,`security_identity_id`,`buddy_id`,`type`),
  KEY `idx_user` (`security_identity_id`),
  KEY `idx_user_id` (`security_identity_id`),
  CONSTRAINT `FK_6692B54DF9183C9` FOREIGN KEY (`security_identity_id`) REFERENCES `user` (`id`)
)
ENGINE=InnoDB AUTO_INCREMENT=262441 DEFAULT CHARSET=utf8 
COLLATE=utf8_unicode_ci'

Я удалил некоторые столбцы, которые не имеют отношения к делу.

Предоставьте сгенерированный SQL.

Rick James 01.05.2023 16:41
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
0
1
70
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы присоединились ко многим сторонам. Это причина замедления

$qb->leftJoin('s.memberships', 'ex_sm', 'WITH', 'ex_sm.revokedAt IS NULL');

больше членств, тем медленнее ваш запрос. Я не знаю, как сделать полный запрос, но вы можете запустить запрос из таблицы членства или сделать второй запрос.

Привет, Мехмет, спасибо за ответ. Я удалил все дополнительные материалы, так что сейчас весь запрос — это то, что вы видите. Если выбрать столбец присоединенного членства на сайте и удалить часть jobApprover, запрос будет успешно выполнен в течение 4 секунд. Это не супер быстро для объема возвращаемых данных, но все же не полчаса. Проблема, кажется, начинается с присоединения пользователя.

domjanzsoo 01.05.2023 09:32

«Это возвращает первую запись сайта» — я не вижу LIMIT 1; как вы находите "первое"?

Rick James 02.05.2023 04:32

привет. Я думал, что ваша проблема возникла из-за многих побочных запросов. Однако я видел, что вы добавили индексы. Это поможет ускорить запрос, но размер вашей базы данных увеличится. хорошего дня. Спасибо за Рика Джеймса :)

Mehmet Emre Vural 08.05.2023 08:59
Ответ принят как подходящий

При выполнении LEFT JOINON нужно сказать, как связаны таблицы. В предложении WHERE обычно есть IS NULL или IS NOT NULL, чтобы указать, следует ли исключить или включить правые строки.

LEFT JOIN и INNER JOIN в основном имеют одинаковую скорость. Но мне нужно увидеть индексы (SHOW CREATE TABLE) и SQL SELECT, чтобы увидеть, есть ли другие проблемы.

Более

Заменять

KEY `IDX_6692B5481257D5D` (`entity_id`),

С

INDEX(entity_id, type, revoked_at)

Привет, Рик, только что обновил вопрос. Пока я смотрел на запрос, мне пришло в голову, что проблема может быть вызвана GRUP CONCAT имен. Попробую без этого. Также интересно, должен ли быть какой-либо индекс имен в пользовательской таблице?

domjanzsoo 01.05.2023 21:30

Нет, удаление конкатенации не добавило никаких улучшений.

domjanzsoo 01.05.2023 21:48

Ну, что-нибудь еще попробовать.

Rick James 02.05.2023 04:33

Не уверен, что правильно понимаю, что вы подразумеваете под заменой. Вы хотите удалить этот ключ и создать другой, который будет использоваться тремя столбцами, или просто использовать один и тот же существующий ключ со столбцами «type» и «revoked_at»?

domjanzsoo 02.05.2023 08:43

Это то, что я добавил новый ключ индекса для этих 3 столбцов. Время выполнения, ранее составлявшее более получаса, теперь сокращено до 8 секунд. Кажется, мне действительно не хватает хорошего понимания того, как работают эти индексы. Поэтому я предполагаю, что хорошей практикой является наличие общего ключа индекса среди столбцов, которые участвуют в одной и той же логике левого соединения. Так ли это?

domjanzsoo 02.05.2023 08:56

Мне потребовались годы, чтобы стать достаточно опытным, чтобы я мог обычно смотреть на SELECT и выстукивать оптимальное INDEX. Некоторые из моих приемов есть в моей Index Cookbook

Rick James 02.05.2023 21:25

Это действительно приятно, спасибо за это.

domjanzsoo 02.05.2023 22:03

Другие вопросы по теме