Doctrine / MySQL Медленный запрос даже при использовании индексов - PullRequest
0 голосов
/ 11 января 2020

Я немного убрал вопрос, потому что он становился очень большим и нечитаемым.

Запуск на моем локальном хосте.

Как вы можете видеть на изображении ниже, запрос занимает 755.15 ms при выборе из таблицы Job, которая содержит 15000 строк (с условиями, возвращающими 6650)

Таблица Company, содержит 1000 строк. Таблица geo__name содержит приблизительно 84300 строк и не доставляет мне никаких проблем, поэтому я считаю, что проблема заключается в структуре базы данных или чем-то подобном.

Структура этих двух таблиц следующая:

Задание таблицы:

CREATE TABLE `job` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `company_id` int(11) NOT NULL,
  `activity_sector_id` int(11) DEFAULT NULL,
  `status` int(11) NOT NULL,
  `active` datetime NOT NULL,
  `contract_type_id` int(11) NOT NULL,
  `salary_type_id` int(11) NOT NULL,
  `workday_id` int(11) NOT NULL,
  `geoname_id` int(11) NOT NULL,
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `minimum_experience` int(11) DEFAULT NULL,
  `min_salary` decimal(7,2) DEFAULT NULL,
  `max_salary` decimal(7,2) DEFAULT NULL,
  `zip_code` int(11) DEFAULT NULL,
  `vacancies` int(11) DEFAULT NULL,
  `show_salary` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `created_at` (`created_at`,`active`,`status`) USING BTREE,
  CONSTRAINT `FK_FBD8E0F823F5422B` FOREIGN KEY (`geoname_id`) REFERENCES `geo__name` (`id`),
  CONSTRAINT `FK_FBD8E0F8398DEFD0` FOREIGN KEY (`activity_sector_id`) REFERENCES `activity_sector` (`id`),
  CONSTRAINT `FK_FBD8E0F85248165F` FOREIGN KEY (`salary_type_id`) REFERENCES `job_salary_type` (`id`),
  CONSTRAINT `FK_FBD8E0F8979B1AD6` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`),
  CONSTRAINT `FK_FBD8E0F8AB01D695` FOREIGN KEY (`workday_id`) REFERENCES `workday` (`id`),
  CONSTRAINT `FK_FBD8E0F8CD1DF15B` FOREIGN KEY (`contract_type_id`) REFERENCES `job_contract_type` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Компания таблицы:

CREATE TABLE `company` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `logo` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `website` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `user_id` int(11) NOT NULL,
  `phone` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `cifnif` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `type` int(11) NOT NULL,
  `subscription_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UNIQ_4FBF094FA76ED395` (`user_id`),
  KEY `IDX_4FBF094F9A1887DC` (`subscription_id`),
  KEY `name` (`name`(191)),
  CONSTRAINT `FK_4FBF094F9A1887DC` FOREIGN KEY (`subscription_id`) REFERENCES `subscription` (`id`),
  CONSTRAINT `FK_4FBF094FA76ED395` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Запрос следующий:

SELECT 
  j0_.id AS id_0, 
  j0_.status AS status_1, 
  j0_.title AS title_2, 
  j0_.min_salary AS min_salary_3, 
  j0_.max_salary AS max_salary_4, 
  c1_.id AS id_5, 
  c1_.name AS name_6, 
  c1_.logo AS logo_7, 
  a2_.id AS id_8, 
  a2_.name AS name_9, 
  g3_.id AS id_10, 
  g3_.name AS name_11, 
  j4_.id AS id_12, 
  j4_.name AS name_13, 
  j5_.id AS id_14, 
  j5_.name AS name_15, 
  w6_.id AS id_16, 
  w6_.name AS name_17 
FROM 
  job j0_ 
  INNER JOIN company c1_ ON j0_.company_id = c1_.id 
  INNER JOIN activity_sector a2_ ON j0_.activity_sector_id = a2_.id 
  INNER JOIN geo__name g3_ ON j0_.geoname_id = g3_.id 
  INNER JOIN job_salary_type j4_ ON j0_.salary_type_id = j4_.id 
  INNER JOIN job_contract_type j5_ ON j0_.contract_type_id = j5_.id 
  INNER JOIN workday w6_ ON j0_.workday_id = w6_.id 
WHERE 
  j0_.active >= CURRENT_TIMESTAMP 
  AND j0_.status = 1 
ORDER BY 
  j0_.created_at DESC

При выполнении вышеуказанного запроса у меня есть это результаты:

в MYSQL верстак: 0.578 sec / 0.016 sec в Symfony профилировщик: 755.15 ms

Вопрос: является ли продолжительность этого запроса правильной? если нет, как я могу улучшить скорость запроса? это кажется многим.

enter image description here

Панель инструментов отладки symfony, если она помогает:

enter image description here

Как вы можете видеть на изображении ниже, я получаю только те данные, которые мне действительно нужны:

enter image description here

Объяснение запрос:

enter image description here

Временная шкала:

enter image description here

Ответы [ 4 ]

1 голос
/ 17 января 2020

Сервер MySQL не может справиться с нагрузкой на него. Это может быть связано с конфликтом ресурсов или с неправильной настройкой, а также с проблемой жесткого диска.

0 голосов
/ 14 января 2020

Существует несколько причин, почему Symfony медленный.

1. Ошибка сервера

Во-первых, это может быть ошибка сервера. Производительность сервера может помешать вашему запросу.

2. Размер данных и отложенный рендеринг

Затем следует размер данных. Как вы можете видеть на изображении ниже, запрос в одном из моих проектов имеет размер данных 50 МБ (в настоящее время около 20 000 строк).
Анализ 50 МБ в HTML может занять некоторое время, в основном из-за циклов.
Тем не менее, есть решения по этому поводу, такие как отложенный рендеринг.

enter image description here

Отложенный рендеринг довольно прост, вместо анализа данных в вашей ветке вы,
отправьте все данные в javascript varaible и используйте javascript для анализа / рендеринга данных после загрузки DOM.

3. Оптимизация запросов

Как я уже писал в комментарии, вы можете проверить следующий вопрос, на котором я объяснил, почему пользовательские запросы важны.
Являются ли Doctrine отношениями, влияющими на приложение производительность?

В этом вопросе вы прочтете этот порядок значения ... На самом деле это самая важная вещь.

В то время как stati c данные в ваших базах данных часто вставляются в правильном порядке
это редко имеет место для динамических c данных (данных, предоставленных пользователем в течение срока службы сайта)

Именно поэтому использование ORDER BY в вашем запросе часто ускоряет рендеринг страницы,
при doctrine не будет выполнять дополнительные запросы самостоятельно.

Например, на одном из моих сайтов в индексе отображается около 700 записей.
Во-первых, вот количество запросов при использовании findAll():

enter image description here

Показывает 254 запроса (253 дубликата) за 144 мс плюс время рендеринга 39.
Далее , используя второй параметр findBy(), ORDER BY, я получаю это повторно sult:

enter image description here

Здесь вы можете увидеть полный запрос (скриншот большой)
Намного лучше, только 1 запрос в 8 мс и примерно в то же время рендеринга.
Но здесь я не использую никаких полей из ассоциаций.
С того момента, как я это сделаю, doctrine qui сделает дополнительный запрос и подсчитает количество запросов. и время взлетит до небес.
В конце оно вернется к чему-то вроде findAll()

И наконец, это пользовательский запрос:

enter image description here

В этом пользовательском запросе время запроса изменилось с 8 мс до 38 мс.
Но, в отличие от предыдущего запроса, я получил гораздо больше данных в своем результате,
что помешает doctrine выполнять дополнительный запрос.
Опять же, ORDER BY() имеет значение в этом запросе. Без этого я стремительно вернусь к 84 запросам.

4. Partials

Когда вы делаете пользовательский запрос, вы можете загружать объекты partials вместо полных данных.
Как вы сказали в своем вопросе, поле description, похоже, замедляет скорость загрузки. ,
с партиалами, вы можете избежать загрузки некоторых полей из таблицы, что увеличит скорость запроса.

Во-первых, вместо обычного синтаксиса вы создадите конструктор запросов:

$em=$this->getEntityManager();
$qb=$em->createQueryBuilder();

На всякий случай, я предпочитаю хранить $em в качестве отдельной переменной (если я хочу, например, получить какой-нибудь репозиторий классов).

Затем вы можете запустить свой частичный select. Осторожно, сначала select не может включать в себя любые поля ассоциации:

$qb->select("partial job.{id, status, title, minimum_experience, min_salary, max_salary, zip_code, vacancies")
   ->from(Job::class, "job");

Затем вы можете добавить свои ассоциации:

$qb->addSelect("company")
   ->join("job.company", "company");

Или даже добавить частичную ассоциацию, если вы этого не делаете нужны все данные ассоциации:

$qb->addSelect("partial activitySector.{id}")
   ->join("job.activitySector", "activitySector");

$qb->addSelect("partial job.{id, company_id, activity_sector_id, status, active, contract_type_id, salary_type_id, workday_id, geoname_id, title, minimum_experience, min_salary, max_salary, zip_code, vacancies, show_salary");

5. Кэши

Вы также можете использовать различные кэши, например Zend OPCache для PHP, которые вы найдете в этом вопросе: Почему Symfony3 такой медленный?

Существует также SQL кэш Varni sh.


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

Надеюсь, это окажется полезным, и вы сможете решить вашу проблему.

0 голосов
/ 16 января 2020

Во-первых, я бы начал ваше выступление, добавив MySQL ключевое слово "STRAIGHT_JOIN", которое говорит MySQL запрашивать данные в указанном мной порядке, не пытайтесь думать об отношениях для меня. Тем не менее, если ваш набор данных настолько мал и уже занимает 1/2 секунды, не знаю, поможет ли это так же сильно, но на более крупных наборах данных, которые, как я знал, это значительно повысит производительность.

Далее вы появляетесь получать описания поиска на основе результатов отношений PK / FK. Не видя индексы в этих таблицах, я бы предложил выполнить покрывающие индексы, которые содержат и ключ, и описание, чтобы объединение могло получать данные со страниц индекса, которые оно использует для JOIN вместо использования страницы индекса, найти фактические страницы данных, чтобы получить описание и продолжить.

Наконец, ваша таблица заданий с индексом (created_at, active, status) может работать лучше, если индекс имеет индекс как (status, active, created_at).

Имея существующий индекс, думайте об этом так, каждый день данных помещается в один ящик. Внутри каждого блока дня, который сортируется по активной метке времени (даже если он упрощен по активной дате), ТО затем статус. Итак, на каждый день CREATED вы открываете ящик. Посмотрите на дополнительные поля, по одному на каждую «Активную» метку времени (например, по дням). В пределах каждой активной временной метки (дня) только теперь вы можете видеть, записаны ли записи «Status = 1». Поэтому откройте каждый активный день метки времени, оцените Status = 1, затем закройте каждый созданный блок дня и go для следующего созданного блока дня и повторите. Итак, посмотрите на трудоемкость открытия каждого ящика в день, каждого активного ящика в этот день.

Теперь, под предлагаемым индексом, начинающимся со статуса. Теперь у вас есть очень ограниченное количество блоков, по одному для каждого статуса. Откройте только 1 поле для статуса = 1 Это единственные, которые вы хотите рассмотреть ... Все остальные вас не волнуют. Внутри этого у вас есть фактические записи, основанные на ACTIVE Timestamp, и это подразделено. Отсюда вы можете сразу перейти к текущим отметкам времени. Начиная с первой записи и оставшейся в коробке, теперь у вас есть все соответствующие записи. Выполнено. Так как эти записи (индекс) ТАКЖЕ имеют Created_at как часть индекса, он может оптимизировать это с помощью сортировки по убыванию.

Для обеспечения "покрытия индексов" для других таблиц поиска, если они еще не существуют, Я предлагаю следующее.

table              index
company            ( id, name, logo )
activity_sector    (id, name )
geo__name          ( id, name )
job_salary_type    ( id, name )
job_contract_type  ( id, name )
workday            ( id, name )

И ключевое слово MySQL ...

SELECT STRAIGHT_JOIN   (rest of query...)
0 голосов
/ 13 января 2020

Столько ключей, постарайтесь свести к минимуму количество ключей.

...