Плохой план выполнения MySQL

Мне нужна помощь в понимании вывода данных, объясняя очень похожие запросы и огромное влияние на производительность. У меня есть 2 таблицы: анонс и геолокализация. Первый содержит объявления об аренде, а второй - соответствующее местоположение. Итак, ищем аренду в заданном месте. Если я использую план по умолчанию

EXPLAIN
SELECT a.*, g.label AS geo_label, g.geo_url
FROM annonce a
INNER JOIN geolocalisation g ON a.geolocalisation_id = g.geolocalisation_id 
WHERE a.categorie_id = 1 AND g.gauche >= 151579 AND g.droite <= 151580 
AND couchage >= 2
ORDER BY FIELD(provenance_id, 2, 1), prix DESC, date_modification DESC, annonce_id ASC

У меня время выполнения более 10 секунд.

+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys                   | key          | key_len | ref                          | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ref    | geolocalisation_id,categorie_id | categorie_id | 4       | const                        | 502897 |    33.33 | Using index condition; Using where; Using filesort |
|  1 | SIMPLE      | g     | NULL       | eq_ref | PRIMARY,droite,gauche           | PRIMARY      | 4       | vacamax.a.geolocalisation_id |      1 |    25.00 | Using where                                        |
+----+-------------+-------+------------+--------+---------------------------------+--------------+---------+------------------------------+--------+----------+----------------------------------------------------+

Если я установлю индекс геолокации на "бестактный"

EXPLAIN
SELECT a.*, g.label AS geo_label, g.geo_url
FROM annonce a
INNER JOIN geolocalisation g ON a.geolocalisation_id = g.geolocalisation_id 
WHERE a.categorie_id = 1 AND g.gauche >= 151579 AND g.droite <= 151580 
AND couchage >= 2
ORDER BY FIELD(provenance_id, 2, 1), prix DESC, date_modification DESC, annonce_id ASC

У меня время выполнения .1 с.

+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys                   | key                | key_len | ref                          | rows  | filtered | Extra                                                               |
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+
|  1 | SIMPLE      | g     | NULL       | range | gauche                          | gauche             | 4       | NULL                         | 52785 |    33.33 | Using index condition; Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | a     | NULL       | ref   | geolocalisation_id,categorie_id | geolocalisation_id | 5       | vacamax.g.geolocalisation_id |    13 |    16.66 | Using where                                                         |
+----+-------------+-------+------------+-------+---------------------------------+--------------------+---------+------------------------------+-------+----------+---------------------------------------------------------------------+

Результат - 188 строк. Кажется, что в первом случае проверяется слишком много строк, но во втором случае фильтрация эффективна: геолокализация - это фильтр, который следует применять ДО соединения: 1) вы получаете места, удовлетворяющие условиям 2) вы находите арендуемые места с такими размещает geolocalisation_id, сопоставляя таблицы. Просвети меня, пожалуйста.

В какой таблице содержится couchage?

Rick James 22.09.2018 05:02

couchage находится в таблице annonce

MarvinLeRouge 24.09.2018 09:03

А как насчет INDEX(categorid_id, couchage)?

Rick James 24.09.2018 17:34
Освоение архитектуры микросервисов с 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
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
1
3
135
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ты знает, что фильтровать геолокацию разумнее до, чем после, потому что вы знаете кое-что о своих данных и своем запросе, чего не знает MySQL.

В частности, MySQL предполагает, что он должен просмотреть строки 502897*1 в первом запросе и строки 52785*13=686205 во втором запросе, и решает использовать первый. Есть и другие факторы, которые влияют на решение, какой план выполнения использовать, но он дает вам приблизительное представление о том, как MySQL думает, как выглядят ваши данные. Это далеко от реальности (188 строк), и неудивительно, что решение, основанное на таких неверных предположениях, привело к неудачной стратегии.

Фактически, даже я знаю это только потому, что вы сказали мне и теперь можете предположить, основываясь на именах столбцов, что gauche всегда меньше, чем droite, поэтому ваше условие на g, вероятно, описывает очень узкое окно. Но MySQL этого не знает, поскольку вы не сказали этого MySQL, поэтому он не может принять это во внимание. И, конечно же, он не может принимать решения на основании значений имен столбцов.

Поскольку у вас есть индекс на gauge, для высокого значения (например, g.gauge >= your_max_value_in_that_column) MySQL фактически должен быть в состоянии обнаружить, что существует только несколько строк, и должен использовать лучший план выполнения. В противном случае MySQL в основном невежественен. Попробуйте изменять размер окна в очень широком диапазоне (например, g.gauche >= 100000 AND g.droite <= 200000); MySQL не будет показывать существенно другое число в rows, если вы не приблизитесь к пределам своих столбцов (и не укажете на них индекс). Для некоторых диапазонов первый запрос действительно должен выполняться быстрее, поскольку он приближается к распределению данных, которое предполагает MySQL.

Итак, как вы можете сообщить MySQL о вашем распределении данных?

Возможно, можно будет закодировать вашу информацию как пространственные данные (точка) и указатель на ней. Затем вы можете искать точки, которые лежат в 2-м прямоугольнике, и теперь MySQL может понять, что на самом деле это очень маленький прямоугольник, содержащий ограниченный объем данных. Не обязательно, чтобы ваши данные были на самом деле геометрическими данными, просто вы можете закодировать их в двух измерениях.

Предполагая, что мое предположение верно, вы также можете использовать (g.gauche = 151579 or g.gauche = 151580), и MySQL также должен понимать, что это только ограниченный объем данных.

И вы, конечно, можете просто принудительно создать индекс (или использовать FROM geolocalisation g STRAIGHT_JOIN annonce a). Вы знаете что-то, чего не знает MySQL, и часто вы не можете сказать MySQL иначе. Недостатком является то, что он не может адаптироваться к другим ситуациям, например. если вы (иногда) используете в запросе окна большего размера, или gauche <= droite больше не соответствует действительности.

OK. Во-первых, гош всегда меньше droite (что по-французски означает «левый и правый»): географическая система - это дерево, корень которого является «миром», поэтому трюк «влево / вправо» позволяет выбрать любой географический регион и все его потомки. В примере это всего лишь город, поэтому только один узел с разницей влево и вправо, равной 1. Во-вторых, "gauche" и "droite" являются индексированными значениями. В-третьих, mysql оценивает сложность одного плана по сравнению с другим и выбирает самый дешевый. Но как это так ошибочно? geolocalisation_id - это первичный ключ таблицы g, так почему он начинается с таблицы a?

MarvinLeRouge 07.09.2018 13:27

Я понял, что это слева и справа, моя точка зрения заключалась в том, что MySQL этого не делает. Предположим, что идентификаторы идут от 1 до 1.000.000. Если вы скажете: gauge >= 100.000, MySQL может сказать: ну, наверное, 9/10 всех строк выше этого (на самом деле это не так точно, и гораздо лучше, если это уникальный индекс, поэтому вы должны использовать его, если возможно, это может улучшить вашу статистику), но вы можете получить yist. Если вы скажете droite <= 100.001, он может сказать: это может означать, что там находится около 1/10 всех строк. не мочь понимает, что это всего лишь 1 запись, потому что он не знает, что left < right.

Solarflare 07.09.2018 13:43

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