Подсказки для индекса: Как принудительно задать планы запросов
Оптимизатор в основном основан на стоимости и будет пытаться выбрать оптимальный план для любого запроса. Однако в некоторых случаях у него недостаточно информации для выбора идеального плана, и в таких случаях вам может потребоваться предоставить подсказки, чтобы принудить оптимизатор использовать другой план.
Вы можете изучить план запроса для SELECT, написав EXPLAIN перед оператором. SHOW EXPLAIN отображает вывод выполняемого запроса. В некоторых случаях его вывод может быть ближе к реальности, чем EXPLAIN.
Для следующих запросов мы будем использовать базу данных world в качестве примеров.
Настройка примера базы данных World
Загрузите её с ftp://ftp.askmonty.org/public/world.sql.gz
Установите её с помощью:
mariadb-admin create world zcat world.sql.gz | ../client/mysql world
или
mariadb-admin create world gunzip world.sql.gz ../client/mysql world < world.sql
Принудительное задание порядка соединения
Вы можете принудительно задать порядок соединения, используя STRAIGHT_JOIN либо в части SELECT, либо в части JOIN.
Самый простой способ принудительного задания порядка соединения — это расположить таблицы в правильном порядке в FROM-клаузе и использовать SELECT STRAIGHT_JOIN следующим образом:
SELECT STRAIGHT_JOIN SUM(City.Population) FROM Country,City WHERE City.CountryCode=Country.Code AND Country.HeadOfState="Volodymyr Zelenskyy";
Если вы хотите принудительно задать порядок соединения только для нескольких таблиц, используйте STRAIGHT_JOIN в FROM-клаузе. В этом случае порядок будут заданы только для таблиц, связанных с STRAIGHT_JOIN. Например:
SELECT SUM(City.Population) FROM Country STRAIGHT_JOIN City WHERE City.CountryCode=Country.Code AND Country.HeadOfState="Volodymyr Zelenskyy";
В обоих случаях Country будет сканирована первой, а для каждой соответствующей страны (в данном случае одна) будут проверяться все строки в City на соответствие. Поскольку соответствующих стран всего одна, это будет быстрее, чем исходный запрос.
Вывод EXPLAIN для указанных случаев:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | Country | ALL | PRIMARY | NULL | NULL | NULL | 239 | Using where |
| 1 | SIMPLE | City | ALL | NULL | NULL | NULL | NULL | 4079 | Using where; Using join buffer (flat, BNL join) |
Это один из немногих случаев, когда ALL приемлем, так как сканирование таблицы Country найдёт только одну соответствующую строку.
Принудительное использование определенного индекса для условия WHERE
В некоторых случаях оптимизатор может выбрать неэффективный индекс или вообще не использовать индекс, даже если теоретически какой-либо индекс мог бы быть использован.
В таких случаях у вас есть возможность либо указать оптимизатору использовать только ограниченный набор индексов, проигнорировать один или несколько индексов, либо принудительно использовать определенный индекс.
USE INDEX: Использование ограниченного набора индексов
Вы можете ограничить набор рассматриваемых индексов с помощью опции USE INDEX.
USE INDEX [{FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
По умолчанию используется 'FOR JOIN', что означает, что подсказка влияет только на оптимизацию WHERE-клаузы.
USE INDEX используется после имени таблицы в FROM-клаузе.
Пример:
CREATE INDEX Name ON City (Name); CREATE INDEX CountryCode ON City (Countrycode); EXPLAIN SELECT Name FROM City USE INDEX (CountryCode) WHERE name="Helsingborg" AND countrycode="SWE";
Это даст:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | City | ref | CountryCode | CountryCode | 3 | const | 14 | Using where |
Если бы мы не использовали USE INDEX, индекс Name был бы в possible keys.
IGNORE INDEX: Не использовать определенный индекс
Вы можете указать оптимизатору не учитывать определенный индекс с помощью опции IGNORE INDEX.
IGNORE INDEX [{FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
Это используется после имени таблицы в FROM-клаузе:
CREATE INDEX Name ON City (Name); CREATE INDEX CountryCode ON City (Countrycode); EXPLAIN SELECT Name FROM City IGNORE INDEX (Name) WHERE name="Helsingborg" AND countrycode="SWE";
Это даст:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | City | ref | CountryCode | CountryCode | 3 | const | 14 | Using where |
Преимущества использования IGNORE_INDEX вместо USE_INDEX в том, что это не отключит новый индекс, который вы можете добавить позже.
Также см. Игнорируемые индексы для возможности указать в определении индекса, что индексы должны игнорироваться.
FORCE INDEX: Принудительное использование индекса
Принудительное использование индекса в основном полезно, когда оптимизатор решает выполнить сканирование таблицы, даже если известно, что использование индекса было бы лучше. (Оптимизатор может решить выполнить сканирование таблицы, даже если доступен индекс, когда он считает, что большинство или все строки будут соответствовать, и он может избежать накладных расходов на использование индекса).
CREATE INDEX Name ON City (Name); EXPLAIN SELECT Name,CountryCode FROM City FORCE INDEX (Name) WHERE name>="A" and CountryCode >="A";
Это даст:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | City | range | Name | Name | 35 | NULL | 4079 | Using where |
FORCE_INDEX работает, рассматривая только указанные индексы (как с USE_INDEX), но дополнительно сообщает оптимизатору считать сканирование таблицы очень дорогостоящим. Однако, если ни один из «принудительных» индексов не может быть использован, сканирование таблицы всё равно будет выполнено.
Префиксы индексов
При использовании подсказок индексов (USE, FORCE или IGNORE INDEX), имя индекса может также быть недвусмысленным префиксом имени индекса.
Принудительное использование индекса для ORDER BY или GROUP BY
Оптимизатор будет пытаться использовать индексы для разрешения ORDER BY и GROUP BY.
Вы можете использовать USE INDEX, IGNORE INDEX и FORCE INDEX, как в WHERE-клаузе выше, чтобы гарантировать, что используется определённый индекс:
USE INDEX [{FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
Это используется после имени таблицы в FROM-клаузе.
Пример:
CREATE INDEX Name ON City (Name); EXPLAIN SELECT Name,Count(*) FROM City FORCE INDEX FOR GROUP BY (Name) WHERE population >= 10000000 GROUP BY Name;
Это даст:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | City | index | NULL | Name | 35 | NULL | 4079 | Using where |
Без опции FORCE INDEX в столбце «Extra» было бы 'Using where; Using temporary; Using filesort', что означает, что оптимизатор создал бы временную таблицу и отсортировал бы её.
Помощь оптимизатору в оптимизации GROUP BY и ORDER BY
Оптимизатор использует несколько стратегий для оптимизации GROUP BY и ORDER BY:
- Разрешение с помощью индекса:
- Filesort:
- Сканирование таблицы для сортировки и сбор ключей сортировки во временный файл.
- Сортировка ключей + ссылка на строку (с filesort)
- Сканирование таблицы в отсортированном порядке
- Использование временной таблицы для ORDER BY:
- Создание временной (в памяти) таблицы для данных, которые нужно отсортировать. (Если она становится больше, чем
max_heap_table_sizeили содержит BLOB-данные, используется дисковая таблица Aria или MyISAM) - Сортировка ключей + ссылка на строку (с filesort)
- Сканирование таблицы в отсортированном порядке
- Создание временной (в памяти) таблицы для данных, которые нужно отсортировать. (Если она становится больше, чем
Временная таблица всегда будет использоваться, если поля, которые будут отсортированы, не из первой таблицы в порядке JOIN.
- Использование временной таблицы для GROUP BY:
- Создание временной таблицы для хранения результатов GROUP BY с индексом, соответствующим полям GROUP BY.
- Вывод строки результата
- Если в временной таблице существует строка с ключом GROUP BY, добавьте новую строку результата в неё. Если нет, создайте новую строку.
- Перед отправкой результатов пользователю отсортируйте строки с filesort, чтобы получить результаты в порядке GROUP BY.
Принудительное использование/отключение временных таблиц для GROUP BY:
Использование таблицы в памяти (как описано выше) обычно является самым быстрым вариантом для GROUP BY, если результат небольшой. Это не оптимально, если результат очень большой. Вы можете сообщить об этом оптимизатору, используя SELECT SQL_SMALL_RESULT или SELECT SQL_BIG_RESULT.
Например:
EXPLAIN SELECT SQL_SMALL_RESULT Name,Count(*) AS Cities FROM City GROUP BY Name HAVING Cities > 2;
даёт:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | City | ALL | NULL | NULL | NULL | NULL | 4079 | Using temporary; Using filesort |
в то время как:
EXPLAIN SELECT SQL_BIG_RESULT Name,Count(*) AS Cities FROM City GROUP BY Name HAVING Cities > 2;
даёт:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | City | ALL | NULL | NULL | NULL | NULL | 4079 | Using filesort |
Разница заключается в том, что с SQL_SMALL_RESULT используется временная таблица.
Принудительное использование временных таблиц
В некоторых случаях вы можете захотеть принудительно использовать временную таблицу для результата, чтобы как можно быстрее освободить блокировки таблицы/строки для используемых таблиц.
Это можно сделать с помощью опции SQL_BUFFER_RESULT:
CREATE INDEX Name ON City (Name); EXPLAIN SELECT SQL_BUFFER_RESULT Name,Count(*) AS Cities FROM City GROUP BY Name HAVING Cities > 2;
Это дает:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | City | index | NULL | Name | 35 | NULL | 4079 | Использование индекса; Использование временной таблицы |
Без SQL_BUFFER_RESULT, вышеуказанный запрос не использовал бы временную таблицу для набора результатов.
Переключатель оптимизатора
В MariaDB 5.3 мы добавили переключатель оптимизатора, который позволяет указать, какие алгоритмы будут рассматриваться при оптимизации запроса.
Дополнительную информацию о различных используемых алгоритмах см. в разделе оптимизатор.
См. также
© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/index-hints-how-to-force-query-plans/