10.2.1.2 Оптимизация диапазонов
Метод доступа range использует один индекс для извлечения подмножества строк таблицы, которые содержатся в одном или нескольких интервалах значений индекса. Он может использоваться для индексов с одной частью или несколькими частями. В следующих разделах описаны условия, при которых оптимизатор использует доступ по диапазонам.
Метод доступа по диапазонам для индексов с одной частью
Для индекса с одной частью интервалы значений индекса удобно представлять соответствующими условиями в WHERE запроса, обозначаемыми как условия диапазона, а не “интервалами”.
Определение условия диапазона для индекса с одной частью следующее:
Для индексов
BTREEиHASHсравнение части ключа со значением константы является условием диапазона при использовании операторов=,<=>,IN(),IS NULLилиIS NOT NULL.Кроме того, для индексов
BTREEсравнение части ключа со значением константы является условием диапазона при использовании операторов>,<,>=,<=,BETWEEN,!=или<>, или сравненийLIKE, если аргументом кLIKEявляется строка-константа, которая не начинается с символа подстановки.Для всех типов индексов несколько условий диапазона, объединённых с помощью
ORилиAND, образуют условие диапазона.
“Значение константы” в предыдущих описаниях означает одно из следующего:
Ниже приведены примеры запросов с условиями диапазона в WHERE запроса:
SELECT * FROM t1
WHERE key_col > 1
AND key_col < 10;
SELECT * FROM t1
WHERE key_col = 1
OR key_col IN (15,18,20);
SELECT * FROM t1
WHERE key_col LIKE 'ab%'
OR key_col BETWEEN 'bar' AND 'foo';
Некоторые значения, не являющиеся константами, могут быть преобразованы в константы на стадии распространения констант оптимизатора.
MySQL пытается извлечь условия диапазона из WHERE запроса для каждого из возможных индексов. В процессе извлечения условия, которые не могут быть использованы для построения условия диапазона, отбрасываются, условия, которые дают перекрывающиеся диапазоны, объединяются, а условия, которые дают пустые диапазоны, удаляются.
Рассмотрим следующее утверждение, где key1 — это индексированный столбец, а nonkey — нет:
SELECT * FROM t1 WHERE
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z');
Процесс извлечения для ключа key1 следующий:
-
Начинаем с исходного
WHEREзапроса:(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR (key1 < 'bar' AND nonkey = 4) OR (key1 < 'uux' AND key1 > 'z')
-
Удаляем
nonkey = 4иkey1 LIKE '%b', так как они не могут быть использованы для сканирования по диапазону. Правильный способ их удаления — заменить их наTRUE, чтобы не пропустить ни одной совпадающей строки при сканировании по диапазону. Заменяя их наTRUE, получаем:(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR (key1 < 'bar' AND TRUE) OR (key1 < 'uux' AND key1 > 'z')
-
Сжимаем условия, которые всегда истинны или ложны:
(key1 LIKE 'abcde%' OR TRUE)всегда истинно(key1 < 'uux' AND key1 > 'z')всегда ложно
Замена этих условий константами даёт:
(key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)
Удаление ненужных
TRUEиFALSEконстант даёт:(key1 < 'abc') OR (key1 < 'bar')
-
Объединение перекрывающихся интервалов в один даёт окончательное условие, которое будет использоваться для сканирования по диапазону:
(key1 < 'bar')
В общем случае (и как показано в предыдущем примере), условие, используемое для сканирования по диапазону, менее строгое, чем WHERE запрос. MySQL выполняет дополнительную проверку, чтобы отфильтровать строки, удовлетворяющие условию диапазона, но не всему WHERE запросу.
Алгоритм извлечения условия диапазона может обрабатывать вложенные конструкции AND/OR произвольной глубины, и его результат не зависит от порядка, в котором условия появляются в WHERE запросе.
MySQL не поддерживает слияние нескольких диапазонов для метода доступа range для пространственных индексов. Чтобы обойти это ограничение, можно использовать UNION с идентичными SELECT операторами, за исключением того, что каждое пространственное условие вы помещаете в различные SELECT.
Метод доступа к диапазону для индексов из нескольких частей
Условие диапазона для индекса из нескольких частей является расширением условия диапазона для однокомпонентного индекса. Условие диапазона для индекса из нескольких частей ограничивает строки индекса, которые находятся в пределах одного или нескольких интервалов кортежей ключей. Интервалы кортежей ключей определяются над набором кортежей ключей с использованием упорядочивания из индекса.
Например, рассмотрим индекс из нескольких частей, определенный как key1(, и следующий набор кортежей ключей, упорядоченных по ключу:key_part1,
key_part2,
key_part3)
key_part1 key_part2 key_part3
NULL 1 'abc'
NULL 1 'xyz'
NULL 2 'foo'
1 1 'abc'
1 1 'xyz'
1 2 'abc'
2 1 'aaa'
Условие определяет этот интервал:key_part1
= 1
(1,-inf,-inf) <= (key_part1,key_part2,key_part3) < (1,+inf,+inf)
Интервал охватывает 4-й, 5-й и 6-й кортежи в предыдущем наборе данных и может использоваться методом доступа к диапазону.
В противоположность этому, условие не определяет единственный интервал и не может использоваться методом доступа к диапазону.key_part3 =
'abc'
Следующие описания более подробно показывают, как работают условия диапазона для индексов из нескольких частей.
-
Для индексов
HASHкаждый интервал, содержащий одинаковые значения, может быть использован. Это означает, что интервал может быть получен только для условий в следующей форме:key_part1cmpconst1ANDkey_part2cmpconst2AND ... ANDkey_partNcmpconstN;Здесь
const1,const2, … — константы,cmp— один из=,<=>илиIS NULLоператоров сравнения, и условия охватывают все части индекса. (То есть, естьNусловий, по одному для каждой части индекса изNчастей.) Например, следующее является условием диапазона для трёхкомпонентного индексаHASH:key_part1= 1 ANDkey_part2IS NULL ANDkey_part3= 'foo'Для определения того, что считается константой, см. Метод доступа к диапазону для однокомпонентных индексов.
-
Для индекса
BTREEинтервал может быть пригоден для условий, объединённых сAND, где каждое условие сравнивает часть ключа со значением константы, используя=,<=>,IS NULL,>,<,>=,<=,!=,<>,BETWEENилиLIKE '(гдеpattern''не начинается с подстановки). Интервал может быть использован, если возможно определить единственный кортеж ключа, содержащий все строки, которые соответствуют условию (или два интервала, если используетсяpattern'<>или!=).Оптимизатор пытается использовать дополнительные части ключа для определения интервала, до тех пор, пока оператор сравнения не является
=,<=>илиIS NULL. Если оператор —>,<,>=,<=,!=,<>,BETWEENилиLIKE, оптимизатор его использует, но не рассматривает больше частей ключа. Для следующего выражения оптимизатор использует=из первого сравнения. Он также использует>=из второго сравнения, но не рассматривает больше частей ключа и не использует третье сравнение для построения интервала:key_part1= 'foo' ANDkey_part2>= 10 ANDkey_part3> 10Единственный интервал:
('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf)Возможно, что созданный интервал содержит больше строк, чем исходное условие. Например, предыдущий интервал включает значение
('foo', 11, 0), которое не удовлетворяет исходному условию. -
Если условия, охватывающие наборы строк, содержащихся в интервалах, объединены с
OR, они образуют условие, охватывающее набор строк, содержащихся в объединении их интервалов. Если условия объединены сAND, они образуют условие, охватывающее набор строк, содержащихся в пересечении их интервалов. Например, для этого условия для индекса из двух частей:(
key_part1= 1 ANDkey_part2< 2) OR (key_part1> 5)Интервалы:
(1,-inf) < (
key_part1,key_part2) < (1,2) (5,-inf) < (key_part1,key_part2)В этом примере интервал на первой строке использует одну часть ключа для левой границы и две части ключа для правой границы. Интервал на второй строке использует только одну часть ключа. Столбец
key_lenв выводеEXPLAINуказывает максимальную длину префикса ключа, используемого.В некоторых случаях
key_lenможет указывать, что была использована часть ключа, но это может быть не так, как вы ожидаете. Предположим, чтоkey_part1иkey_part2могут бытьNULL. Тогда столбецkey_lenотображает две длины частей ключа для следующего условия:key_part1>= 1 ANDkey_part2< 2Но на самом деле условие преобразуется в это:
key_part1>= 1 ANDkey_part2IS NOT NULL
Для описания того, как выполняются оптимизации для объединения или устранения интервалов для условий диапазона для однокомпонентного индекса, см. Метод доступа к диапазону для однокомпонентных индексов. Аналогичные шаги выполняются для условий диапазона для индексов из нескольких частей.
Оптимизация диапазонов равенства для многозначных сравнений
Рассмотрим эти выражения, где col_name - индексированный столбец:
col_name IN(val1, ..., valN)
col_name = val1 OR ... OR col_name = valN
Каждое выражение истинно, если col_name равно одному из нескольких значений. Эти сравнения являются сравнениями диапазона равенства (где «диапазон» — это одно значение). Оптимизатор оценивает стоимость чтения соответствующих строк для сравнений диапазона равенства следующим образом:
Если существует уникальный индекс на
col_name, оценка строк для каждого диапазона равна 1, так как не более одной строки может иметь данное значение.В противном случае любой индекс на
col_nameявляется неуникальным, и оптимизатор может оценить количество строк для каждого диапазона, используя погружения в индекс или статистику индекса.
При использовании погружений в индекс оптимизатор производит погружение в каждом конце диапазона и использует количество строк в диапазоне как оценку. Например, выражение содержит три диапазона равенства, и оптимизатор производит два погружения на каждый диапазон для получения оценки строк. Каждая пара погружений даёт оценку количества строк, имеющих данное значение.col_name IN (10, 20,
30)
Погружения в индекс обеспечивают точные оценки строк, но по мере увеличения количества значений сравнения в выражении оптимизатор тратит больше времени на получение оценки строк. Использование статистики индекса менее точно, чем погружения в индекс, но позволяет быстрее оценивать строки для больших списков значений.
Системная переменная eq_range_index_dive_limit позволяет настроить количество значений, при которых оптимизатор переключается с одной стратегии оценки строк на другую. Чтобы разрешить использование погружений в индекс для сравнений до N диапазонов равенства, установите eq_range_index_dive_limit на N + 1. Чтобы отключить использование статистики и всегда использовать погружения в индекс независимо от N, установите eq_range_index_dive_limit на 0.
Для обновления статистики индексов таблицы для лучших оценок используйте ANALYZE TABLE.
До MySQL 8.4 нет способа пропустить использование погружений в индекс для оценки полезности индекса, кроме использования системной переменной eq_range_index_dive_limit. В MySQL 8.4 пропуск погружений в индекс возможен для запросов, удовлетворяющих всем этим условиям:
Запрос выполняется для одной таблицы, а не для соединения по нескольким таблицам.
Присутствует подсказка индекса single-index
FORCE INDEX. Идея в том, что если использование индекса принудительно, дополнительная нагрузка от выполнения погружений в индекс не принесёт пользы.Индекс является неуникальным и не является индексом
FULLTEXT.Подзапрос отсутствует.
Нет клаузы
DISTINCT,GROUP BYилиORDER BY.
Для EXPLAIN FOR
CONNECTION вывод меняется следующим образом, если погружения в индекс пропускаются:
Для традиционного вывода значения
rowsиfilteredравныNULL.Для вывода в формате JSON
rows_examined_per_scanиrows_produced_per_joinне отображаются,skip_index_dive_due_to_forceравноtrue, и вычисления стоимости не точны.
Без FOR CONNECTION вывод EXPLAIN не меняется, когда погружения в индекс пропускаются.
После выполнения запроса, для которого погружения в индекс пропускаются, соответствующая строка в таблице Информационной схемы OPTIMIZER_TRACE содержит значение index_dives_for_range_access равное skipped_due_to_force_index.
Метод доступа к диапазону Skip Scan
Рассмотрим следующий сценарий:
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
INSERT INTO t1 VALUES
(1,1), (1,2), (1,3), (1,4), (1,5),
(2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;
ANALYZE TABLE t1;
EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;
Для выполнения этого запроса MySQL может выбрать сканирование индекса для извлечения всех строк (индекс включает все столбцы, которые нужно выбрать), а затем применить условие f2 > 40 из клаузы WHERE для получения конечного набора результатов.
Сканирование диапазона более эффективно, чем полное сканирование индекса, но в этом случае не может быть использовано, так как нет условия для f1, первого столбца индекса. Оптимизатор может выполнить несколько сканирований диапазонов, по одному для каждого значения f1, используя метод под названием Skip Scan, аналогичный Loose Index Scan (см. Раздел 10.2.1.17, «Оптимизация GROUP BY»):
Пропустить между различными значениями первой части индекса,
f1(префикс индекса).Выполнить сканирование поддиапазона для каждого уникального префиксного значения для условия
f2 > 40в оставшейся части индекса.
Для набора данных, показанного ранее, алгоритм работает следующим образом:
Получить первое уникальное значение первой части ключа (
f1 = 1).Построить диапазон, основанный на первой и второй частях ключа (
f1 = 1 AND f2 > 40).Выполнить сканирование диапазона.
Получить следующее уникальное значение первой части ключа (
f1 = 2).Построить диапазон, основанный на первой и второй частях ключа (
f1 = 2 AND f2 > 40).Выполнить сканирование диапазона.
Использование этой стратегии уменьшает количество обращённых строк, потому что MySQL пропускает строки, которые не соответствуют каждому построенному диапазону. Этот метод доступа Skip Scan применим при следующих условиях:
Таблица T имеет по крайней мере один составной индекс с частями ключа в форме ([A_1, ..., A_
k,] B_1, ..., B_m, C [, D_1, ..., D_n]). Части ключа A и D могут быть пустыми, но B и C должны быть непустыми.Запрос ссылается только на одну таблицу.
Запрос не использует
GROUP BYилиDISTINCT.Запрос ссылается только на столбцы в индексе.
Предикаты на A_1, ..., A_
kдолжны быть предикатами равенства и должны быть константами. Это включает операторIN().Запрос должен быть конъюнктивным запросом; то есть,
ANDизORусловий:(cond1(key_part1) ORcond2(key_part1)) AND (cond1(key_part2) OR ...) AND ...Должно быть условие диапазона на столбце C.
Условиям на столбцах D разрешено. Условия на столбцах D должны быть в союзе с условием диапазона на столбце C.
Использование Skip Scan показано в выводе EXPLAIN следующим образом:
Using index for skip scanв столбцеExtraуказывает, что используется метод доступа Loose Index Skip Scan.Если индекс может быть использован для Skip Scan, он должен быть виден в столбце
possible_keys.
Использование Skip Scan показано в выводе трассировки оптимизатора элементом "skip scan" следующего формата:
"skip_scan_range": {
"type": "skip_scan",
"index": index_used_for_skip_scan,
"key_parts_used_for_access": [key_parts_used_for_access],
"range": [range]
}
Вы также можете увидеть элемент "best_skip_scan_summary". Если Skip Scan выбран в качестве лучшего варианта доступа диапазона, записывается "chosen_range_access_summary". Если Skip Scan выбран в качестве наилучшего метода доступа в целом, присутствует элемент "best_access_path".
Использование Skip Scan зависит от значения флага skip_scan системной переменной optimizer_switch. По умолчанию этот флаг равен on. Чтобы его отключить, установите skip_scan на off.
Помимо использования системной переменной optimizer_switch для управления использованием Skip Scan оптимизатором на уровне сеанса, MySQL поддерживает подсказки оптимизатора, чтобы повлиять на оптимизатор на уровне каждого оператора. См. Раздел 10.9.3, «Подсказки оптимизатора».
Оптимизация диапазона для выражений конструкторов строк
Оптимизатор может применять метод доступа по диапазону к запросам такого вида:
SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' ));
Ранее для использования сканирования по диапазону необходимо было писать запрос следующим образом:
SELECT ... FROM t1 WHERE ( col_1 = 'a' AND col_2 = 'b' )
OR ( col_1 = 'c' AND col_2 = 'd' );
Для использования сканирования по диапазону запросы должны удовлетворять этим условиям:
В левой части предиката
IN()конструктор строки содержит только ссылки на столбцы.В правой части предиката
IN()конструкторы строк содержат только константы выполнения, которые являются либо литералами, либо ссылками на локальные столбцы, привязанные к константам во время выполнения.В правой части предиката
IN()содержится более одного конструктора строки.
Дополнительную информацию об оптимизаторе и конструкторах строк см. в разделе 10.2.1.22 «Оптимизация выражений конструкторов строк».
Ограничение использования памяти для оптимизации диапазона
Для управления памятью, доступной оптимизатору диапазона, используйте системную переменную range_optimizer_max_mem_size:
Значение 0 означает “нет ограничений.”
-
При значении больше 0 оптимизатор отслеживает потребление памяти при рассмотрении метода доступа по диапазону. Если указанное ограничение приближается к пределу, метод доступа по диапазону отменяется, и рассматриваются другие методы, включая полное сканирование таблицы. Это может быть менее оптимальным. В этом случае появляется следующее предупреждение (где
N— текущее значениеrange_optimizer_max_mem_size):Warning 3170 Memory capacity of
Nbytes for 'range_optimizer_max_mem_size' exceeded. Range optimization was not done for this query. Для операторов
UPDATEиDELETE, если оптимизатор возвращается к полному сканированию таблицы, и системная переменнаяsql_safe_updatesвключена, возникает ошибка, а не предупреждение, потому что фактически не используется ключ для определения строк, которые нужно изменить. Дополнительную информацию см. в разделе о режиме безопасных обновлений (--safe-updates).
Для отдельных запросов, которые превышают доступную память для оптимизации диапазона и для которых оптимизатор переходит к менее оптимальным планам, увеличение значения range_optimizer_max_mem_size может улучшить производительность.
Для оценки необходимого объёма памяти для обработки выражения диапазона используйте следующие рекомендации:
-
Для простого запроса, такого как приведенный ниже, где существует один кандидатный ключ для метода доступа по диапазону, каждое предикат, объединённое с
OR, использует приблизительно 230 байт:SELECT COUNT(*) FROM t WHERE a=1 OR a=2 OR a=3 OR .. . a=
N; -
Аналогично для запроса, подобного приведённому ниже, каждое предикат, объединённое с
AND, использует приблизительно 125 байт:SELECT COUNT(*) FROM t WHERE a=1 AND b=1 AND c=1 ...
N; -
Для запроса с предикатами
IN():SELECT COUNT(*) FROM t WHERE a IN (1,2, ...,
M) AND b IN (1,2, ...,N);Каждое литеравльное значение в списке
IN()считается предикатом, объединённым сOR. Если есть два спискаIN(), количество предикатов, объединённых сOR, равно произведению количества литеравльных значений в каждом списке. Таким образом, количество предикатов, объединённых сORв предыдущем случае, равноM×N.
© 2025 Oracle
Licensed under the GPLv2 License.