Во-первых, я признаю, что мой опыт работы с пространственными функциями очень минимален. У меня есть таблица в MySQL с 20 полями и 23549187 записей, которые содержат географические данные. Одно из полей — это точка, которая имеет тип данных точки и имеет пространственный индекс. У меня есть запрос, который выбирает все точки в многоугольнике, который выглядит так,
select * from `table_name` where ST_CONTAINS(ST_GEOMFROMTEXT('POLYGON((151.186 -23.497,151.207 -23.505,151.178 -23.496,151.174 -23.49800000000001,151.176 -23.496,151.179 -23.49500000000002,151.186 -23.497))'), `point`)
Это хорошо работает, так как многоугольник маленький. Однако, если многоугольник становится массивным, время выполнения становится очень медленным, и самый медленный запрос до сих пор выполнялся в течение 15 минут. Добавление индекса действительно помогло снизить его до 15 минут, которые в противном случае заняли бы около часа. Есть ли что-нибудь, что я могу сделать здесь для дальнейшего улучшения.
Этот запрос будет выполняться PHP-скриптом, который запускается как демон, и я беспокоюсь, не остановит ли этот медленный запрос сервер MySQL.
Все предложения, чтобы сделать это лучше, приветствуются. Благодарю.
РЕДАКТИРОВАТЬ:
show create table;
CREATE TABLE `table_name` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`lat` float(12,6) DEFAULT NULL,
`long` float(12,6) DEFAULT NULL,
`point` point NOT NULL,
PRIMARY KEY (`id`),
KEY `lat` (`lat`,`long`),
SPATIAL KEY `sp_index` (`point`)
) ENGINE=MyISAM AUTO_INCREMENT=47222773 DEFAULT CHARSET=utf8mb4
Есть еще несколько полей, которые я не должен раскрывать здесь, однако фильтр выиграл
Объясните вывод sql для медленного запроса:
+----+-------------+------------+------+---------------+------+---------+------+----------+-------------+ | id | select_type | стол | тип | возможные_ключи | ключ | key_len | ref | строки | Extra | + ---- + ------------- + ------------ + ------ + ---------- ----- + ------ + --------- + ------ + ---------- + --------- ---- + | 1 | ПРОСТО | имя_таблицы | ВСЕ | NULL | NULL | NULL | NULL | 23549187 | Используя где | + ---- + ------------- + ------------ + ------ + ---------- ----- + ------ + --------- + ------ + ---------- + --------- ---- +
Объясните вывод sql для запроса с меньшими полигонами,
+----+-------------+------------+-------+---------------+----------+---------+------+------+-------------+ | id | select_type | стол | тип | возможные_ключи | ключ | key_len | ref | строки | Extra | + ---- + ------------- + ------------ + ------- + --------- ------ + ---------- + --------- + ------ + ------ + -------- ----- + | 1 | ПРОСТО | имя_таблицы | диапазон | sp_index | sp_index | 34 | NULL | 1 | Используя где | + ---- + ------------- + ------------ + ------- + --------- ------ + ---------- + --------- + ------ + ------ + -------- ----- +
Похоже, самый большой полигон не использует индекс.
MySQL использует R-деревья для индексации пространственных данных. подобно B-Tree индексы, они оптимальны для запросов, направленных на небольшое подмножество общего числа. По мере того, как ваш ограничивающий полигон увеличивается, количество возможных совпадений увеличивается, и в какой-то момент оптимизатор решает, что более эффективно перейти к полному сканированию таблицы. Это похоже на сценарий здесь, и я вижу три варианта:
Сначала попробуйте добавить LIMIT
на ваш запрос. Обычно MySQL игнорирует индекс, если оптимизатор приходит к выводу, что при полном сканировании таблицы будет происходить меньше операций поиска ввода-вывода. Но, по крайней мере с индексами B-Tree, MySQL закоротит эту логику и всегда выполнит погружение B-Tree, когда LIMIT
настоящее. Я предполагаю, что R-Tree имеют аналогичное короткое замыкание.
Второе и похожее по духу на первое, попробуй заставляя MySQL использовать индекс. Это указывает MySQL, что сканирование таблицы обходится дороже, чем решит оптимизатор. Поймите, что оптимизатор обладает только эвристикой и не знает, насколько «дорогие» вещи выходят за рамки выводов его внутренней статистики. У нас, людей, есть интуиция, которая иногда — иногда — знает лучше
select * force index (`sp_index`) from `table_name` where ST_CONTAINS(ST_GEOMFROMTEXT('POLYGON((151.186 -23.497,151.207 -23.505,151.178 -23.496,151.174 -23.49800000000001,151.176 -23.496,151.179 -23.49500000000002,151.186 -23.497))'), `point`)
Наконец, если они не работают, то вам нужно разбить ограничивающий полигон на более мелкие. Например, если ограничивающим полигоном является квадрат 500 км с каждой стороны, разбейте его на 4 квадрата по 250 км с каждой стороны или 16 квадратов по 125 км с каждой стороны и т. Д. Затем UNION
все это вместе. Индекс будет использоваться для каждого, и совокупный результат может быть быстрее. (Обратите внимание, что важно UNION
они вместе: MySQL не может применить многократное сканирование диапазона к пространственному запросу.)
Других решений пока нет …