У меня есть запрос, который иногда выполняется очень быстро, а иногда невероятно медленно, в зависимости от количества результатов, которые соответствуют полнотекстовому булевому поиску в запросе.
Запрос также содержит подзапрос.
Без подзапроса основной запрос всегда быстрый.
Сам подзапрос также всегда быстрый.
Но вместе они очень медленные.
Удаление полного текстапоиск из предложения 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;