MySQL запрос с подзапросом занимает значительно больше времени при использовании полного текста в где, а не порядок - PullRequest
0 голосов
/ 18 декабря 2018

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

Запрос также содержит подзапрос.

Без подзапроса основной запрос всегда быстрый.

Сам подзапрос также всегда быстрый.

Но вместе они очень медленные.

Удаление полного текстапоиск из предложения where и упорядочение по полнотекстовому поиску действительно быстрое.

Так что это медленнее, чем при использовании полнотекстового поиска в пределах где.

Это простой читаемый обзор, точные запросы приведены ниже.

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

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

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

В частности, я запутался, почему это быстро с полнотекстовым поиском в порядкено не с этим в where.

Запрос, который я хочу (медленный)

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

select 
  *, 
  MATCH (name) AGAINST ('Old Tra*' IN BOOLEAN MODE) AS relevance_score 
from 
  `app_records` 
where 
  `id` in (
    select 
      distinct(app_record_parents.record_id) 
    from 
      `app_group_records` 
      inner join `app_record_parents`
        on `app_record_parents`.`parent_id` = `app_group_records`.`record_id` 
    where 
      `group_id` = 3
  ) 
  and
    MATCH (name) AGAINST ('Old Tra*' IN BOOLEAN MODE)
order by 
  `relevance_score` desc 
limit 
  10;

Этот запрос принимает10 секунд.

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

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

Самостоятельная выборка

select distinct(app_record_parents.record_id) 
from
   `app_group_records` 
   inner join
      `app_record_parents` 
      on `app_record_parents`.`parent_id` = `app_group_records`.`record_id` 
where
   `group_id` = 3

Самостоятельная выборка занимает 7 мс с результатами 2600.

Основной запрос без дополнительного выбора

select 
  *, 
  MATCH (name) AGAINST ('Old Tra*' IN BOOLEAN MODE) AS relevance_score 
from 
  `app_records` 
where 
  MATCH (name) AGAINST ('Old Tra*' IN BOOLEAN MODE)
order by 
  `relevance_score` desc 
limit 
  10;

Основнойзапрос без дополнительного выбора занимает 6 мсек с 2971 возможным результатом (очевидно, там есть предел 10.)

Это быстрее с меньшими результатами

Тот же запрос, но совпадает с "Old Traf", а не "«Старый Тра» занимает 300 мс.

Количество результатовпри использовании «Old Traf» они явно отличаются от «Old Tra».

Результаты полного запроса

  • «Old Tra»: 9
  • «Old Traf»": 2

Записи, соответствующие полнотекстовому поиску

  • " Old Tra ": 2971
  • " Old Traf ": 120

Удаление местоположения решает проблему

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

select 
  *, 
  MATCH (name) AGAINST ('Old Tra*' IN BOOLEAN MODE) AS relevance_score 
from 
  `app_records` 
where 
  `id` in (
    select 
      distinct(app_record_parents.record_id) 
    from 
      `app_group_records` 
      inner join `app_record_parents`
        on `app_record_parents`.`parent_id` = `app_group_records`.`record_id` 
    where 
      `group_id` = 3
  )
order by 
  `relevance_score` desc 
limit 
  10;

Но тогда мне нужно отфильтровать нерелевантные результаты в коде

Я использую это в php, так что теперь я могу отфильтровать результаты, чтобы удалить все, которые имеют 0 балл релевантности (например, если есть только 2 совпадения), 8 случайных результатов с оценкой релевантности по-прежнему будут включены, так как я не использую где).

array_filter($results, function($result) {
    return $result->relevance_score > 0;
});

Очевидно, что это действительно быстро, так что это не проблема.

Но я все еще не понимаю, что не так с моими запросами.

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

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

Схема таблицы

Вот мои таблицы

CREATE TABLE `app_records` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `app_models_name_IDX` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=960004 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;



CREATE TABLE `app_record_parents` (
  `record_id` int(10) unsigned NOT NULL,
  `parent_id` int(10) unsigned DEFAULT NULL,
  KEY `app_record_parents_record_id_IDX` (`record_id`) USING BTREE,
  KEY `app_record_parents_parent_id_IDX` (`parent_id`) USING BTREE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;



CREATE TABLE `app_group_records` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `group_id` int(10) unsigned NOT NULL,
  `record_id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Примечание о том, что делают запросы

Подзапрос получает список record_idкоторые принадлежат group_id 3.

Так что, хотя в app_records имеется 960004 записей, есть только 2600, которые принадлежат к группе 3, и именно против этих 2600 я пытаюсь запросить имена, которые соответствуют «Old Tra»,

Итак, подзапрос получает список этих 2600 record_id, а затем я делаю WHERE id IN <subquery>, чтобы получить релевантные результаты из app_records.

РЕДАКТИРОВАТЬ: Использование объединений одинаково медленно

Простое добавление с использованием объединений имеет ту же проблему.Требуется 10 секунд для «Old Traf» и 400 мс для «Old Traf» и очень быстрая, когда не используется полнотекстовый поиск в местоположении.

SELECT 
  app_records.*, 
  MATCH (NAME) AGAINST ('Old Tra*' IN BOOLEAN MODE) AS relevance_score 
FROM 
  `app_records` 
  INNER JOIN app_record_parents ON app_records.id = app_record_parents.record_id 
  INNER JOIN app_group_records ON app_group_records.record_id = app_record_parents.parent_id 
WHERE 
  `group_id` = 3 
  AND MATCH (NAME) AGAINST ('Old Tra*' IN BOOLEAN MODE) 
GROUP BY 
  app_records.id 
LIMIT 
  10;

1 Ответ

0 голосов
/ 18 декабря 2018

app_record_parents

  • Не имеет PRIMARY KEY;следовательно, могут иметься ненужные повторяющиеся пары.
  • Не имеет оптимальных индексов.
  • См. this для нескольких советов.
  • Возможноapp_group_records также много-много?

Вы ищете Old Tra* где-нибудь в name?Если нет, то почему бы не использовать WHERE name LIKE 'Old Tra%.В этом случае добавьте INDEX(name).

Примечание. Когда задействован FULLTEXT, он выбирается первым.Пожалуйста, укажите EXPLAIN SELECT, чтобы подтвердить это.

Эта формулировка может быть быстрее:

select  *,
        MATCH (r.name) AGAINST ('Old Tra*' IN BOOLEAN MODE) AS relevance_score
    from  `app_records` AS r
    WHERE MATCH (r.name) AGAINST ('Old Tra*' IN BOOLEAN MODE)
      AND EXISTS ( SELECT 1
              FROM app_group_records AS gr
              JOIN app_record_parents AS rp  ON rp.parent_id = gr.record_id
              WHERE gr.group_id = 3
                AND r.id = rp.record_id )
    ORDER BY relevance_score DESC
    LIMIT 10

Индексы:

gr:  (group_id, record_id)  -- in this order
r:   nothing but the FULLTEXT will be used
rp:  (record_id, parent_id)  -- in this order
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...