Эта глава объясняет, как оптимизировать работу MySQL и обеспечивает примеры. Оптимизация вовлекает конфигурирование, настройку и определение эксплуатационных качеств на нескольких уровнях. В зависимости от Вашей роли (разработчик, DBA или комбинация обоих), Вы могли бы оптимизировать на уровне отдельных запросов SQL, всех приложений, единственного сервера базы данных или многих сетевых серверов базы данных. Иногда Вы можете запланировать заранее работу в то время, как в других случаях Вы могли бы расследовать конфигурацию или кодировать проблему после того, как проблема происходит. Оптимизация центрального процессора и использования памяти может также улучшить масштабируемость, позволяя базе данных обработать большие нагрузки без замедления.
Работа базы данных зависит от нескольких факторов на уровне базы данных, таких как таблицы, запросы и настройки конфигурации. Это результат конструкций программного обеспечения в центральном процессоре и операциях ввода/вывода на уровне аппаратных средств, который Вы должны минимизировать и сделать настолько эффективным, насколько возможно. Поскольку Вы работаете над работой базы данных, Вы начинаете, изучая высокоуровневые правила и направляющие линии для стороны программного обеспечения, и измеряя работу, используя время. По мере того, как Вы становитесь экспертом, Вы узнаете больше о том, что происходит внутренне, и начинаете измерять вещи, такие как циклы центрального процессора и операции ввода/вывода.
Типичные пользователи стремятся вытаскивать лучшую работу базы данных из своих существующих конфигураций программного и аппаратного обеспечения. Усовершенствованные пользователи ищут возможности улучшить программное обеспечение MySQL непосредственно или развить их собственные механизмы хранения и аппаратные средства, чтобы расширить экосистему MySQL.
Наиболее важным фактором в создании приложения базы данных является базовая конструкция:
Таблицы структурированы должным образом? В частности, у столбцов есть правильные типы данных, и действительно каждая таблица имеет соответствующие столбцы для типа работы? Например, у приложений, которые выполняют частые обновления, есть много таблиц с малым числом столбцов в то время, как у приложений, которые анализируют большие объемы данных, есть немного таблиц со многими столбцами.
InnoDB
или MyISAM
может быть очень важным для работы и масштабируемости.
InnoDB
механизм хранения по умолчанию для новых таблиц.
Практически, усовершенствованные технические характеристики означают, что
таблицы InnoDB
часто выигрывают у более простого
MyISAM
, специально для занятой базы данных.
InnoDB
и MyISAM
только для чтения.InnoDB
обрабатывает
большинство проблем блокировки без Вашего участия, ведет учет лучшего
параллелизма в базе данных и сокращает количество экспериментирования и
настройки для Вашего кода.InnoDB
, кэш ключей MyISAM
и кэш запросов MySQL.
Любое приложение базы данных в конечном счете упирается в пределы аппаратных средств, поскольку база данных становится более занятой. DBA должен оценить, возможно ли настроить приложение или реконфигурировать сервер, чтобы избежать этих проблем или требуется больше ресурсов аппаратных средств. Системные узкие места, как правило, являются результатом этих источников:
Диск поиски. Это занимает время, чтобы найти часть данных. С современными дисками среднее время для этого обычно ниже 10 миллисекунд, таким образом, мы можем в теории делать, приблизительно 100 поисков в секунду. Это время медленно улучшается с новыми дисками и очень трудно оптимизировать для единственной таблицы. Способ оптимизировать время поиска, это распределить данные больше, чем на один диск.
Чтобы использовать ориентируемый на работу на расширения SQL в портируемой
программе MySQL, Вы можете обернуть MySQL-определенные ключевые слова в
запросе в пределах комментария /*! */
.
Другие SQL-серверы игнорируют прокомментированные ключевые слова.
Для информации об использовании комментариев см.
раздел 10.6.
Основная логика приложения базы данных выполнена через запросы SQL.
Запросы в форме SELECT
выполняют все операции поиска в базе данных. Настройка этих запросов
является высшим приоритетом.
Кроме того SELECT
также относятся к таким конструкциям, как
CREATE TABLE...AS SELECT
, INSERT INTO...SELECT
и
WHERE
в DELETE
.
У этих запросов есть дополнительные исполнительные соображения, потому что
они объединяют операции записи с ориентируемыми на чтение операциями запроса.
Основные соображения для того, чтобы оптимизировать запросы:
Чтобы ускорить SELECT ... WHERE
,
первая вещь, которую надо проверить, состоит в том, можете ли Вы добавить
индекс.
Настроенный индекс на столбцах, используемых в WHERE
, нужен,
чтобы ускорить оценку, фильтрацию и заключительное извлечение результатов.
Чтобы избежать потраченного впустую дискового пространства, создайте
маленький набор индексов, которые ускоряют много связанных запросов,
используемых в Вашем приложении.
Индексы особенно важны для запросов, которые обращаются к
различным таблицам, используя функции, такие как
joins и
внешние ключи.
Вы можете использовать EXPLAIN
,
чтобы определить, который индекс используется для
SELECT
. См. разделы
9.3.1 и
9.8.1.
ANALYZE TABLE
периодически, таким образом, оптимизатор получит информацию, чтобы создать
эффективный план выполнения.InnoDB
и MyISAM
имеют наборы
для включения и поддержки высокой производительности в запросах. Для деталей
см. разделы 9.5.6 и
9.6.1.InnoDB
, используя метод в
разделе 9.5.3.EXPLAIN
и корректируя Ваши
индекс, WHERE
, join и т.д. Когда Вы достигаете определенного
уровня экспертизы, чтение плана EXPLAIN
может быть Вашим первым шагом для каждого запроса.InnoDB
, кэша ключей MyISAM
и кэша запроса MySQL, повторенные
запросы работают быстрее, потому что результаты получены из
памяти в последующие разы.Этот раздел обсуждает оптимизацию, которая может быть сделана для того,
чтобы обработать WHERE
. Используем в качестве примера
SELECT
, но та же самая оптимизация
WHERE
работает в DELETE
и UPDATE
.
Поскольку работа над оптимизатором MySQL продолжается, не вся оптимизация, которую выполняет MySQL, зарегистрирована здесь.
Вы могли бы испытать желание переписать свои запросы, чтобы сделать арифметические операции быстрее, жертвуя удобочитаемостью. Поскольку MySQL делает подобную оптимизацию автоматически, Вы можете часто избегать этой работы, и оставлять запрос в более понятной форме. Часть оптимизации, выполненной MySQL:
Удаление ненужных круглых скобок:
((a AND b) AND c OR (((a AND b) AND (c AND d)))) -> (a AND b AND c) OR (a AND b AND c AND d)
Постоянное сворачивание:
(a<b AND b=c) AND a=5 -> b>5 AND b=c AND a=5
Постоянное удаление условия (необходимо из-за постоянного сворачивания):
(B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6) -> B=5 OR B=6
Постоянные выражения, использующие индексы, оценены только однажды.
COUNT(*)
на единственной таблице без WHERE
получен непосредственно от информации о таблице для MyISAM
и
MEMORY
. Это также сделано для любого выражения NOT NULL
, когда используется только с одной таблицей.SELECT
невозможны и не возвращает строки.HAVING
слит с WHERE
,
если Вы не используете GROUP BY
или совокупные функции
(COUNT()
,
MIN()
и т.д.).WHERE
создан, чтобы получить быструю оценку WHERE
для таблицы и пропускать строки как можно скорее.Пустая таблица или таблица с одной строкой.
WHERE
в
PRIMARY KEY
или индексе UNIQUE
,
где все индексные части сравниваются с постоянными выражениями и определены
как NOT NULL
.Все следующие таблицы используются в качестве постоянных таблиц:
SELECT * FROM t WHEREprimary_key
=1; SELECT * FROM t1,t2 WHERE t1.primary_key
=1 AND t2.primary_key
=t1.id;
Лучшая комбинация соединения для того, чтобы присоединиться к таблицам
найдена, пробуя все возможности. Если все столбцы в
ORDER BY
и GROUP BY
прибывают из той же самой таблицы, та таблица предпочтена.
ORDER BY
и отличающийся
GROUP BY
, или если ORDER BY
или GROUP BY
содержит столбцы от таблиц кроме первой таблицы в очереди соединения,
временная таблица составлена.SQL_SMALL_RESULT
,
MySQL использует временную таблицу в памяти.HAVING
пропущены.Некоторые примеры запросов, которые очень быстры:
SELECT COUNT(*) FROMtbl_name
; SELECT MIN(key_part1
), MAX(key_part1
) FROMtbl_name
; SELECT MAX(key_part2
) FROMtbl_name
WHEREkey_part1
=constant
; SELECT ... FROMtbl_name
ORDER BYkey_part1
,key_part2
,... LIMIT 10; SELECT ... FROMtbl_name
ORDER BYkey_part1
DESC,key_part2
DESC, ... LIMIT 10;
MySQL решает следующие запросы, используя только индексное дерево, предполагая, что индексированные столбцы являются числовыми:
SELECTkey_part1
,key_part2
FROMtbl_name
WHEREkey_part1
=val
; SELECT COUNT(*) FROMtbl_name
WHEREkey_part1
=val1
ANDkey_part2
=val2
; SELECTkey_part2
FROMtbl_name
GROUP BYkey_part1
;
Следующая индексация использования запросов, чтобы получить строки в сортированном порядке без отдельного прохода сортировки:
SELECT ... FROMtbl_name
ORDER BYkey_part1
,key_part2
,... ; SELECT ... FROMtbl_name
ORDER BYkey_part1
DESC,key_part2
DESC, ... ;
Метод доступа диапазона
использует один индекс, чтобы получить подмножество строк
таблицы, которые содержатся в пределах одного или нескольких индексных
интервалов. Это может использоваться для единственной или нескольких частей
индекса. Следующие разделы дают описания условий, при которых оптимизатор
использует доступ диапазона.
Интервалы значения могут быть удобно представлены соответствующими
условиями в WHERE
, обозначенный как условия диапазона
вместо интервалов.
Определение условия диапазона для единственной части индекса:
Для индексов BTREE
и HASH
сравнение
части ключа с постоянной величиной условия диапазона сделано, используя
операторы =
,
<=>
,
IN()
,
IS NULL
или
IS NOT NULL
.
BTREE
сравнение
части ключа с постоянной величиной условия диапазона сделано, используя
операторы >
,
<
,
>=
,
<=
,
BETWEEN
,
!=
,
<>
или
LIKE
,
если параметр LIKE
постоянная строка, которая не начинается с подстановочного символа.OR
или
AND
.Постоянная величина в предыдущих описаниях означает одно из следующего:
Константа от строки запроса.
const
или system
из
того же самого соединения.Вот некоторые примеры запросов с условиями диапазона в WHERE
:
SELECT * FROM t1 WHEREkey_col
> 1 ANDkey_col
< 10; SELECT * FROM t1 WHEREkey_col
= 1 ORkey_col
IN (15,18,20); SELECT * FROM t1 WHEREkey_col
LIKE 'ab%' ORkey_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)
всегда true.
(key1 < 'uux' AND key1 > 'z')
всегда false.
Заменяя эти условия константами, получим:
(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 не поддерживает сливающиеся многократные диапазоны для
метода доступа диапазона
для пространственного индекса. Чтобы работать вокруг этого ограничения,
Вы можете использовать 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_part1
cmp
const1
ANDkey_part2
cmp
const2
AND ... ANDkey_partN
cmp
constN
;
Здесь const1
, const2
являются константами, cmp
один из операторов сравнения
=
,
<=>
или
IS NULL
, а
условия покрывают все индексные части. Таким образом, есть
N
условий, одно для каждой части индекса из
N
частей. Например, следующее условие диапазона для
трехчастного индекса HASH
:
key_part1
= 1 ANDkey_part2
IS NULL ANDkey_part3
= 'foo'
Для определения константы см. раздел 9.2.1.3.1.
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_part2
IS NOT NULL
Раздел 9.2.1.3.1 описывает, как оптимизация выполнена, чтобы объединить или устранить интервалы для условий диапазона на единственной части индекса. Аналогичные шаги выполнены для условий диапазона на многих частях индекса.
Рассмотрите эти выражения, где
col_name
индексированный столбец:
col_name
IN(val1
, ...,valN
)col_name
=val1
OR ... ORcol_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
.
Чтобы управлять памятью, доступной оптимизатору, используйте
range_optimizer_max_mem_size
:
0 значит отсутствие ограничений.
N
текущее значение
range_optimizer_max_mem_size
):
Warning 3170 Memory capacity of N
bytes for
'range_optimizer_max_mem_size' exceeded. Range
optimization was not done for this query.
Для отдельных запросов, которые превышают доступную память оптимизации
диапазона и для которого оптимизатор отступает к менее оптимальным планам,
увеличение
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
.
Оптимизатор в состоянии применить метод доступа просмотра диапазона для запросов этой формы:
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()
есть больше, чем один конструктор строки.См. раздел 9.2.1.20 .
Метод слияния индекса
используется, чтобы получить строки с несколькими
диапазонами
и слить их результаты в один. Слияние может произвести союзы, пересечения или
союзы пересечений его основных просмотров. Этот метод доступа слияния
индексирует просмотры от единственной таблицы,
это не сливает просмотры через многократные таблицы.
В выводе EXPLAIN
метод
слияния появляется как
index_merge
в столбце type
. В этом случае столбец
key
содержит список используемых индексов, а
key_len
содержит список самых длинных ключевых частей
для тех индексов.
Примеры:
SELECT * FROMtbl_name
WHEREkey1
= 10 ORkey2
= 20; SELECT * FROMtbl_name
WHERE (key1
= 10 ORkey2
= 20) ANDnon_key
=30; SELECT * FROM t1, t2 WHERE (t1.key1
IN (1,2) OR t1.key2
LIKE 'value
%') AND t2.key1
=t1.some_col
; SELECT * FROM t1, t2 WHERE t1.key1
=1 AND (t2.key1
=t1.some_col
OR t2.key2
=t1.some_col2
);
У метода слияния есть несколько алгоритмов доступа (отмечено в поле
Extra
вывода EXPLAIN
):
Using intersect(...)
Using union(...)
Using sort_union(...)
Следующие разделы описывают эти методы более подробно.
У алгоритма оптимизации Слияния есть следующие известные недостатки:
Если у Вашего запроса есть сложный WHERE
с глубоким
вложением AND
/
OR
и MySQL
не выбирает оптимальный план, пытается распределить сроки, используя
следующие законы об идентичности:
(x
ANDy
) ORz
= (x
ORz
) AND (y
ORz
) (x
ORy
) ANDz
= (x
ANDz
) OR (y
ANDz
)
Выбор между различными возможными разновидностями метода доступа слияния и других методов доступа основан на сметах различных доступных параметров.
Этот алгоритм доступа может использоваться, когда WHERE
был преобразован в несколько условий диапазона на различных ключах,
объединенных с AND
,
и каждое условие одно из следующего:
В этой форме, где индексирование имеет точно
N
частей (то есть, все индексные части покрыты):
key_part1
=const1
ANDkey_part2
=const2
... ANDkey_partN
=constN
InnoDB
.
Примеры:
SELECT * FROMinnodb_table
WHEREprimary_key
< 10 ANDkey_col1
=20; SELECT * FROMtbl_name
WHERE (key1_part1
=1 ANDkey1_part2
=2) ANDkey2
=2;
Перекрестный алгоритм слияния использует одновременные просмотры на всех используемых индексах и производит пересечение последовательностей строки, которые получает от слияния просмотров индекса.
Если все столбцы, используемые в запросе, покрыты используемым индексом,
полные строки таблицы не получены
(вывод EXPLAIN
включает
Using index
в поле Extra
).
Вот пример такого запроса:
SELECT COUNT(*) FROM t1 WHERE key1=1 AND key2=1;
Если используемый индекс не покрывают все столбцы, используемые в запросе, все строки получены только, когда условия диапазона для всех используемых ключей удовлетворены.
Если одно из слитых условий это условие по первичному ключу
InnoDB
, это не используется для извлечения строки, но
используется, чтобы отфильтровать полученные с использованием
других условий строки.
Критерии применимости для этого алгоритма подобны критериям для
перекрестного алгоритма. Алгоритм может использоваться когда
WHERE
был преобразован в несколько условий диапазона на
различных ключах, объединенных с OR
, и каждое условие одно из следующего:
В этой форме, где индексирование имеет точно
N
частей (то есть, все индексные части покрыты):
key_part1
=const1
ANDkey_part2
=const2
... ANDkey_partN
=constN
InnoDB
.Примеры:
SELECT * FROM t1 WHEREkey1
=1 ORkey2
=2 ORkey3
=3; SELECT * FROMinnodb_table
WHERE (key1
=1 ANDkey2
=2) OR (key3
='foo' ANDkey4
='bar') ANDkey5
=5;
Этот алгоритм доступа используется, когда WHERE
был преобразован в несколько условий диапазона, объединенных
OR
,
но для которого алгоритм союза неприменим.
Примеры:
SELECT * FROMtbl_name
WHEREkey_col1
< 10 ORkey_col2
< 20; SELECT * FROMtbl_name
WHERE (key_col1
> 10 ORkey_col2
= 20) ANDnonkey_col
=30;
Различие между алгоритмом сортировки союза и алгоритмом союза в том, что алгоритм сортировки союза должен сначала принести ID строки для всех строк и сортировать их прежде, чем возвратить любые строки.
Эта оптимизация улучшает эффективность прямых сравнений между
неиндексированным столбцом и константой. В таких случаях условие
передано механизму хранения для оценки. Эта
оптимизация может использоваться только
NDB
.
NDB
в настоящее время не доступен в MySQL 8.0.
Если Вы интересуетесь использованием MySQL Cluster, см.
MySQL Cluster NDB 7.5.
Для MySQL Cluster эта оптимизация может избавить от необходимости посылать несоответствие строк по сети между узлами данных кластера и MySQL Server, который выпустил запрос, и может ускорить запросы, где это используется, в 5-10 раз.
Предположите, что таблица MySQL Cluster определена следующим образом:
CREATE TABLE t1 (a INT, b INT, KEY(a)) ENGINE=NDB;
Условие может использоваться с такими запросами, как один показанный здесь, который включает сравнение между неиндексированным столбцом и константой:
SELECT a, b FROM t1 WHERE b = 10;
Использование условия может быть замечено в выводе
EXPLAIN
:
mysql> EXPLAIN SELECT a,b FROM t1 WHERE b = 10\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 10 Extra: Using where with pushed condition
Однако, условие не может использоваться ни с одним из этих двух запросов:
SELECT a,b FROM t1 WHERE a = 10; SELECT a,b FROM t1 WHERE b + 1 = 10;
Это неприменимо к первому запросу, потому что индексирование существует на
столбце a
. Метод доступа к индексу был бы более эффективным и
будет выбран. Это также не может использоваться и для второго запроса потому,
что сравнение, вовлекающее неиндексированный столбец b
является косвенным. Однако, условие могло быть применено, если Вы должны были
уменьшить b + 1 = 10
до b = 9
в WHERE
.
Условие может также использоваться, когда индексированный столбец сравнен
с постоянной с помощью оператора >
или <
:
mysql> EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: range possible_keys: a key: a key_len: 5 ref: NULL rows: 2 Extra: Using where with pushed condition
Другие поддержанные сравнения включают следующее:
column
[NOT] LIKE
pattern
pattern
должна быть строка, буквально
содержащий образец, который будет соответствующим, см.
раздел 13.5.1.
column
IS [NOT] NULL
column
IN
(value_list
)
Каждый элемент в value_list
должен быть константой, литеральным значением.
column
BETWEEN
constant1
AND
constant2
constant1
и constant2
должны быть константами.
Во всех случаях в предыдущем списке для условия возможно быть преобразованным в форму одного или более прямых сравнений между столбцом и константой.
Условие механизма включено по умолчанию. Чтобы отключить это при запуске
сервера, установите
optimizer_switch
. Например, в файле my.cnf
используйте эти строки:
[mysqld] optimizer_switch=engine_condition_pushdown=off
Во время выполнения включите условие:
SET optimizer_switch='engine_condition_pushdown=off';
Ограничения. Условие механизма подвергается следующим ограничениям:
Поддержано только NDB
.
BLOB
или
TEXT
.
EXPLAIN EXTENDED
, чтобы определить, какие
условия фактически применены.Index Condition Pushdown (ICP) оптимизация для случая, где MySQL получает
строки от таблицы, используя индексирование. Без ICP механизм хранения
не применяет индексирование, чтобы определить местонахождение строк в базовой
таблице и возвращает их к серверу MySQL, который оценивает условие
WHERE
для строк. С ICP, если части WHERE
могут быть оценены при использовании только областей от индекса, сервер MySQL
продвигает эту часть WHERE
к механизму хранения. Механизм
хранения тогда оценивает индексное условие при использовании индексной
записи и только если это удовлетворено, строка считана из таблицы. ICP может
уменьшить число раз, которое механизм хранения должен получить доступ к
базовой таблице, и число раз, которое сервер MySQL должен получить
доступ к механизму хранения.
Index Condition Pushdown используется для методов доступа
диапазона
,
ref
,
eq_ref
и
ref_or_null
,
когда есть потребность получить доступ к полным строкам таблицы. Эта
стратегия может использоваться для InnoDB
и MyISAM
,
включая разделенные таблицы InnoDB
и MyISAM
. Для
InnoDB
ICP используется только для вторичного индекса.
Цель ICP состоит в том, чтобы сократить количество чтений полных записей
и таким образом уменьшить операции IO. Для кластеризируемого индекса
InnoDB
полная запись уже считана в буфер InnoDB
.
ICP в этом случае не уменьшает IO.
Оптимизация ICP не поддержана со вторичным индексом на произведенных виртуальных столбцах.
Чтобы видеть, как эта оптимизация работает, рассмотрите сначала, как работает просмотр индекса, когда Index Condition Pushdown выключена:
Получите следующую строку, читая индексный кортеж, а затем при использовании индексного кортежа определите местонахождение и считайте полную строку таблицы.
WHERE
, которая относится к этой таблице.
Примите или отклоните строку.С Index Condition Pushdown просмотра идет так:
Получите следующую строку индексного кортежа (но не полную строку таблицы).
WHERE
, которая относится к этой таблице
и может быть проверена, используя только индексированные столбцы. Если
условие не удовлетворено, перейдите к индексному кортежу
для следующей строки.WHERE
, которая относится к
этой таблице. Примите или отклоните строку.Когда Index Condition Pushdown включен, столбец Extra
в
EXPLAIN
показывает
Using index condition
. Это не будет показывать
Index only
, потому что это не применяется, когда полные строки
таблицы должны быть считаны.
Предположите, что у нас есть таблица, содержащая информацию о людях и их
адресах и что таблице определили индексирование как INDEX (zipcode,
lastname, firstname)
. Если мы знаем zipcode
, но не
уверены в фамилии, мы можем искать так:
SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';
MySQL может использовать индексирование, чтобы просмотреть людей с
zipcode='95054'
. Вторая часть
(lastname LIKE '%etrunia%'
) не может использоваться, чтобы
ограничить число строк, которые должны быть просмотрены, таким образом, без
Index Condition Pushdown этот запрос должен получить полные строки таблицы
для всех людей, которые имеют zipcode='95054'
.
С Index Condition Pushdown MySQL проверит часть
lastname LIKE '%etrunia%'
прежде, чем считать полную строку
таблицы. Это избегает читать все строки, соответствующие всем индексным
кортежам, которые не соответствуют lastname
.
Index Condition Pushdown включен по умолчанию, этим можно управлять с
помощью optimizer_switch
установкой флага index_condition_pushdown
, см.
раздел 9.9.2.
InnoDB
автоматически расширяет каждый вторичный индекс, прилагая столбцы первичного
ключа к этому. Рассмотрите это табличное определение:
CREATE TABLE t1 (i1 INT NOT NULL DEFAULT 0, i2 INT NOT NULL DEFAULT 0, d DATE DEFAULT NULL, PRIMARY KEY (i1, i2), INDEX k_d (d)) ENGINE = InnoDB;
Эта таблица определяет первичный ключ на столбцах (i1, i2)
.
Это также определяет вторичный индекс
k_d
на столбце (d)
,
но внутренне InnoDB
расширяет этот индекс
и обрабатывает это как столбцы (d, i1, i2)
.
Оптимизатор принимает во внимание, что столбцы первичного ключа добавлены во вторичный индекс, определяя, как и использовать ли индекс. Это может привести к более эффективным планам выполнения запроса и лучшей работе.
Оптимизатор может использовать расширенный вторичный индекс для
доступа ref
, range
и
index_merge
, свободных индексных просмотров, соединения и
оптимизации сортировки и для оптимизации
MIN()
/
MAX()
.
Следующий пример показывает, как планы выполнения затронуты тем,
использовал ли оптимизатор расширенные вторичные индексы. Предположите, что
t1
заполнен с этими строками:
INSERT INTO t1 VALUES (1, 1, '1998-01-01'), (1, 2, '1999-01-01'), (1, 3, '2000-01-01'), (1, 4, '2001-01-01'), (1, 5, '2002-01-01'), (2, 1, '1998-01-01'), (2, 2, '1999-01-01'), (2, 3, '2000-01-01'), (2, 4, '2001-01-01'), (2, 5, '2002-01-01'), (3, 1, '1998-01-01'), (3, 2, '1999-01-01'), (3, 3, '2000-01-01'), (3, 4, '2001-01-01'), (3, 5, '2002-01-01'), (4, 1, '1998-01-01'), (4, 2, '1999-01-01'), (4, 3, '2000-01-01'), (4, 4, '2001-01-01'), (4, 5, '2002-01-01'), (5, 1, '1998-01-01'), (5, 2, '1999-01-01'), (5, 3, '2000-01-01'), (5, 4, '2001-01-01'), (5, 5, '2002-01-01');
Теперь рассмотрите этот запрос:
EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'
Оптимизатор не может использовать первичный ключ в этом случае, потому что
это включает столбцы (i1, i2)
и запрос не обращается к
i2
. Вместо этого оптимизатор может использовать вторичный индекс
k_d
на (d)
, и план выполнения зависит от того,
используется ли расширение индекса.
Когда оптимизатор не рассматривает индексные расширения, он обрабатывает
индекс k_d
только как (d)
.
EXPLAIN
приводит к этому результату:
mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref possible_keys: PRIMARY,k_d key: k_d key_len: 4 ref: const rows: 5 Extra: Using where; Using index
Когда оптимизатор берет индексные расширения во внимание, он обрабатывает
k_d
как (d, i1, i2)
.
В этом случае это может использовать начальный индексный префикс
(d, i1)
, чтобы произвести лучший план выполнения:
mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref possible_keys: PRIMARY,k_d key: k_d key_len: 8 ref: const,const rows: 1 Extra: Using index
В обоих случаях key
указывает, что оптимизатор будет
использовать вторичный индекс k_d
, но
EXPLAIN
показывает эти
усовершенствования от использования расширенного индекса:
key_len
идет от 4 байтов до 8 байтов, указывая, что
ключевые поиски используют столбцы d
и i1
, а
не просто d
.
ref
изменяется с const
на
const,const
, потому что ключевой поиск использует две ключевых
части, не одну.rows
уменьшается от 5 до 1, указывая на то, что
InnoDB
должен исследовать меньше строк.Extra
меняется с Using where; Using index
на
Using index
. Это означает, что строки могут быть считаны,
используя только индексирование, без консультационных
столбцов в строке данных.Различия в поведении оптимизатора для использования расширенных индексов
могут также быть замечены с SHOW STATUS
:
FLUSH TABLE t1; FLUSH STATUS; SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'; SHOW STATUS LIKE 'handler_read%'
Предыдущие запросы включают
FLUSH TABLE
и
FLUSH STATUS
для сброса
табличного кэша и очистки счетчиков состояния.
Без индексного расширения SHOW
STATUS
приводит к этому результату:
+-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 5 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 0 | +-----------------------+-------+
С индексным расширением SHOW
STATUS
приводит к этому результату.
Handler_read_next
уменьшается от 5 до 1, указывая на более
эффективное использование индексирования:
+-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 1 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 0 | +-----------------------+-------+
Флаг use_index_extensions
в
optimizer_switch
разрешает управление, принимает ли оптимизатор столбцы первичного ключа
во внимание, определяя, как использовать вторичные индексы таблицы
InnoDB
. По умолчанию use_index_extensions
включен. Чтолыб проверить, улучшит ли работу отключение использования
расширения, надо использовать этот запрос:
SET optimizer_switch = 'use_index_extensions=off';
Использование расширения подвергается обычным пределам на число ключевых частей в индексировании (16) и максимальную длину ключа (3072 байт).
MySQL может выполнить ту же самую оптимизацию на
col_name
IS
NULL
, которую может использовать для
col_name
=
constant_value
. Например, MySQL
может использовать индекс и диапазоны, чтобы искать NULL
с
IS NULL
.
Примеры:
SELECT * FROMtbl_name
WHEREkey_col
IS NULL; SELECT * FROMtbl_name
WHEREkey_col
<=> NULL; SELECT * FROMtbl_name
WHEREkey_col
=const1
ORkey_col
=const2
ORkey_col
IS NULL;
Если WHERE
включает col_name
IS NULL
для столбца, который объявлен как NOT NULL
,
то выражение оптимизировано. Эта оптимизация не происходит в случаях, когда
столбец мог бы произвести NULL
так или иначе, например, если это
прибывает из таблицы на правой стороне LEFT JOIN
.
MySQL может также оптимизировать комбинацию
,
форма, которая распространена в решенных подзапросах.
col_name
= expr
OR
col_name
IS NULLEXPLAIN
показывает
ref_or_null
,
когда эта оптимизация используется.
Эта оптимизация может обработать
IS NULL
для любой ключевой части.
Некоторые примеры запросов, которые оптимизированы, предполагая, что есть
индексирование на столбцах a
и b
таблицы
t2
:
SELECT * FROM t1 WHERE t1.a=expr
OR t1.a IS NULL;
SELECT * FROM t1, t2 WHERE t1.a=t2.a OR t2.a IS NULL;
SELECT * FROM t1, t2 WHERE (t1.a=t2.a OR t2.a IS NULL) AND t2.b=t1.b;
SELECT * FROM t1, t2 WHERE t1.a=t2.a AND (t2.b=t1.b OR t2.b IS NULL);
SELECT * FROM t1, t2 WHERE (t1.a=t2.a AND t2.a IS NULL AND ...) OR
(t1.a=t2.a AND t2.a IS NULL AND ...);
ref_or_null
выполняет чтение на ссылочном ключе, а затем отдельный поиск строк со
значением ключа NULL
.
Оптимизация может обработать только один уровень
IS NULL
.
В следующем запросе MySQL использует ключевые поиски только по выражению
(t1.a=t2.a AND t2.a IS NULL)
и не в состоянии использовать ключевую часть на b
:
SELECT * FROM t1, t2 WHERE (t1.a=t2.a AND t2.a IS NULL) OR (t1.b=t2.b AND t2.b IS NULL);
MySQL осуществляет
следующим образом:A
LEFT JOIN
B
join_condition
Таблица B
установлена в зависимости от
таблицы A
и всех таблиц, от которых зависит
A
.
A
установлена в зависимости от всех
таблиц (кроме B
), которые используются в
выражении LEFT JOIN
.LEFT JOIN
используется, чтобы решить, как получить строки от
таблицы B
. Другими словами, любое условие в
WHERE
не используется.WHERE
выполнена.A
, которая соответствует
WHERE
, но нет никакой строки в B
,
которая соответствует выражению ON
, дополнительная строка в
B
произведена со всем
набором столбцов в NULL
.LEFT JOIN
, чтобы найти строки, которые
не существуют в некоторой таблице, и у Вас есть следующий тест:
col_name
IS NULL
в
WHERE
, где col_name
столбец, который объявлен как NOT NULL
, MySQL
прекращает искать больше строк (для особого сочетания ключей) после того, как
он нашел одну строку, которая соответствует LEFT JOIN
.
Выполнение RIGHT JOIN
походит на
LEFT JOIN
с тем, что роли таблиц поменялись местами.
Оптимизатор соединения вычисляет порядок, в котором нужно присоединить
таблицы. Табличный порядок чтения, вызванный LEFT JOIN
или
STRAIGHT_JOIN
помогает оптимизатору, делая его работу намного
более быстрой, потому что есть меньше табличных перестановок, чтобы
проверить. Отметьте, что это означает, что, если Вы делаете запрос следующего
типа, MySQL делает полный просмотр на b
так как LEFT
JOIN
предписывает, чтобы это было считано прежде d
:
SELECT * FROM a JOIN b LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key) WHERE b.key=d.key;
Затруднительное положение в этом случае является обратным порядком, в
котором a
и b
перечислены в FROM
:
SELECT * FROM b JOIN a LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key) WHERE b.key=d.key;
Для LEFT JOIN
, если WHERE
всегда ложно для произведенной строки NULL
,
LEFT JOIN
изменен на нормальное соединение. Например,
WHERE
был бы ложен в следующем запросе, если
t2.column1
NULL
:
SELECT * FROM t1 LEFT JOIN t2 ON (column1) WHERE t2.column2=5;
Поэтому безопасно преобразовать запрос в нормальное соединение:
SELECT * FROM t1, t2 WHERE t2.column2=5 AND t1.column1=t2.column1;
Это может быть сделано быстрее, потому что MySQL может использовать
таблицу t2
до t1
, если выполнение привело бы к
лучшему плану запроса. Чтобы обеспечить подсказку о табличном порядке
соединения, надо использовать STRAIGHT_JOIN
, см.
раздел 14.2.9. Но STRAIGHT_JOIN
может не дать использовать индекс, потому что это отключает преобразования
полусоединения. См. раздел 9.2.1.18.1.
MySQLвыполняет соединения между таблицами, используя алгоритм вложенной петли.
Алгоритм соединения вложенной петли
Простой алгоритм nested-loop join (NLJ) читает строки из первой таблицы в петле по одной, передавая каждую строку к вложенной петле, которая обрабатывает следующую таблицу в соединении. Этот процесс повторен так много раз, пока там остаются таблицы, к которым присоединятся.
Предположите, что соединение между тремя таблицами t1
,
t2
и t3
должно быть выполнено, используя
следующие типы соединения:
Table Join Type t1 range t2 ref t3 ALL
Если простой алгоритм NLJ используется, соединение обработано так:
for each row in t1 matching range { for each row in t2 matching reference key { for each row in t3 { if row satisfies join conditions, send to client } } }
Поскольку алгоритм NLJ передает строки по одной от внешних петель внутренним, он как правило читает таблицы, обработанные во внутренних петлях, много раз.
Алгоритм Block Nested-Loop Join
Алгоритм Block Nested-Loop (BNL) присоединяется к буферизации использования алгоритма чтения строк во внешних петлях, чтобы уменьшить число раз, которое таблицы во внутренних петлях должны быть считаны. Например, если 10 строк считаны в буфер, и буфер передают к следующей внутренней петле, каждое чтение строки во внутренней петле может быть сравнено со всеми 10 строками в буфере.
MySQL использует буферизацию соединения при этих условиях:
join_buffer_size
определяет размер каждого буфера соединения.
ALL
или
index
(другими словами, когда никакие возможные ключи не могут использоваться, и
полный просмотр сделан, данных или индексных строки, соответственно), или
range
.
Использование буферизации также применимо к внешним соединениям, как описано
в разделе 9.2.1.14.ALL
или
index
.Для соединения в качестве примера, описанного ранее для алгоритма NLJ (не буферизуя), сделано соединение с использованием буферизации соединения:
for each row in t1 matching range { for each row in t2 matching reference key { store used columns from t1, t2 in join buffer if buffer is full { for each row in t3 { for each t1, t2 combination in join buffer { if row satisfies join conditions, send to client } } empty buffer } } } if buffer is not empty { for each row in t3 { for each t1, t2 combination in join buffer { if row satisfies join conditions, send to client } } }
Если S
размер каждого сохраненного t1
,
t2
комбинация буфера соединения и C
, числа
комбинаций в буфере, сколько раз таблица t3
просмотрена:
(S
*C
)/join_buffer_size + 1
Число t3
уменьшается по мере того, как значение
join_buffer_size
растет, пока
join_buffer_size
является достаточно большим, чтобы содержать все
предыдущие комбинации строки. В этом пункте нет никакой скорости, которая
будет получена, делая это больше.
Синтаксис для выражений разрешает вложенные соединения. Следующее обсуждение обращается к синтаксису соединения, описанному в разделе 14.2.9.2.
Синтаксис table_factor
расширен по сравнению со
стандартом SQL. Последний принимает только table_reference
, но не список их в паре круглых скобок. Это консервативное расширение,
если мы рассматриваем каждую запятую в списке элементов
table_reference
как эквивалент
внутреннему соединению. Например:
SELECT * FROM t1 LEFT JOIN (t2, t3, t4) ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
эквивалентно вот этому:
SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4) ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
В MySQL CROSS JOIN
эквивалентно
INNER JOIN
(они могут заменить друг друга). В стандартном SQL
они не эквивалентны. INNER JOIN
используется с
ON
, иначе используется CROSS JOIN
.
Вообще, круглые скобки могут быть проигнорированы в выражениях соединения, содержащих только внутренние операции соединения. После удаления круглых скобок и группировки операций выражение соединения:
t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL) ON t1.a=t2.a
преобразовывается в выражение:
(t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL
Все же эти два выражения не эквивалентны. Чтобы видеть это, предположите,
что таблицы t1
, t2
и t3
имеют следующее состояние:
Таблица t1
содержит строки
(1)
, (2)
t2
содержит строки (1,101)
.t3
содержит строки (101)
.В этом случае первое выражение возвращает набор результатов, включая
строки (1,1,101,101)
, (2,NULL,NULL,NULL)
,
тогда как второе выражение возвращает строки (1,1,101,101)
,
(2,NULL,NULL,101)
:
mysql> SELECT * FROM t1 LEFT JOIN -> (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL) -> ON t1.a=t2.a; +---+------+------+------+ | a | a | b | b | +---+------+------+------+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | NULL | +---+------+------+------+ mysql> SELECT * FROM (t1 LEFT JOIN t2 ON t1.a=t2.a) -> LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL; +---+------+------+-----+ | a | a | b | b | +---+------+------+-----+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | 101 | +---+------+------+-----+
В следующем примере внешняя работа соединения используется вместе с внутренней работой соединения:
t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
Это выражение не может быть преобразовано в следующее выражение:
t1 LEFT JOIN t2 ON t1.a=t2.a, t3.
Для данных табличных состояний эти два выражения возвращают различные наборы строк:
mysql> SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a; +---+------+------+------+ | a | a | b | b | +---+------+------+------+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | NULL | +---+------+------+------+ mysql> SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3; +---+------+------+-----+ | a | a | b | b | +---+------+------+-----+ | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | 101 | +---+------+------+-----+
Поэтому, если мы опускаем круглые скобки в выражении соединения с внешними операторами соединения, мы могли бы изменить набор результатов для оригинального выражения.
Более точно, мы не можем проигнорировать круглые скобки в правом операнде соединения left outer и в левом операнде соединения right join. Другими словами, мы не можем проигнорировать круглые скобки для внутренних табличных выражений внешних операций соединения. Круглые скобки для другого операнда (операнд для внешней таблицы) могут быть проигнорированы.
Следующее выражение:
(t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b)
эквивалентно этому выражению:
t1, t2 LEFT JOIN t3 ON P(t2.b,t3.b)
для любых таблиц t1,t2,t3
и любого условия
P
по признакам t2.b
и t3.b
.
Всякий раз, когда порядок выполнения операций соединения в выражении
(join_table
) не слева направо, мы говорим о вложенных
соединениях. Рассмотрите следующие запросы:
SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a WHERE t1.a > 1 SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a WHERE (t2.b=t3.b OR t2.b IS NULL) AND t1.a > 1
Те запросы, как полагают, содержат эти вложенные соединения:
t2 LEFT JOIN t3 ON t2.b=t3.b t2, t3
Вложенное соединение сформировано в первом запросе с left join, тогда как во втором запросе это сформировано с inner join.
В первом запросе могут быть опущены круглые скобки: грамматическая
структура выражения соединения продиктует тот же самый порядок выполнения для
операций соединения. Для второго запроса не могут быть опущены круглые
скобки, хотя выражение соединения здесь может интерпретироваться однозначно
без них. В нашем расширенном синтаксисе круглые скобки в (t2, t3)
из второго запроса требуются, хотя теоретически запрос мог быть
разобран без них: у нас все еще была бы однозначная синтаксическая структура
для запроса, потому что LEFT JOIN
и ON
играл бы
роль левых и правых разделителей для выражения (t2,t3)
.
Предыдущие примеры демонстрируют эти пункты:
Для выражений соединения, вовлекающих только внутренние соединения (но не outer join), могут быть удалены круглые скобки. Вы можете удалить круглые скобки и оценить слева направо (или, фактически, Вы можете оценить таблицы в любом порядке).
Запросы с вложенными внешними соединениями выполнены в той же самой манере
трубопровода как запросы с внутренними соединениями. Более точно,
эксплуатируется изменение алгоритма соединения вложенной петли.
Предположите, что у нас есть запрос соединения более, чем 3 таблиц
T1,T2,T3
:
SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2) INNER JOIN T3 ON P2(T2,T3) WHERE P(T1,T2,T3).
Здесь P1(T1,T2)
и P2(T3,T3)
некоторые условия соединения (по выражениям), тогда как
P(T1,T2,T3)
условие по столбцам таблиц T1,T2,T3
.
Алгоритм соединения вложенной петли выполнил бы этот запрос в следующей манере:
FOR each row t1 in T1 { FOR each row t2 in T2 such that P1(t1,t2) { FOR each row t3 in T3 such that P2(t2,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } } }
Запись t1||t2||t3
значит строка, созданная,
связывая столбцы строк t1
, t2
и
t3
. В некоторых из следующих примеров
NULL
где имя строки означает, что NULL
используется для каждого столбца той строки. Например,
t1||t2||NULL
значит строка, созданная, связывая
столбцы строк t1
, t2
и NULL
для каждого столбца t3
.
Теперь давайте рассматривать запрос с вложенными внешними соединениями:
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON P2(T2,T3)) ON P1(T1,T2) WHERE P(T1,T2,T3).
Для этого запроса мы изменяем образец вложенной петли:
FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) { BOOL f2:=FALSE; FOR each row t3 in T3 such that P2(t2,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f2=TRUE; f1=TRUE; } IF (!f2) { IF P(t1,t2,NULL) { t:=t1||t2||NULL; OUTPUT t; } f1=TRUE; } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } } }
Вообще для любой вложенной петли для первой внутренней таблицы в
outer join флаг введен, который выключен перед петлей и проверен после петли.
Флаг включен, когда для текущей строки из внешней таблицы, найдено
соответствие из таблицы, представляющей внутренний операнд.
Если в конце петли циклически повторяются, флаг все еще выключен, никакое
соответствие не было найдено для текущей строки внешней таблицы.
В этом случае строка дополнена значениями NULL
для столбцов внутренних таблиц. Строку результата передают к последней
проверке для вывода или в следующую вложенную петлю, но только если строка
удовлетворяет условие соединения всех встроенных внешних соединений.
В нашем примере встроена внешняя таблица соединения, выраженная следующим выражением:
(T2 LEFT JOIN T3 ON P2(T2,T3))
Для запроса с внутренними соединениями оптимизатор мог выбрать различный порядок вложенных петель, такой как этот:
FOR each row t3 in T3 { FOR each row t2 in T2 such that P2(t2,t3) { FOR each row t1 in T1 such that P1(t1,t2) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } } }
Для запросов с внешними соединениями оптимизатор может выбрать только такой порядок, где петли для внешних таблиц предшествуют петлям для внутренних таблиц. Таким образом, для нашего запроса с внешними соединениями, только один порядок вложения возможен. Для следующего запроса оптимизатор оценит два различных вложения:
SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3) WHERE P(T1,T2,T3)
Вложения будут такие:
FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) { FOR each row t3 in T3 such that P2(t1,t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f1:=TRUE } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } } }
и:
FOR each row t1 in T1 { BOOL f1:=FALSE; FOR each row t3 in T3 such that P2(t1,t3) { FOR each row t2 in T2 such that P1(t1,t2) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } f1:=TRUE } } IF (!f1) { IF P(t1,NULL,NULL) { t:=t1||NULL||NULL; OUTPUT t; } } }
В обоих вложениях T1
должен быть обработан во внешней петле,
потому что она используется во внешнем соединении. T2
и
T3
используются во внутреннем соединении так, чтобы соединение
было обработано во внутренней петле. Однако, потому что соединение
внутреннее, T2
и T3
могут быть
обработаны в любом порядке.
Обсуждая алгоритм вложенной петли для внутренних соединений, мы опустили
некоторые детали, воздействие которых на исполнение запроса может быть
огромным. Мы не упоминали так называемый
pushed-down. Предположите, что в нашем
WHERE
условие
P(T1,T2,T3)
может быть представлено соединительной формулой:
P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3).
В этом случае MySQL фактически использует следующую схему вложенной петли для выполнения запроса с внутренними соединениями:
FOR each row t1 in T1 such that C1(t1) { FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2) { FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) { IF P(t1,t2,t3) { t:=t1||t2||t3; OUTPUT t; } } } }
Вы видите, что каждый из C1(T1)
,
C2(T2)
, C3(T3)
продвинуты из самой внутренней петли
к самой внешней петле, где это может быть оценено. Если C1(T1)
очень строгое условие, это условие может очень сократить количество строк от
таблицы T1
к внутренним петлям. В результате время выполнения
для запроса может улучшиться.
Для запроса с внешними соединениями WHERE
состоит в том, чтобы быть проверенным только после того, как было найдено,
что у текущей строки от внешней таблицы есть соответствие во внутренних
таблицах. Таким образом, оптимизация продвижения условий из внутренних
вложенных петель не может быть применена непосредственно к запросам
с внешними соединениями. Здесь мы должны ввести условные вниз продвинутые
предикаты, которые охраняют флаги, которые включены,
когда столкнулись с соответствием.
Для нашего примера с внешними соединениями:
P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)
схема вложенной петли, используя продвинутые вниз условия, похожа на это:
FOR each row t1 in T1 such that C1(t1) { BOOL f1:=FALSE; FOR each row t2 in T2 such that P1(t1,t2) AND (f1?C2(t2):TRUE) { BOOL f2:=FALSE; FOR each row t3 in T3 such that P2(t2,t3) AND (f1&&f2?C3(t3):TRUE) { IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) { t:=t1||t2||t3; OUTPUT t; } f2=TRUE; f1=TRUE; } IF (!f2) { IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) { t:=t1||t2||NULL; OUTPUT t; } f1=TRUE; } } IF (!f1 && P(t1,NULL,NULL)) { t:=t1||NULL||NULL; OUTPUT t; } }
Вообще, вниз продвинутые предикаты могут быть извлечены из условий
соединения P1(T1,T2)
и P(T2,T3)
.
В этом случае вниз продвинутый предикат охраняет также флаг, который
предотвращает проверку предиката для NULL
-дополненной строки,
произведенной соответствующей внешней операцией outer join.
Доступ ключом от одной внутренней таблицы к другой
в том же самом вложенном соединении запрещен, если это вызвано предикатом от
WHERE
. Мы могли использовать условный ключевой доступ в этом
случае, но этот метод еще не используется в MySQL.
Табличные выражения в FROM
упрощены во многих случаях.
На этапе анализа запросы с правильными внешними операциями соединений преобразованы в эквивалентные запросы, содержащие только left join. В общем случае преобразование выполнено согласно следующему правилу:
(T1, ...) RIGHT JOIN (T2,...) ON P(T1,...,T2,...) = (T2, ...) LEFT JOIN (T1,...) ON P(T1,...,T2,...)
Все внутренние выражения соединения формы T1 INNER JOIN
T2 ON P(T1,T2)
заменены списком
T1,T2
, P(T1,T2)
будучи присоединенным как соединенное к WHERE
(или к условию соединения встраивания, если есть).
Когда оптимизатор оценивает планы относительно запросов с outer join, он учитывает только планы, где для каждой такой работы к внешним таблицам получают доступ перед внутренними таблицами. Опции ограничены, потому что только такие планы позволяют нам выполнить запросы с внешними операциями соединений вложенной схемой петли.
Предположите, что у нас есть запрос формы:
SELECT * T1 LEFT JOIN T2 ON P1(T1,T2) WHERE P(T1,T2) AND R(T2)
с R(T2)
очень сужающим число соответствия строк от таблицы
T2
. Если бы мы выполняли запрос, как это, то у оптимизатор не
было бы никакого другого выбора, кроме доступа к таблице T1
до
T2
, что может привести к очень неэффективному плану выполнения.
MySQL преобразовывает такой запрос в запрос без outer join,
если условие WHERE
отклонено null.
Условие называют отклоненным нулем для outer join,
если оно оценивается к FALSE
или UNKNOWN
для
любой NULL
-дополненной строки для работы.
Таким образом, для этого внешнего соединения:
T1 LEFT JOIN T2 ON T1.A=T2.A
Такие условия отклонены нулем:
T2.B IS NOT NULL, T2.B > 3, T2.C <= T1.C, T2.B < 2 OR T2.C > 1
Такие условия не отклонены нулем:
T2.B IS NULL, T1.B < 3 OR T2.B IS NOT NULL, T1.B < 3 OR T2.B > 3
Общие правила для того, чтобы проверить, отклонено ли условие нулем для outer join просты. Условие отклонено нулем в следующих случаях:
Если это имеет форму A IS NOT NULL
, где
A
признак любой из внутренних таблиц.
UNKNOWN
, когда один
из параметров NULL
.Условие может быть отклонено нулем для одной внешней работы соединения в запросе и не отклонено нулем для другой. В запросе:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A LEFT JOIN T3 ON T3.B=T1.B WHERE T3.C > 0
WHERE
отклонено нулем для второго outer join, но не отклонено
нулем для первого.
Если WHERE
отклонено нулем для outer join в запросе, outer
join соединения заменен inner join.
Например, предыдущий запрос заменен запросом:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A INNER JOIN T3 ON T3.B=T1.B WHERE T3.C > 0
Для оригинального запроса оптимизатор оценил бы планы, совместимые только
с одним порядком доступа T1,T2,T3
.
Для запроса замены это дополнительно рассматривает последовательность доступа
T3,T1,T2
.
Преобразование одного outer join может вызвать преобразование другого. Таким образом, запрос:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A LEFT JOIN T3 ON T3.B=T2.B WHERE T3.C > 0
будет сначала преобразован в запрос:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A INNER JOIN T3 ON T3.B=T2.B WHERE T3.C > 0
который эквивалентен запросу:
SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3 WHERE T3.C > 0 AND T3.B=T2.B
Теперь остающийся outer join может быть заменен inner join,
потому что условие T3.B=T2.B
отклонено нулем и мы получаем запрос без внешних соединений вообще:
SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3 WHERE T3.C > 0 AND T3.B=T2.B
Иногда мы преуспеваем в том, чтобы заменить встроенный outer join, но не можем преобразовать встраивающий outer join. Следующий запрос:
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A WHERE T3.C > 0
преобразован в:
SELECT * FROM T1 LEFT JOIN (T2 INNER JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A WHERE T3.C > 0,
Это может быть переписано только к форме, все еще содержащей встроенный outer join:
SELECT * FROM T1 LEFT JOIN (T2,T3) ON (T2.A=T1.A AND T3.B=T2.B) WHERE T3.C > 0.
Пытаясь преобразовать встроенный outer join в запросе, мы должны принять
во внимание, что условие соединения для внешнего встраивания
объединяется с WHERE
. В запросе:
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A AND T3.C=T1.C WHERE T3.D > 0 OR T1.D > 0
WHERE
не отклонено нулем для встроенного outer join, но
но условия соединения outer join T2.A=T1.A AND T3.C=T1.C
отклонено нулем. Таким образом, запрос может быть преобразован в:
SELECT * FROM T1 LEFT JOIN (T2, T3) ON T2.A=T1.A AND T3.C=T1.C AND T3.B=T2.B WHERE T3.D > 0 OR T1.D > 0
Чтение строк, используя просмотр диапазона на вторичном индексе, может привести ко многим случайным дисковым доступам к базовой таблице, когда таблица является большой и не сохранена в кэше механизма хранения. С оптимизацией Disk-Sweep Multi-Range Read (MRR) MySQL пытается сократить количество случайного дискового доступа для просмотров диапазона первым только просмотром индекса и сбором ключей для соответствующих строк. Тогда ключи сортированы, и строки получены из базовой таблицы, используя порядок первичного ключа. Побуждение для Disk-sweep MRR должно сократить количество случайных дисковых доступов и вместо этого достигнуть более последовательного просмотра данных базовой таблицы.
Оптимизация Multi-Range Read обеспечивает эту выгоду:
MRR позволяет строкам данных быть полученными доступ последовательно, а не в случайном порядке, основанном на индексных кортежах. Сервер получает ряд индексных кортежей, которые удовлетворяют условиям запроса, сортирует их согласно порядку ID строк данных и использует сортированные кортежи, чтобы получить строки данных в этом порядке. Это делает доступ к данным более эффективным и менее дорогим.
Оптимизация MRR не поддержана со вторичным индексом на произведенных виртуальных столбцах.
Следующие примеры иллюстрируют, когда оптимизация MRR может быть выгодной:
Сценарий A: MRR может использоваться для таблиц
InnoDB
и MyISAM
для просмотра индексного диапазона и операции equi-соединения.
Часть индексных кортежей накоплена в буфере.
Сценарий B: MRR может использоваться для
NDB
для мультидиапазонного индексного
просмотра или выполняя equi-соединение.
Часть диапазонов, возможно, одноключевые диапазоны, накоплена в буфере на центральном узле, где запрос представлен.
Когда MRR используется, столбец Extra
в выводе
EXPLAIN
показывает Using MRR
.
InnoDB
и MyISAM
не используют
MRR, если к полным строкам таблицы нельзя получить доступ, чтобы привести к
результату запроса. Дело обстоит так, если к результатам можно привести
полностью на основе информации в индексных кортежах (посредством
покрытия индекса), MRR
не обеспечивает выгоды.
Запрос в качестве примера, для которого MRR может использоваться,
предполагая, что есть индексирование на (
:
key_part1
, key_part2
)
SELECT * FROM t WHEREkey_part1
>= 1000 ANDkey_part1
< 2000 ANDkey_part2
= 10000;
Индексирование состоит из кортежей (
, упорядоченных сначала по
key_part1
,
key_part2
)key_part1
, затем по key_part2
.
Без MRR индексный просмотр покрывает все индексные кортежи для
key_part1
в диапазоне 1000-2000, независимо от
key_part2
в этих кортежах. Просмотр делает
дополнительную работу до такой степени, что кортежи в диапазоне содержат
значения key_part2
кроме 10000.
С MRR просмотр разбит на многократные диапазоны, каждый
для единственного значения key_part1
(1000, 1001, ...,
1999). Каждый из этих просмотров должен искать только кортежи с
key_part2
= 10000.
Если индексирование содержит много кортежей для которых
key_part2
не 10000, результаты MRR
MRR во многом меньше считанных индексных кортежей.
Чтобы выразить это примечание интервала использования, не-MRR просмотр
должен исследовать индексный диапазон [{1000, 10000}, {2000, MIN_INT})
, который может включать много кортежей кроме тех, для которых
key_part2
= 10000. Просмотр MRR
исследует многократные интервалы [{1000, 10000}]
, ...,
[{1999, 10000}]
, которые включают только кортежи с
key_part2
= 10000.
Два флага в
optimizer_switch
обеспечивают интерфейс к использованию
оптимизации MRR. Флаг mrr
управляет, включен ли MRR. Если
mrr
= on
, флаг mrr_cost_based
управляет, пытается ли оптимизатор сделать выбор на основе издержек между
использованием и не использованием MRR (on
) или использование
MRR возможно когда бы ни было (off
). По умолчанию
mrr
= on
и mrr_cost_based
=
on
. См. раздел
9.9.2.
Для MRR механизм хранения использует значение
read_rnd_buffer_size
как направляющую линию для того, сколько памяти это может
выделить для буфера. Механизм использует до
read_rnd_buffer_size
байт и определяет число диапазонов, чтобы
обработать в единственном проходе.
В MySQL алгоритм Batched Key Access (BKA) Join доступен, который использует индексный доступ к таблице, к которой присоединяются, и буфер соединения. Алогритм BKA поддерживает внутреннее соединение, внешнее соединение, и операции полусоединения, включая вложенные внешние соединения. Выгода BKA включает улучшенную работу соединения из-за более эффективного табличного просмотра. Кроме того, алгоритм Block Nested-Loop (BNL) Join, ранее используемый только для внутренних соединений, расширен и может использоваться для внешнего соединения и операций полусоединения, включая вложенные внешние соединения.
Следующие разделы обсуждают буферное управление соединением, которое лежит в основе расширения оригинального алгоритма BNL, расширенного алгоритма BNL, и алгоритма BKA. См. раздел 9.2.1.18.1.
MySQL Server может использовать буферы соединения, чтобы выполнить не только внутренние соединения без индексного доступа к внутренней таблице, но также внешние соединения и полусоединения, которые появляются после выравнивания подзапроса. Кроме того, буфер соединения может эффективно использоваться, когда есть индексный доступ к внутренней таблице.
Буферный управленческий код соединения немного более эффективно использует
буферное пространство соединения, храня значения интересных столбцов строки:
никакие дополнительные байты не выделены в буферах для столбца строки, если
его значение NULL
, и минимальное число байтов выделено для
любого значения типа VARCHAR
.
Код поддерживает два типа буферов, регулярные и возрастающие.
Предположите, что буфер соединения B1
используется, чтобы
присоединиться к таблицам t1
и t2
и к результату
этой работы присоединяются с таблицей t3
с
использованием буфера соединения B2
:
Регулярный буфер соединения содержит столбцы от каждого операнда
соединения. Если B2
регулярный буфер соединения, каждая строка
r
, помещенная в B2
,
составлен из столбцов строки r1
из B1
и
интересные столбцы соответствующей строки это r2
из
t3
.
B2
возрастающий буфер соединения, это содержит интересные столбцы строки
r2
вместе со ссылкой к строке
r1
из B1
.Возрастающие буферы соединения являются всегда возрастающими относительно
буфера соединения от более ранней работы соединения, таким образом, буфер от
первой работы соединения всегда регулярный. В этом примере буфер
B1
используемый, чтобы присоединиться к таблицам t1
и t2
, должен быть регулярным.
Каждая строка возрастающего буфера, используемого для работы соединения,
содержит только интересные столбцы строки от таблицы, к которой
присоединятся. Эти столбцы увеличены со ссылкой на интересные столбцы
соответствующей строки от таблицы, произведенной первым операндом соединения.
Несколько строк в возрастающем буфере могут обратиться к той же самой строке
r
, чьи столбцы сохранены в предыдущих буферах
соединения, поскольку все эти строки соответствуют строке
r
.
Возрастающие буферы включают менее частое копирование столбцов от буферов, используемых для предыдущих операций соединения. Это обеспечивает сбережения в буферном пространстве, потому что в общем случае строка, произведенная первым операндом соединения, может быть соответствующей нескольким строкам, произведенным вторым операндом соединения. Не надо делать несколько копий строки от первого операнда. Возрастающие буферы также обеспечивают сбережения во время обработки из-за сокращения времени копирования.
Флаги block_nested_loop
и
batched_key_access
переменной
optimizer_switch
управляют, как оптимизатор использует алгоритмы Block Nested-Loop и
Batched Key Access join. По умолчанию block_nested_loop
=
on
и batched_key_access
= off
. См.
раздел 9.9.2.
См. раздел 9.2.1.18.1.
Оригинальное выполнение MySQL алгоритма BNL расширено, чтобы поддержать внешнее соединение и операции полусоединения.
Когда эти операции выполнены с буфером соединения, каждая строка, помещенная в буфер, поставляется с флагом соответствия.
Если outer join выполнен, используя буфер соединения, каждая строка
таблицы, произведенной вторым операндом, проверена на соответствие против
каждой строки в буфере соединения. Когда соответствие найдено, новая
расширенная строка сформирована (оригинальная строка плюс столбцы от второго
операнда) и послана на дальнейшие расширения остающимися операциями
соединения. Кроме того, флаг соответствия соответствующей строки в буфере
включен. После того, как все строки таблицы, к которой присоединятся, были
исследованы, буфер соединения просмотрен. Каждая строка от буфера, которой
не включили флаг соответствия, расширена дополнениями NULL
(значения NULL
для каждого столбца во втором операнде), и
послана за дальнейшими расширениями остающимися операциями соединения.
Флаг block_nested_loop
переменной
optimizer_switch
управляет, как оптимизатор использует алгоритм Block Nested-Loop.
По умолчанию block_nested_loop
= on
. См.
раздел 9.9.2.
В выводе EXPLAIN
использование BNL для таблицы показано, когда Extra
содержит Using join buffer (Block Nested Loop)
и
type
= ALL
,
index
или
range
.
См. раздел 9.2.1.18.1.
MySQL Server осуществляет метод присоединяющихся таблиц, названный Batched Key Access (BKA). BKA может быть применен, когда есть индексированный доступ к таблице, произведенной вторым операндом соединения. Как алгоритм соединения BNL, алгоритм соединения BKA использует буфер соединения, чтобы накопить интересные столбцы строк, произведенных первым операндом работы соединения. Тогда алгоритм BKA создает ключи, чтобы получить доступ к таблице, к которой присоединятся для всех строк в буфере, и отправляет эти ключи в пакете к механизму базы данных для индексных поисков. Ключи представлены механизму посредством интерфейса Multi-Range Read (MRR) (см. раздел 9.2.1.13). После представления ключей функции механизма MRR выполняют поиски в индексе оптимальным способом, принося строки таблицы, к которой присоединяются, найденные этими ключами, и начинают снабжать алгоритм соединения BKA соответствием строк. Каждая строка соответствия идет вместе со ссылкой на строку в буфере соединения.
При применении BKA значение
join_buffer_size
определяет, насколько большой пакет ключей находится в каждом запросе к
механизму хранения. Чем больше буфер, тем более последовательный доступ будет
к правой таблице работы соединения, что может значительно улучшить работу.
При применении BKA флаг batched_key_access
в переменной
optimizer_switch
должен быть установлен в on
. BKA использует MRR,
таким образом, флаг mrr
должен также быть on
.
В настоящее время, оценка стоимости для MRR слишком пессимистична.
Следовательно, также необходимо mrr_cost_based
= off
для использования BKA. Следующая установка включает BKA:
mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
Есть два сценария, которые выполняют функции MRR:
Первый сценарий используется для обычных основанных на диске
механизмов хранения, таких как InnoDB
и
MyISAM
.
Для этих механизмов обычно ключи для всех строк буфера соединения
представлены интерфейсу MRR сразу. Определенные для механизма функции MRR
делают индексные поиски для представленных ключей, получают ID
строки (или первичные ключи) от них, и затем приносят строки для всех этих
выбранных ID строк одну за другой по запросу от алгоритма BKA.
Каждая строка возвращена со ссылкой ассоциации, которая включает доступу к
соответствующей строке в буфере соединения. Строки принесены функциями MRR
оптимальным способом: они забраны в порядке ID строк (primary key).
Это улучшает работу, потому что чтения находятся в дисковом порядке,
а не случайном порядке.
NDB
. Пакет ключей для части строк от буфера
соединения, вместе с их ассоциациями, посылает MySQL Server (узел SQL) узлам
данных MySQL Cluster. В свою очередь, узел SQL получает пакет (или несколько
пакетов) соответствия строк вместе с соответствующими ассоциациями. Алгоритм
соединения BKA берет эти строки и создает новые строки, к которым
присоединяются. Тогда новый набор ключей посылают в узлы данных, и строки от
возвращенных пакетов используются, чтобы создать новые строки, к которым
присоединяются. Процесс продолжается, пока последние ключи от буфера
соединения не посылают в узлы данных, и узел SQL не получит и не
присоединится ко всем строкам, соответствующим этим ключам. Это улучшает
работу, потому что меньше имеющих ключ пакетов, посланных узлом SQL в узлы
данных, означает меньше путешествий туда и обратно между нем и узлами данных.
С первым сценарием часть буфера соединения сохранена, чтобы сохранить ID строки (первичные ключи), выбранные индексными поисками и прошедшими в качестве параметра к функциям MRR.
Нет никакого специального буфера, чтобы сохранить ключи, созданные для строк из буфера соединения. Вместо этого функцию, которая создает ключ для следующей строки в буфере, передают в качестве параметра к функциям MRR.
В выводе EXPLAIN
использование
BKA для таблицы показано, когда значение Extra
содержит
Using join buffer (Batched Key Access)
и type
ref
или
eq_ref
.
В некоторых случаях MySQL может использовать индексирование, чтобы
удовлетворить ORDER BY
, не делая дополнительную сортировку.
Индексирование может также использоваться даже если ORDER BY
не соответствует индексу точно, пока все неиспользованные части индекса
и всех дополнительных столбцов ORDER BY
это константы в
WHERE
. Следующие запросы используют индексирование, чтобы решить
часть ORDER BY
:
SELECT * FROM t1 ORDER BYkey_part1
,key_part2
,... ; SELECT * FROM t1 WHEREkey_part1
=constant
ORDER BYkey_part2
; SELECT * FROM t1 ORDER BYkey_part1
DESC,key_part2
DESC; SELECT * FROM t1 WHEREkey_part1
= 1 ORDER BYkey_part1
DESC,key_part2
DESC; SELECT * FROM t1 WHEREkey_part1
>constant
ORDER BYkey_part1
ASC; SELECT * FROM t1 WHEREkey_part1
<constant
ORDER BYkey_part1
DESC; SELECT * FROM t1 WHEREkey_part1
=constant1
ANDkey_part2
>constant2
ORDER BYkey_part2
;
В некоторых случаях MySQL не может использовать
индекс, чтобы решить ORDER BY
, хотя это все еще использует
индекс, чтобы найти строки, которые соответствуют WHERE
.
Эти случаи включают следующее:
Использование запроса ORDER BY
на различных индексах:
SELECT * FROM t1 ORDER BYkey1
,key2
;
ORDER BY
на
непоследовательных частях индекса:
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey_part2
;
ASC
и DESC
:
SELECT * FROM t1 ORDER BYkey_part1
DESC,key_part2
ASC;
ORDER BY
:
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey1
;
ORDER BY
с выражением, которое
включает термины, кроме имени столбца индекса:
SELECT * FROM t1 ORDER BY ABS(key
); SELECT * FROM t1 ORDER BY -key
;
ORDER BY
не все от первой непостоянной таблицы, которая
используется, чтобы получить строки. Это первая таблица в выводе
EXPLAIN
, у которой нет
типа соединения const
.
ORDER BY
и GROUP BY
.ORDER BY
. В этом случае индексирование не может использоваться,
чтобы полностью решить порядок сортировки. Например, если только первые 10
байтов столбца CHAR(20)
индексированы, индекс не может отличить значения после 10-го байта и
будет необходим filesort
.HASH
в таблице MEMORY
.Доступность индексирования для того, чтобы сортировать может быть
затронута при помощи псевдонимов столбца. Предположите, что столбец
t1.a
индексирован. В этом запросе название столбца в
избранном списке a
.Это обращается к t1.a
, так
что для ссылки на a
в ORDER BY
индексирование может использоваться:
SELECT a FROM t1 ORDER BY a;
В этом запросе название столбца в избранном списке также a
,
но это имя псевдонима. Это обращается к ABS(a)
, так что для
ссылки на a
в ORDER BY
индекс
не может использоваться:
SELECT ABS(a) AS a FROM t1 ORDER BY a;
В следующем запросе ORDER BY
обращается к имени, которое не
является названием столбца в избранном списке. Но есть столбец в
t1
с именем a
, так что ORDER BY
использует это, и индекс может использоваться. Получающийся порядок
сортировки может абсолютно отличаться от порядка для ABS(a)
.
SELECT ABS(a) AS b FROM t1 ORDER BY a;
По умолчанию MySQL сортирует все запросы GROUP BY
как будто Вы определили col1
, col2
, ...ORDER BY
в запросе. Если Вы включаете явный
col1
,
col2
, ...ORDER BY
, который содержит тот же самый список столбца, MySQL
оптимизирует это без потери скорости, хотя сортировка все еще происходит.
Доверие неявному GROUP BY
устарело.
Чтобы достигнуть определенного порядка сортировки сгруппированных
результатов, предпочтительно использовать явный ORDER BY
.
GROUP BY
это расширение MySQL, которое может измениться в
будущем выпуске, например, чтобы позволить оптимизатору упорядочить
группировки в любой манере, которую это считает самым эффективной и
избегать издержек сортировки.
Если запрос включает GROUP BY
, но Вы хотите избежать
сортировки результата, Вы можете подавить сортировку, определяя
ORDER BY NULL
. Например:
INSERT INTO foo SELECT a, COUNT(*) FROM bar GROUP BY a ORDER BY NULL;
Оптимизатор может все еще хотеть использовать сортировку, чтобы
осуществить группирующиеся операции. ORDER BY NULL
подавляет сортировку результата, но не предшествующую сортировку, сделанную,
группируя операции, чтобы определить результат.
С EXPLAIN SELECT ... ORDER BY
Вы можете проверить, может ли MySQL использовать индекс, чтобы решить запрос.
Не может, если Вы видите Using filesort
в столбце Extra
, см. раздел 9.8.1. Filesort
использует формат хранения строки фиксированной длины, подобный используемому
механизмом хранения MEMORY
. Типы переменной длины, такие как
VARCHAR
,
сохранены, используя фиксированную длину.
MySQL имеет два алгоритма filesort
для сортировки и получения результатов. Оригинальный метод использует только
столбцы ORDER BY
. Измененный метод использует не только столбцы
ORDER BY
, а все столбцы в запросе.
Оптимизатор выбирает, который алгоритм filesort
использовать. Это обычно использует измененный алгоритм кроме тех случаев,
когда вовлечены столбцы BLOB
или
TEXT
, тогда это использует
оригинальный алгоритм. Для обоих алгоритмов размер буфера сортировки это
sort_buffer_size
.
Оригинальный алгоритм работает следующим образом:
Считать все строки согласно ключу или табличному
просмотру. Пропустить строки, которые не соответствуют WHERE
.
MERGEBUFF
(7) к одному блоку в
другом временном файле. Повторите, пока все блоки от первого файла не будут
во втором файле.MERGEBUFF2
(15) блоков.read_rnd_buffer_size
. Код для этого шага находится в исходном файле
sql/records.cc
.Одна проблема с этим подходом состоит в том, что он читает строки дважды:
во время оценки WHERE
и снова после сортировки пар значения. И
даже если к строкам получили доступ последовательно в первый раз (например,
если сканирование таблицы сделано), во второй раз к ним получают доступ
беспорядочно. Ключи сортировки упорядочены, но позиции строк нет.
Измененный алгоритм filesort
включает оптимизацию, чтобы избежать читать строки дважды:
это делает запись значения ключа сортировки, но вместо ID
строки, это делает запись столбцов, на которые ссылается запрос. Измененный
filesort
работает так:
Считайте строки, которые соответствуют WHERE
.
Кортежи, используемые измененным filesort
длинней, чем пары, используемые оригинальным алгоритмом, и меньше их
помещается в буфер. В результате для дополнительного ввода/вывода возможно
сделать измененный подход медленнее. Чтобы избежать замедления, оптимизатор
использует измененный алгоритм, только если полный размер дополнительных
столбцов в кортеже не превышает значения
max_length_for_sort_data
. Признаком установки значения этой
переменной слишком высоко является комбинация высокой дисковой деятельности и
низкой деятельности центрального процессора.
Измененный алгоритм filesort
включает дополнительную оптимизацию, разработанную, чтобы позволить большему
количеству кортежей вписаться в буфер: для дополнительных столбцов типа
CHAR
, VARCHAR
или любого nullable типа данных
фиксированного размера, значения упакованы. Например, без упаковки
значение столбца VARCHAR(255)
, содержащее только 3 символа,
берет 255 символов в буфере сортировки. С упаковкой значение требует только 3
символов плюс двухбайтовый индикатор длины. Значения NULL
требуют только битовой маски.
Для данных, содержащих пакуемые строки короче, чем максимальная длина
столбца или многие значения NULL
, больше записей вписывается в
буфер сортировки. Это улучшает сортировку буфера в памяти
и исполнение основанной на диске временной сортировки слияния файла.
В случаях края упаковка может быть невыгодной: если пакуемая строка это
максимальная длина столбца или есть немного значений NULL
,
место, требуемое для индикаторов длины, сокращает количество записей, которые
вписываются в буфер, и сортировка медленнее в памяти и на диске.
Есои filesort
сделан, вывод
EXPLAIN
включает
Using filesort
в столбце Extra
. Кроме того, вывод
трассировки оптимизатора включает блок filesort_summary
:
"filesort_summary": {"rows": 100, "examined_rows": 100, "number_of_tmp_files": 0, "sort_buffer_size": 25192, "sort_mode": "<sort_key, packed_additional_fields>"}
sort_mode
предоставляет информацию об используемом алгоритме
filesort
и содержании кортежей в буфере:
<sort_key, rowid>
: Это указывает на
использование оригинального алгоритма. Буферные кортежи сортировки это
пары, которые содержат значение ключа и ID оригинальной строки таблицы.
Кортежи сортированы значением ключа сортировки, и
ID строки используется, чтобы считать строку из таблицы.
<sort_key, additional_fields>
:
Это указывает на использование измененного алгоритма.
Буферные кортежи содержат значение ключа
и столбцы, на которые ссылается запрос. Кортежи сортированы значением ключа,
и значения столбцов считаны непосредственно из кортежа.<sort_key, packed_additional_fields>
:
Это указывает на использование измененного алгоритма. Буферные кортежи
содержат значение ключа и упакованные столбцы, на которые ссылается запрос.
Кортежи сортированы значением ключа, и значения столбцов считаны
непосредственно из кортежа.Предположите, что таблица t1
имеет четыре столбца
VARCHAR
: a
, b
, c
и
d
, и что оптимизатор использует для
этого запроса filesort
:
SELECT * FROM t1 ORDER BY a, b;
Запрос сортируется по a
и b
,
но возвращает все столбцы, таким образом, столбцы, на которые ссылается
запрос, это a
, b
, c
и
d
. В зависимости от алгоритма filesort
,
запрос выполняется следующим образом:
Для оригинального алгоритма у буферных кортежей есть это содержание:
(фиксированный размер значения a, фиксированный размер значения b, ID строки в t1)
Оптимизатор сортирует на значениях фиксированного размера. После
сортировки оптимизатор читает кортежи в порядке и использует ID строки
в каждом кортеже, чтобы считать строки из t1
, чтобы
получить избранные значения столбцов списка.
Для измененного алгоритма без упаковки у буферных кортежей есть это содержание:
(фиксированный размер значения a, фиксированный размер значения b, значение a, значение b, значение c, значение d)
Оптимизатор сортирует на значениях фиксированного размера. После
сортировки оптимизатор читает кортежи в порядке и использует значения для
a
, b
, c
и d
, чтобы
получить избранные значения столбцов списка без чтения t1
.
Для измененного алгоритма с упаковкой у буферных кортежей есть это содержание:
(фиксированный размер значения a, фиксированный размер значения b, длина a, упакованное значение a, длина b, упакованное значение b, длина c, упакованное значение c, длина d, упакованное значение d)
Если что-то из a
, b
,
c
или d
NULL
,
они не занимают места в буфере, кроме как в битовой маске.
Оптимизатор сортирует на значениях фиксированного размера. После
сортировки оптимизатор читает кортежи в порядке и использует значения для
a
, b
, c
и d
, чтобы
получить избранные значения столбцов списка без чтения t1
.
Для медленных запросов, для которых filesort
не используется, попытайтесь понизить
max_length_for_sort_data
, к значению, которое является
соответствующим, чтобы вызвать filesort
.
Чтобы ускорить ORDER BY
, проверьте, можете ли Вы заставить
MySQL использовать индекс, а не дополнительную фазу сортировки.
Если это невозможно, Вы можете попробовать следующие стратегии:
Увеличьте
sort_buffer_size
. Идеально, значение должно быть достаточно
большим для всего набора результатов, чтобы поместиться в буфер, но в
минимуме значение должно быть достаточно большим, чтобы
приспособить пятнадцать кортежей.
Примите во внимание, что размер значений столбцов, сохраненных в буфере,
затронут max_sort_length
. Например, если кортежи хранят значения длинных строковых
столбцов, и Вы увеличиваете значение
max_sort_length
,
размер буферных кортежей также может потребовать, чтобы Вы увеличили и
sort_buffer_size
. Для значений столбцов, вычисленных в результате строковых выражений
(таких как те, которые вызывают оцененную к строке функцию), алгоритм
filesort
не может сказать максимальную длину значений выражения,
таким образом, это должно выделить
max_sort_length
байт для каждого кортежа.
Чтобы контролировать число проходов слияния, проверьте
Sort_merge_passes
.
read_rnd_buffer_size
.CHAR(16)
лучше, чем
CHAR(200)
, если значения никогда не превышают 16 символов.tmpdir
,
чтобы указать на специализированную файловую систему с большим количеством
свободного пространства. Переменное значение может перечислить несколько
путей, которые используются круговым способом, Вы можете использовать эту
функцию, чтобы распространить загрузку на несколько каталогов. Пути должны
быть отделены символами двоеточия (:
) в Unix и символами
точки с запятой (;
) в Windows. Пути должны назвать каталоги в
файловых системах, расположенных на
различных физических дисках.Если индексирование не используется для ORDER BY
, но
LIMIT
также присутствует, оптимизатор может быть в состоянии
избегать использования файла слияния и сортировать строки в памяти. См.
раздел 9.2.1.19.
Самый общий способ удовлетворить GROUP BY
должен просмотреть целую таблицу и составить новую временную таблицу, где
все строки от каждой группы последовательны, а затем использовать эту
временную таблицу, чтобы обнаружить группы и применить совокупные функции
(если есть). В некоторых случаях MySQL в состоянии сделать намного лучше
и избежать создания временных таблиц при использовании индекса.
Самые важные предварительные условия для того, чтобы использовать индекс
для GROUP BY
это все ссылочные признаки столбцов
GROUP BY
от того же самого индекса, и что ключи индекса
хранятся в порядке (например, это индекс BTREE
, но не
HASH
). Может ли использование временных таблиц быть заменено
индексным доступом, также зависит от того, на которой части индексирования
используются в запросе условия, определенные для этих частей
и выбранных совокупных функций.
Есть два способа выполнить запрос GROUP BY
через индексный доступ, как детализировано в следующих разделах. В первом
методе группировка применена вместе со всеми предикатами диапазона (если
есть). Второй метод сначала выполняет просмотр диапазона, затем
группирует получающиеся кортежи.
В MySQL GROUP BY
используется для того, чтобы сортировать,
таким образом, сервер может также применить ORDER BY
к группировке. Однако, доверие неявному GROUP BY
устарело, см.
раздел 9.2.1.15.
Самый эффективный способ обработать GROUP BY
, когда
индексирование используется, чтобы непосредственно получить группирующиеся
столбцы. С этим методом доступа MySQL использует свойство некоторых типов
индекса, что ключи упорядочены (например, BTREE
).
Это свойство включает использование групп поиска в индексировании, не имея
необходимость рассматривать все ключи в индексировании, которые удовлетворяют
все выражения WHERE
. Этот метод доступа рассматривает только
фракцию ключей в индексировании, таким образом, это называют
свободный просмотр индекса. Когда нет WHERE
,
свободный просмотр читает столько ключей, сколько имеется групп, что
может быть намного меньшим числом, чем все ключи. Если WHERE
содержит предикаты диапазона (см. обсуждение
range
в
разделе 9.8.1), свободный просмотр
ищет первый ключ каждой группы, которая удовлетворяет условиям диапазона, и
снова читает наименее возможное число ключей.
Это возможно при следующих условиях:
Запрос по единственной таблице.
GROUP BY
называет только столбцы, которые формируют крайний
левый префикс индекса и никаких других столбцов. Если вместо
GROUP BY
у запроса есть DISTINCT
,
все отличные признаки обращаются к столбцам, которые формируют крайний левый
префикс индекса. Например, если таблица t1
имеет индексирование на (c1,c2,c3)
, свободный просмотр
применим, если запрос имеет GROUP BY c1, c2,
.
Это не применимо, если запрос имеет GROUP BY c2, c3
(столбцы не
крайний левый префикс) или GROUP BY c1, c2, c4
(c4
не находится в индексировании).MIN()
и
MAX()
,
все они обращаются к тому же самому столбцу. Столбец должен быть в индексе и
должен немедленно следовать за столбцами в GROUP BY
.GROUP BY
должны быть константами (то есть, на них нужно сослаться в равенствах с
константами), за исключением параметра функций
MIN()
или
MAX()
.c1 VARCHAR(20), INDEX (c1(10))
индекс не может использоваться
для свободного просмотра.Если свободный просмотр, применим к запросу,
EXPLAIN
показывает
Using index for group-by
в столбце Extra
.
Предположите, что есть индекс idx(c1,c2,c3)
на таблице
t1(c1,c2,c3,c4)
. Свободный просмотр может использоваться
для следующих запросов:
SELECT c1, c2 FROM t1 GROUP BY c1, c2; SELECT DISTINCT c1, c2 FROM t1; SELECT c1, MIN(c2) FROM t1 GROUP BY c1; SELECT c1, c2 FROM t1 WHERE c1 <const
GROUP BY c1, c2; SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 >const
GROUP BY c1, c2; SELECT c2 FROM t1 WHERE c1 <const
GROUP BY c1, c2; SELECT c1, c2 FROM t1 WHERE c3 =const
GROUP BY c1, c2;
Следующие запросы не могут быть выполнены с этим методом по приведенным причинам:
Есть совокупные функции кроме
MIN()
или
MAX()
:
SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
GROUP BY
не формируют крайний левый
префикс из индекса:
SELECT c1, c2 FROM t1 GROUP BY c2, c3;
Запрос обращается к части ключа, которая после
GROUP BY
и для которой нет никакого равенства с константой:
SELECT c1, c3 FROM t1 GROUP BY c1, c2;
Если был запрос, чтобы включать WHERE c3=
, свободный индексный просмотр мог использоваться.const
Метод доступа может быть применен к другим формам совокупных
функциональных ссылок в избранном списке, в дополнение к
MIN()
и
MAX()
:
AVG(DISTINCT)
,
SUM(DISTINCT)
и
COUNT(DISTINCT)
поддерживаются. AVG(DISTINCT)
и SUM(DISTINCT)
берут единственный параметр.
COUNT(DISTINCT)
может иметь в параметре больше одного столбца.
GROUP BY
или DISTINCT
в запросе.
Предположите, что есть индекс idx(c1,c2,c3)
на таблице
t1(c1,c2,c3,c4)
. Свободный просмотр может использоваться
для следующих запросов:
SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1; SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;
Свободный просмотр не может использоваться для следующих запросов:
SELECT DISTINCT COUNT(DISTINCT c1) FROM t1; SELECT COUNT(DISTINCT c1) FROM t1 GROUP BY c1;
Трудный индексный просмотр может быть полным просмотром или просмотром диапазона, в зависимости от условий запроса.
Когда условия для свободного индексного просмотра не встречены,
все еще может быть возможно избежать создания временных таблиц для
GROUP BY
. Если есть условия диапазона в WHERE
,
этот метод читает только ключи, которые удовлетворяют этим условиям.
Иначе это выполняет индексный просмотр. Поскольку этот метод читает все ключи
в каждом диапазоне, определенном WHERE
, или просматривает
весь индекс, если нет никаких условий диапазона, мы называем это
трудный индексный просмотр. С трудным индексным просмотром
группировка выполнена только после того, как все ключи, которые удовлетворяют
условиям диапазона, были найдены.
Для этого метода достаточно, что есть постоянное условие равенства для
всех столбцов в запросе, обращающемся к частям ключа,
прежде или между частями ключа GROUP BY
.
Константы от условий равенства заполняют любой
промежуток в ключах поиска так, чтобы было возможно
сформировать полные префиксы индекса. Эти префиксы могут использоваться для
индексных поисков. Если мы требуем сортировки результата
GROUP BY
и возможно сформировать ключи поиска, которые являются
префиксами индекса, MySQL также избегает дополнительных операций сортировки,
потому что поиск с префиксами в упорядоченном индексе уже получает все
ключи в нужном порядке.
Предположите, что есть индекс idx(c1,c2,c3)
на таблице
t1(c1,c2,c3,c4)
. Следующие запросы не работают со свободным
просмотром, описанный ранее, но работают с трудным индексным просмотром.
Есть промежуток в GROUP BY
, но это покрыто условием
c2 = 'a'
:
SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
GROUP BY
не начинается с первой части ключа, но есть
условие, которое обеспечивает константу для той части:
SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;
DISTINCT
с ORDER BY
нуждается во временной таблице во многих случаях.
Поскольку DISTINCT
может использовать GROUP BY
,
изучите, как MySQL работает со столбцами в
ORDER BY
или HAVING
, которые не являются частью
выбранных столбцов. См. раздел 13.19.3
.
В большинстве случаев DISTINCT
можно рассмотреть как особый случай GROUP BY
.
Например, следующие два запроса эквивалентны:
SELECT DISTINCT c1, c2, c3 FROM t1 WHERE c1 >const
; SELECT c1, c2, c3 FROM t1 WHERE c1 >const
GROUP BY c1, c2, c3;
Из-за этой эквивалентности, оптимизация, применимая к
GROUP BY
, может быть также применена к запросам с
DISTINCT
. Таким образом, для большего количества деталей о
возможностях оптимизации для DISTINCT
см.
раздел 9.2.1.16.
Объединяя LIMIT
с
row_count
DISTINCT
, MySQL останавливается, как только находит
row_count
уникальных строк.
Если Вы не используете столбцы от всех таблиц, названных в запросе, MySQL
прекращает просматривать любые неиспользованные таблицы, как только он
находит первое соответствие. В следующем случае, предполагая что
t1
используется прежде t2
(с чем Вы можете свериться с помощью
EXPLAIN
), MySQL
прекращает читать из t2
(для любой особой строки в
t1
), когда это находит первую строку в t2
:
SELECT DISTINCT t1.a FROM t1, t2 where t1.a=t2.a;
Оптимизатор запроса MySQL имеет различные стратегии, чтобы
оценить подзапросы. Для IN
(или =ANY
)
у оптимизатора есть этот выбор:
Semi-join
EXISTS
Для NOT IN
(или <>ALL
)
у оптимизатора есть этот выбор:
Материализация
EXISTS
Для полученных таблиц (подзапросы в FROM
)
и ссылок представления у оптимизатора есть этот выбор:
Слить полученную таблицу или представление во внешний блок запроса.
Следующее обсуждение предоставляет больше информации об этих стратегиях оптимизации.
Ограничение UPDATE
и
DELETE
, которые используют
подзапрос, чтобы изменить единственную таблицу, это когда оптимизатор не
использует полусоединение или оптимизацию подзапроса материализации.
Как обходное решение, попытайтесь переписать их как многотабличный
UPDATE
и
DELETE
, которые используют
соединение, а не подзапрос.
Оптимизатор использует стратегии полусоединения, чтобы улучшить выполнение подзапроса, как описано в этом разделе.
Для внутреннего соединения между двумя таблицами соединение возвращает
строку из одной таблицы так много раз, сколько есть соответствий в другой
таблице. Но для некоторых запросов, единственная информация, это есть ли
соответствие, а не число соответствий. Предположите, что есть таблицы
class
и roster
, перечисляющие классы в
программе курса и спискм класса (студенты, зарегистрированные в каждом
классе), соответственно.Чтобы перечислить классы, которым фактически
зарегистрировали студентов, Вы могли бы использовать это соединение:
SELECT class.class_num, class.class_name FROM class INNER JOIN roster WHERE class.class_num = roster.class_num;
Однако, результат перечисляет каждый класс однажды для каждого зарегистрированного студента. Для вопроса, который задают, это ненужное дублирование информации.
Будем считать, что class_num
это primary key в таблице
class
, подавление дубликатов могло быть достигнуто при
использовании SELECT DISTINCT
,
но это неэффективно, чтобы произвести все строки соответствия сначала только,
чтобы устранить дубликаты позже.
Тот же самый результат может быть получен при использовании подзапроса:
SELECT class_num, class_name FROM class WHERE class_num IN (SELECT class_num FROM roster);
Здесь, оптимизатор может признать, что IN
требует, чтобы подзапрос возвратил только один случай каждого
классификационного индекса от таблицы roster
.
В этом случае запрос может быть выполнен как semi-join,
то есть, работа, которая возвращает только один случай каждой строки в
class
, который является
соответствующим строкам в roster
.
Outer join и inner join разрешены во внешней спецификации запроса, и табличные ссылки могут быть базовыми таблицами или представлениями.
В MySQL подзапрос должен удовлетворить эти критерии, которые будут обработаны как полусоединение:
Это должно быть IN
(или =ANY
), который
появляется на верхнем уровне WHERE
или ON
,
возможно, как термин в AND
:
SELECT ... FROM ot1, ... WHERE (oe1, ...) IN (SELECT ie1, ... FROM it1, ... WHERE ...);
Здесь ot_
и
i
it_
представляют таблицы во внешних и внутренних частях запроса, а
i
oe_
и
i
ie_
представляют выражения, которые
обращаются к столбцам во внешних и внутренних таблицах.i
SELECT
без
UNION
.GROUP BY
или HAVING
.
ORDER BY
с LIMIT
.STRAIGHT_JOIN
во внешнем запросе.STRAIGHT_JOIN
не присутствует.Подзапрос может быть коррелированый или некоррелированый.
DISTINCT
разрешен, как LIMIT
, если
также используется и ORDER BY
.
Если подзапрос соответствует предыдущим критериям, MySQL преобразовывает его в полусоединение и делает выбор на основе издержек из этих стратегий:
Преобразуйте подзапрос в соединение или используйте табличное отступление и выполните запрос как внутреннее соединение между таблицами подзапроса и внешними таблицами. Табличное отступление вытягивает таблицу из подзапроса к внешнему запросу.
Каждая из этих стратегий может быть включена с использованием переменной
optimizer_switch
. Флаг semijoin
управляет, используются ли полусоединения.
Если это установлено в on
, то флаги firstmatch
,
loosescan
, duplicateweedout
и
materialization
включают более гладкое управление разрешенными
стратегиями полусоединения. Эти флаги on
по умолчанию. См.
раздел 9.9.2.
Если стратегия duplicateweedout
выключена, она не
используется, если все другие применимые стратегии также не отключены.
Если duplicateweedout
выключена, при случае оптимизатор может
произвести план запроса, который совсем не оптимален. Это происходит из-за
эвристического сокращения во время поиска, которого можно избежать,
устанавливая
optimizer_prune_level=0
.
Оптимизатор минимизирует различия в обработке представлений и
подзапросов в FROM
. Это затрагивает запросы с
STRAIGHT_JOIN
и представления с подзапросом IN
,
которые могут быть преобразованы в полусоединение. Следующий запрос
иллюстрирует это, потому что изменение в обработке причина изменений в
преобразовании, и таким образом применена иная стратегия выполнения:
CREATE VIEW v AS SELECT * FROM t1 WHERE a IN (SELECT b FROM t2); SELECT STRAIGHT_JOIN * FROM t3 JOIN v ON t3.x = v.a;
Оптимизатор сначала смотрит представление и конвертирует подзапрос
IN
в полусоединение, затем проверяет, возможно ли слить
представление во внешний запрос. Поскольку STRAIGHT_JOIN
во внешнем запросе предотвращает полусоединение, оптимизатор отказывается от
слияния, заставляя полученную таблицу быть оцененной,
используя осуществленную таблицу.
Использование стратегий полусоединения обозначено в выводе
EXPLAIN
так:
Таблицы, к которым полуприсоединяются, обнаруживаются во внешнем
select. EXPLAIN EXTENDED
и SHOW WARNINGS
показывает переписанный запрос, который выводит структуру полусоединения. От
этого Вы можете понять, которые таблицы были вытащены из полусоединения.
Если подзапрос был преобразован в полусоединение, Вы будете видеть, что
предиката подзапроса не стало, и его таблицы и WHERE
были слиты во внешний список соединения запроса и WHERE
.
Start temporary
и End temporary
в столбце
Extra
. Таблицы, которые не были вытащены и находятся в диапазоне
выходных строк EXPLAIN
, покрытые
Start temporary
и End temporary
,
будет иметь их rowid
во временной таблице.FirstMatch(tbl_name
)
в столбце Extra
указывает на сокращенное соединение.LooseScan(m
..n
)
в столбце Extra
указывает на использование стратегии LooseScan.
m
и n
номера ключевой части.select_type
= MATERIALIZED
и
table
= <subqueryN
>
.
Оптимизатор использует материализацию подзапроса в качестве стратегии, которая включает более эффективную обработку подзапроса. Материализация ускоряет выполнение запроса, производя результат подзапроса как временную таблицу, обычно в памяти. В первый раз, когда MySQL нуждается в результате подзапроса, он осуществляет этот результат во временную таблицу. Когда позже результат необходим, MySQL обращается снова к временной таблице. Таблица индексирована с хешем, чтобы сделать поиски быстрыми и недорогими. Индексирование уникально, что делает таблицу меньше, потому что у этого нет никаких дубликатов.
Материализация подзапроса пытается использовать временную таблицу в памяти, когда возможно отступая к хранению на диске, если таблица становится слишком большой. См. раздел 9.4.4.
Если материализация не используется, оптимизатор иногда переписывает
некоррелированый подзапрос как коррелированый подзапрос.
Например, следующий подзапрос IN
некоррелирован
(where_condition
вовлекает только столбцы от
t2
, но не от t1
):
SELECT * FROM t1 WHERE t1.a IN (SELECT t2.b FROM t2
WHERE where_condition
);
Оптимизатор мог бы переписать это как
коррелированый подзапрос EXISTS
:
SELECT * FROM t1 WHERE EXISTS (SELECT t2.b FROM t2
WHERE where_condition
AND t1.a=t2.b);
Материализация подзапроса, используя временную таблицу, избегает таких перезаписей и позволяет выполнить подзапрос только однажды, а не однажды на строку внешнего запроса.
Для материализации подзапроса, которая будет использоваться в MySQL,
флаг materialization
в переменной
optimizer_switch
должен быть on
. Материализация тогда применяется к
предикатам подзапроса, которые появляются где угодно (в избранном списке,
WHERE
, ON
, GROUP BY
,
HAVING
или ORDER BY
)
для предикатов, которые попадают в любой из этих случаев использования:
У предиката есть эта форма, когда никакое внешнее выражение
oe_i
или внутреннее выражение
ie_i
может быть null.
N
= 1 или больше.
(oe_1
,oe_2
, ...,oe_N
) [NOT] IN (SELECTie_1
,i_2
, ...,ie_N
...)
oe
и внутреннее выражение ie
.
Выражения могут быть null.
oe
[NOT] IN (SELECTie
...)
IN
или NOT IN
и результат
UNKNOWN
(NULL
) имеет то же самое
значение в результате FALSE
.Следующие примеры иллюстрируют как требование для эквивалентности
оценки предиката UNKNOWN
и FALSE
затрагивает, может ли материализация подзапроса использоваться. Примите, что
where_condition
вовлекает столбцы только от
t2
, но не из t1
так, чтобы
подзапрос был некоррелирован.
Этот запрос подвергается материализации:
SELECT * FROM t1 WHERE t1.a IN (SELECT t2.b FROM t2
WHERE where_condition
);
Здесь не имеет значения, возвращает предикат IN
UNKNOWN
или FALSE
. Так или иначе, строка от
t1
не включена в результат запроса.
Примером, где материализация подзапроса не будет использоваться, является
следующий запрос, где t2.b
столбец nullable.
SELECT * FROM t1 WHERE (t1.a,t1.b) NOT IN (SELECT t2.a,t2.b FROM t2
WHERE where_condition
);
Следующие ограничения относятся к использованию материализации подзапроса:
Типы внутренних и внешних выражений должны соответствовать. Например, оптимизатор может быть в состоянии использовать материализацию, если оба выражения integer или оба являются decimal. Оптимизатор не может использовать материализацию, если одно выражение integer, а другое decimal.
BLOB
.Применение EXPLAIN
с
запросом может дать некоторый признак того, использует ли оптимизатор
материализацию подзапроса. Сравненный с выполнением запроса, которое не
использует материализацию, select_type
может измениться с
DEPENDENT SUBQUERY
на SUBQUERY
.
Это указывает, что для подзапроса, который был бы выполнен однажды на внешнюю
строку, материализация позволяет подзапросу быть выполненным только однажды.
Кроме того, для EXPLAIN
EXTENDED
текст, выведенный на экран
SHOW WARNINGS
, включает
materialize
и materialized-subquery
.
Оптимизатор может обработать полученные таблицы (подзапросы в
FROM
) и ссылки представления, используя две стратегии:
Слить полученную таблицу или представление во внешний блок запроса.
Пример 1:
SELECT * FROM (SELECT * FROM t1) AS derived_t1;
Со слиянием этот запрос выполнен так:
SELECT * FROM t1;
Пример 2:
SELECT * FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2 ON t1.f2=derived_t2.f1 WHERE t1.f1 > 0;
Со слиянием этот запрос выполнен так:
SELECT t1.*, t2.f1 FROM t1 JOIN t2 ON t1.f2=t2.f1 WHERE t1.f1 > 0;
С материализацией derived_t1
и derived_t2
обработаны как отдельная таблица в пределах их соответствующих запросов.
Оптимизатор обрабатывает полученные таблицы и представления тем же самым путем: это избегает ненужной материализации когда бы ни было возможно, которая позволяет оттолкнуть условия от внешнего запроса до полученных таблиц и производит более эффективные планы выполнения. Для примера см. раздел 9.2.1.18.2 .
Если слияние привело бы к внешнему блоку запроса, который ссылается больше, чем на 61 базовую таблицу, оптимизатор выбирает материализацию вместо этого.
Оптимизатор обрабатывает распространение ORDER BY
в полученной таблице или ссылке представления на блок внешнего запроса,
размножая ORDER BY
, если следующие условия применяются: внешний
запрос не сгруппирован или соединен, не определяет
DISTINCT
, HAVING
или ORDER BY
,
имеет эту полученную таблицу или ссылку представления как единственный
источник в FROM
. Иначе оптимизатор игнорирует ORDER BY
.
Подсказки оптимизатора MERGE
и NO_MERGE
могут использоваться, чтобы влиять, пытается ли оптимизатор слить полученные
таблицы и представления во внешний блок запроса, предполагая, что никакое
другое правило не предотвращает слияние. См.
раздел 9.9.3. Флаг
derived_merge
переменной
optimizer_switch
может также использоваться для этого. По умолчанию флаг
on
, чтобы позволять слиться. Установка флага в off
предотвращает слияние и избегает ошибок
ER_UPDATE_TABLE_USED
. Также возможно отключить слияние при использовании в подзапросе
любых конструкций, которые предотвращают слияние, хотя они не являются столь
явными в их эффекте на материализацию. Конструкции, которые предотвращают
слияние, являются теми же самыми, которые предотвращают слияние в
представлениях. Примеры SELECT DISTINCT
или LIMIT
в подзапросе. Для деталей см.
раздел 21.5.2.
Флаг derived_merge
также относится к представлениям, которые
не содержат параметр ALGORITHM
. Таким образом, если ошибка
ER_UPDATE_TABLE_USED
происходит для ссылки представления, которая использует
выражение, эквивалентное подзапросу, добавляя
ALGORITHM=TEMPTABLE
к представлению, определение предотвращает
слияние и имеет приоритет пеед текущим значением derived_merge
.
Если оптимизатор выбирает стратегию материализации полученной таблицы, это обрабатывает запрос следующим образом:
Оптимизатор откладывает материализацию подзапросов в
FROM
до того, как их содержание будет необходимо во время
выполнения запроса. Это улучшает работу, потому что задержка материализации
может привести к отсутствию необходимости сделать это вообще. Рассмотрите
запрос, который присоединяется к результату подзапроса в
FROM
к другой таблице: если оптимизатор обрабатывает другую
таблицу сначала и находит, что та не возвращает строк, соединение не должно
быть выполнено далее, и оптимизатор может полностью
пропустить осуществление подзапроса.
Рассмотрите следующий EXPLAIN
для которого подзапрос появляется в FROM
запроса
a SELECT
:
EXPLAIN SELECT * FROM (SELECT * FROM t1) AS derived_t1;
Оптимизатор избегает осуществлять подзапрос, задерживая это, пока
результат не станет необходим во время SELECT
. В этом случае запрос не выполнен, таким образом, результат
никогда не будет необходим.
Даже для запросов, которые выполнены, задержка материализации подзапроса
может позволить оптимизатору избежать материализации полностью.
Когда это происходит, выполнение запроса более быстро к тому времени, когда
необходимо выполнить материализацию. Рассмотрите следующий запрос,
который присоединяется к результату подзапроса в
FROM
к другой таблице:
SELECT * FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2 ON t1.f2=derived_t2.f1 WHERE t1.f1 > 0;
Если оптимизация обрабатывает t1
сначала и
WHERE
приводит к пустому результату, соединение должно
обязательно быть пустым, и подзапрос не должен быть осуществлен.
Для случаев, когда полученная таблица требует материализации, оптимизатор
может ускорить доступ к результату, добавляя индекс к осуществленной таблице.
Если такой индекс включает доступ
ref
к таблице, это может очень уменьшить объем данных, который
должен быть считан во время выполнения запроса. Рассмотрите следующий запрос:
SELECT * FROM t1 JOIN (SELECT DISTINCT f1 FROM t2) AS derived_t2 ON t1.f1=derived_t2.f1;
Оптимизатор создает индекс по столбцу f1
от
derived_t2
, если выполнение включило бы использование доступа
ref
для самого низкого по стоимости плана выполнения. После добавления индекса
оптимизатор может обработать осуществленную полученную таблицу, как
регулярную таблицу с индексом, и это извлекает выгоду из произведенного
индекса. Если доступ ref
привел бы к более высокой стоимости, чем некоторый другой метод доступа,
оптимизатор не создает индекс и ничего не теряет.
Определенная оптимизация применима к сравнениям, которые используют
оператор IN
, чтобы проверить результаты подзапроса (или
использующие =ANY
). Этот раздел обсуждает эту оптимизацию,
особенно относительно проблем со значениями NULL
.
Последняя часть обсуждения включает предложения о том, что Вы можете сделать,
чтобы помочь оптимизатору.
Рассмотрите следующее сравнение подзапроса:
outer_expr
IN (SELECTinner_expr
FROM ... WHEREsubquery_where
)
MySQL оценивает запросы снаружи внутрь.
Таким образом, это сначала получает значение внешнего выражения
outer_expr
, затем выполняет подзапрос и получает
строки, которые он производит.
Очень полезная оптимизация информировать
подзапрос, что единственные интересные строки это те, где внутреннее
выражение inner_expr
равно outer_expr
. Это сделано, отталкивая соответствующее равенство в подзапрос
WHERE
. Таким образом, сравнение преобразовано в это:
EXISTS (SELECT 1 FROM ... WHEREsubquery_where
ANDouter_expr
=inner_expr
)
После преобразования MySQL может использовать продвинутое вниз равенство, чтобы ограничить число строк, которые это должно исследовать, оценивая подзапрос.
Более широко, сравнение N
значений с
подзапросом, который возвращает N
строк,
подвергается тому же самому преобразованию. Если oe_i
и
ie_i
представляют соответствующие внешние и внутренние
значения выражения, это сравнение подзапроса:
(oe_1
, ...,oe_N
) IN (SELECTie_1
, ...,ie_N
FROM ... WHEREsubquery_where
)
становится:
EXISTS (SELECT 1 FROM ... WHEREsubquery_where
ANDoe_1
=ie_1
AND ... ANDoe_N
=ie_N
)
Для простоты следующее обсуждение принимает единственную пару внешних и внутренних значений выражения.
У только что описанного преобразования есть свои ограничения.
Это допустимо, только если мы игнорируем возможный NULL
.
Таким образом, стратегия pushdown работает,
пока оба эти условия являются истиной:
outer_expr
и
inner_expr
не NULL
.
NULL
и FALSE
в
результатах подзапроса. Если подзапрос часть
OR
или AND
в WHERE
, MySQL предполагает, что Вы их не различаете.
Другой случай, где оптимизатор замечает, что результаты подзапроса
NULL
и FALSE
нельзя отличить, это конструкция:
... WHEREouter_expr
IN (subquery
)
В этом случае WHERE
отклоняет строку
IN (
независимо от того, вернет
она subquery
)NULL
или FALSE
.
Когда одно или оба из этих условий не выполнены, оптимизация более сложна.
Предположите, что outer_expr
значение не
NULL
, но подзапрос не производит строку таким образом, что
outer_expr
= inner_expr
.
оценивается следующим образом:outer_expr
IN (SELECT ...)
NULL
, если SELECT
производит любую строку, где
inner_expr
= NULL
.
FALSE
, если SELECT
производит значения только не NULL
или не производит вовсе.
В этой ситуации поиск строк с
больше не действителен. Необходимо
искать такие строки, но если ни одна не найдена, также искать строки, где
outer_expr
=
inner_expr
inner_expr
= NULL
.
Примерно говоря, подзапрос может быть преобразован во что-то вроде этого:
EXISTS (SELECT 1 FROM ... WHEREsubquery_where
AND (outer_expr
=inner_expr
ORinner_expr
IS NULL))
Потребность оценить дополнительно
IS NULL
причина того, почему MySQL имеет метод доступа
ref_or_null
:
mysql> EXPLAIN SELECT outer_expr
IN
-> (SELECT t2.maybe_null_key FROM t2, t3 WHERE ...)
-> FROM t1;
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: t1
...
*************************** 2. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: t2
type: ref_or_null
possible_keys: maybe_null_key
key: maybe_null_key
key_len: 5
ref: func
rows: 2
Extra: Using where; Using index
...
У определенных для подзапроса методов доступа
unique_subquery
и
index_subquery
также есть версия or NULL
.
Дополнительное условие OR ... IS NULL
делает выполнение запроса немного более сложным (и некоторая оптимизация в
пределах подзапроса становится неподходящей), но вообще это терпимо.
Ситуация намного хуже, когда
outer_expr
может быть NULL
.
Согласно интерпретации SQL NULL
как неизвестная
величина, NULL IN (SELECT
должен оцениться как:inner_expr
...)
Для надлежащей оценки необходимо быть в состоянии проверить, произвел ли
SELECT
любые строки вообще, таким
образом,
не может быть оттолкнут в подзапрос.
Это проблема, потому что много подзапросов реального мира становятся очень
медленными, если равенство не может быть оттолкнуто.outer_expr
=
inner_expr
По существу должны быть различные способы выполнить подзапрос в
зависимости от значения outer_expr
.
Оптимизатор предпочитает согласие SQL скорости, таким образом, это
составляет возможность, что outer_expr
может быть NULL
.
Если outer_expr
= NULL
,
чтобы оценить следующее выражение, необходимо выполнить
SELECT
, чтобы
определить, производит ли это какие-либо строки:
NULL IN (SELECTinner_expr
FROM ... WHEREsubquery_where
)
Необходимо выполнить оригинал
SELECT
без любых продвинутых вниз равенств, упомянутых ранее.
С другой стороны, когда
outer_expr
не NULL
,
абсолютно важно, что это сравнение:
outer_expr
IN (SELECTinner_expr
FROM ... WHEREsubquery_where
)
будет преобразовано в это выражение, которое использует продвинутое вниз условие:
EXISTS (SELECT 1 FROM ... WHEREsubquery_where
ANDouter_expr
=inner_expr
)
Без этого преобразования подзапросы будут медленными. Чтобы решить дилемму того, оттолкнуть или не оттолкнуть условия в подзапрос, условия обернуты в функции триггера. Таким образом, выражение следующей формы:
outer_expr
IN (SELECTinner_expr
FROM ... WHEREsubquery_where
)
преобразовано в:
EXISTS (SELECT 1 FROM ... WHEREsubquery_where
AND trigcond(outer_expr
=inner_expr
))
Более широко, если сравнение подзапроса основано на нескольких парах внешних и внутренних выражений, преобразование берет это сравнение:
(oe_1
, ...,oe_N
) IN (SELECTie_1
, ...,ie_N
FROM ... WHEREsubquery_where
)
и приводит к этому выражению:
EXISTS (SELECT 1 FROM ... WHEREsubquery_where
AND trigcond(oe_1
=ie_1
) AND ... AND trigcond(oe_N
=ie_N
))
trigcond(
это специальная функция,
которая оценивается к следующим значениям:X
)
X
, когда
связанное внешнее выражение
oe_i
не NULL
.
TRUE
, когда связанное
внешнее выражение oe_i
NULL
.Триггерные функции это не триггеры в смысле
запроса CREATE TRIGGER
.
Равенства, которые обернуты в trigcond()
, это
не предикаты первого класса для оптимизатора. Большинство оптимизации не
может иметь дело с предикатами, которые могут быть включены во время
выполнения запроса, таким образом, они принимают любую
trigcond(
как
неизвестную функцию и проигнорируют это. В настоящее время вызванные
равенства могут использоваться этой оптимизацией:X
)
Ссылочная оптимизация: trigcond(
может использоваться, чтобы создать табличные доступы
X
=
Y
[OR Y
IS NULL])ref
,
eq_ref
или
ref_or_null
.
trigcond(X
=Y
)
может использоваться, чтобы создать доступы
unique_subquery
или index_subquery
.Когда оптимизатор использует вызванное условие, чтобы создать некоторый
основанный на индексном поиске доступ (что касается первых двух элементов
предыдущего списка), у этого должна быть стратегия отступления для случая,
когда условие выключено. Эта стратегия отступления всегда сделать полное
сканирование таблицы. В выводе EXPLAIN
это обнаруживается как Full scan on NULL key
в столбце
Extra
:
mysql> EXPLAIN SELECT t1.col1, t1.col1 IN -> (SELECT t2.key1 FROM t2 WHERE t2.col2=t1.col2) FROM t1\G *************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 ... *************************** 2. row *************************** id: 2 select_type: DEPENDENT SUBQUERY table: t2 type: index_subquery possible_keys: key1 key: key1 key_len: 5 ref: func rows: 2 Extra: Using where; Full scan on NULL key
Если Вы выполняете EXPLAIN
EXTENDED
и SHOW WARNINGS
, Вы можете видеть вызванное условие:
*************************** 1. row *************************** Level: Note Code: 1003 Message: select `test`.`t1`.`col1` AS `col1`, <in_optimizer>(`test`.`t1`.`col1`, <exists>(<index_lookup>(<cache>(`test`.`t1`.`col1`) in t2 on key1 checking NULL where (`test`.`t2`.`col2` = `test`.`t1`.`col2`) having trigcond(<is_not_null_test>(`test`.`t2`.`key1`))))) AS `t1.col1 IN (select t2.key1 from t2 where t2.col2=t1.col2)` from `test`.`t1`
У использования вызванных условий есть некоторые исполнительные значения.
Выражение NULL IN (SELECT ...)
теперь может вызвать полное
сканирование таблицы (которое является медленным), когда оно ранее не
сделало. Это цена, заплаченная за правильные результаты (цель стратегии более
аккуратного условия состояла в том, чтобы улучшить согласие, а не скорость).
Для многотабличных подзапросов выполнение NULL IN (SELECT ...)
будет особенно медленным, потому что соединение оптимизатор не
оптимизирует для случая, где внешнее выражение NULL
.
Это принимает, что такие подзапросы с NULL
на левой стороне
очень редки, даже если есть статистические данные, которые указывают иное.
С другой стороны, если внешнее выражение могло бы быть NULL
,
но никогда фактически не будет, нет никакого исполнительного штрафа.
Чтобы помочь оптимизатору лучше выполнять Ваши запросы, используют эти подсказки:
Объявите столбец как NOT NULL
, если это действительно
так. Это также помогает другим аспектам оптимизатора, упрощая тестирование
условия для столбца.
NULL
от FALSE
,
Вы можете легко избежать медленного пути выполнения. Замените сравнение,
которое похоже на это:
outer_expr
IN (SELECTinner_expr
FROM ...)
вот на такое:
(outer_expr
IS NOT NULL) AND (outer_expr
IN (SELECTinner_expr
FROM ...))
Тогда NULL IN (SELECT ...)
никогда не будет оцениваться, потому что MySQL прекращает оценивать часть
AND
как только результат выражения ясен.
Другая возможная перезапись:
EXISTS (SELECTinner_expr
FROM ... WHEREinner_expr
=outer_expr
)
Это применилось бы, когда Вы не должны различить NULL
и
FALSE
, когда Вы можете фактически хотеть EXISTS
.
Флаг subquery_materialization_cost_based
включает управление выбором между материализацией подзапроса и
преобразованием подзапроса IN
-to-EXISTS
. См.
раздел 9.9.2.
Если Вы нуждаетесь только в конкретном количестве строк от набора
результатов, используйте LIMIT
в запросе, вместо того, чтобы
принести целый набор результатов и выбросить дополнительные данные.
MySQL иногда оптимизирует запрос, у которого есть LIMIT
, но нет row_count
HAVING
:
Если Вы выбираете только несколько строк с LIMIT
,
MySQL использует индексы в некоторых случаях, когда обычно это предпочло бы
делать полное сканирование таблицы.
LIMIT row_count
с
ORDER BY
, MySQL заканчивает сортировку, как только это нашло
первые row_count
строк сортированного результата
вместо того, чтобы сортировать весь результат. Если упорядочивание сделано
при использовании индексирования, это очень быстро.
Если filesort должен быть сделан, все строки, которые соответствуют запросу
без LIMIT
, выбраны и часть (или все они) отсортированы до того,
как найдутся первые row_count
.
После того, как начальные строки были найдены, MySQL не сортирует
остаток набора результатов.
Одно проявление этого поведения то, что ORDER BY
с и без LIMIT
может возвратить строки в различном порядке, как
описано позже в этом разделе.
LIMIT row_count
с
DISTINCT
, MySQL останавливается, как только он находит
row_count
уникальных строк.GROUP BY
может быть решен, читая индексирование в порядке (или делая сортировку на
индексе) и затем вычисляя резюме до изменений значения индекса. В этом случае
LIMIT row_count
не вычисляет
значения GROUP BY
.SQL_CALC_FOUND_ROWS
.
Число строк может тогда быть получено с SELECT FOUND_ROWS()
. См.
раздел 13.14.LIMIT 0
быстро возвращает пустой набор. Это может быть
полезно для проверки законности запроса. Это может также использоваться,
чтобы получить типы столбцов результата, если Вы используете MySQL API,
который делает доступными метаданные набора результатов. С
mysql
Вы можете использовать опцию
--column-type-info
, чтобы вывести на экран типы столбца результата.LIMIT row_count
, чтобы
вычислить, сколько пространства требуется.Если у многих строк есть идентичные значения в ORDER BY
,
сервер свободен возвратить те строки в любом порядке, и может сделать
по-другому в зависимости от полного плана выполнения. Другими словами,
порядок сортировки тех строк недетерминирован
относительно неупорядоченных столбцов.
Один фактор, который затрагивает план выполнения,
LIMIT
, так что ORDER BY
с или без
LIMIT
может возвратить строки в различных порядках.
Рассмотрите этот запрос, который отсортирован по столбцу
category
, но недетерминирован относительно
столбцов id
и rating
:
mysql> SELECT * FROM ratings ORDER BY category; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 3 | 2 | 3.7 | | 4 | 2 | 3.5 | | 6 | 2 | 3.5 | | 2 | 3 | 5.0 | | 7 | 3 | 2.7 | +----+----------+--------+
Включение LIMIT
может затронуть порядок строк в пределах
каждого значения category
.
Например, это допустимый результат запроса:
mysql> SELECT * FROM ratings ORDER BY category LIMIT 5; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 4 | 2 | 3.5 | | 3 | 2 | 3.7 | | 6 | 2 | 3.5 | +----+----------+--------+
В каждом случае строки отсортированы по столбцу ORDER BY
,
который является всем, что требуется стандартом SQL.
Если важно гарантировать тот же самый порядок строк и без
LIMIT
, включайте дополнительные столбцы в ORDER BY
,
чтобы сделать детерминированный порядок. Например, если значения
id
уникальны, Вы можете сделать строки для данного
category
, сортируя их по id
:
mysql> SELECT * FROM ratings ORDER BY category, id; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 3 | 2 | 3.7 | | 4 | 2 | 3.5 | | 6 | 2 | 3.5 | | 2 | 3 | 5.0 | | 7 | 3 | 2.7 | +----+----------+--------+ mysql> SELECT * FROM ratings ORDER BY category, id LIMIT 5; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 3 | 2 | 3.7 | | 4 | 2 | 3.5 | | 6 | 2 | 3.5 | +----+----------+--------+
Оптимизатор действительно обрабатывает запросы (и подзапросы) следующей формы:
SELECT ... FROMsingle_table
... ORDER BYnon_index_column
[DESC] LIMIT [M
,]N
;
Эот тип запроса распространен в веб-приложениях, которые выводят на экран только несколько строк от большего набора результатов. Например:
SELECT col1, ... FROM t1 ... ORDER BY name LIMIT 10; SELECT col1, ... FROM t1 ... ORDER BY RAND() LIMIT 15;
У буфера сортировки есть размер
sort_buffer_size
. Если элементы для N
строк
являются достаточно небольшими, чтобы поместиться в буфер
(M
+N
строк, если
M
указан), сервер может избегать использования файла
слияния и выполнить сортировку полностью в памяти, обрабатывая буфер вида
как приоритетную очередь:
Просмотрите таблицу, вставляя избранные столбцы списка от каждой выбранной строки в сортированном порядке в очередь. Если очередь полна, выталкивается последняя строка в порядке сортировки.
N
строк от очереди. Если
задан M
, пропустите первые M
строк и возвращайте следующие N
строк.Ранее сервер выполнил эту работу при использовании файла слияния для сортировки:
Просмотрите таблицу, повторяя эти шаги до конца таблицы:
Выберите строки, пока буфер не заполнен.
N
строк в буфере
(M
+N
строк, если задан M
) в файл слияния.N
строк. Если задан M
, пропустите M
строк и верните следующие N
строк.Стоимость сканирования таблицы та же самая для методов очереди и файла слияния, таким образом, оптимизатор выбирает между методами, основанными на других затратах:
Метод очереди вовлекает больше ресурсов центрального процессора для того, чтобы вставить строки в очередь в нужном порядке.
Оптимизатор рассматривает баланс между этими факторами для особых
значений N
и размера строки.
Конструкторы строки разрешают одновременные сравнения многократных значений. Например, эти два запроса семантически эквивалентны:
SELECT * FROM t1 WHERE (column1,column2) = (1,1); SELECT * FROM t1 WHERE column1 = 1 AND column2 = 1;
Кроме того, оптимизатор обрабатывает оба выражения одинаково.
Оптимизатор, менее вероятно, будет использовать доступ к индексу, если
столбцы конструктора строки не покрывают префикс индекса. Рассмотрите
следующую таблицу, у которой есть первичный ключ на
(c1, c2, c3)
:
CREATE TABLE t1 (c1 INT, c2 INT, c3 INT, c4 CHAR(100), PRIMARY KEY(c1,c2,c3));
В этом запросе WHERE
использует все столбцы в
индексировании. Однако, конструктор самой строки не покрывает префикс
индекса, так что в итоге оптимизатор использует только
c1
(key_len=4
, размер c1
):
mysql> EXPLAIN SELECT * FROM t1 -> WHERE c1=1 AND (c2,c3) > (1,1)\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: const rows: 3 filtered: 100.00 Extra: Using where
В таких случаях перезапись выражения конструктора строки, используя эквивалентное выражение неконструктора может привести к более полному использованию индекса. Для данного запроса конструктор строки и эквивалентные выражения неконструктора:
(c2,c3) > (1,1) c2 > 1 OR ((c2 = 1) AND (c3 > 1))
Перезапись запроса, чтобы использовать выражение неконструктора приводит
к оптимизатору, использующему все три столбца в индексе
(key_len=12
):
mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 1 AND (c2 > 1 OR -> ((c2 = 1) AND (c3 > 1)))\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: range possible_keys: PRIMARY key: PRIMARY key_len: 12 ref: NULL rows: 3 filtered: 100.00 Extra: Using where
Таким образом, для лучших результатов, избегите смешивать конструктор
строки с выражениями AND
/
OR
.
При определенных условиях оптимизатор может применить метод доступа
диапазона для IN()
, у
которых есть параметры конструктора строки. См.
раздел 9.2.1.3.5
.
Вывод EXPLAIN
показывает
ALL
в столбце
type
, когда MySQL использует
полный просмотр таблицы,
чтобы решить запрос. Это обычно происходит при следующих условиях:
Таблица является настолько маленькой, что быстрее выполнить сканирование таблицы чем обеспокоиться ключевым поиском. Это характерно для таблиц меньше чем с 10 строками и короткой длиной строки.
ON
или WHERE
для индексированных столбцов.Для маленьких таблиц сканирование таблицы часто является соответствующим, и исполнительное воздействие незначительно. Для больших таблиц попробуйте следующие методы, чтобы избежать того, что оптимизатор неправильно выбирает сканирование таблицы:
Используйте ANALYZE TABLE
, чтобы обновить ключевые распределения для просмотренной таблицы. См.
раздел 14.7.2.1.tbl_name
FORCE INDEX
для просмотренной таблицы, чтобы
сказать MySQL, что сканирование таблицы очень дорого по сравнению с
использованием данного индекса:
SELECT * FROM t1, t2 FORCE INDEX (index_for_column
) WHERE t1.col_name
=t2.col_name
;
См. раздел 9.9.4.
--max-seeks-for-key=1000
или примените
SET max_seeks_for_key=1000
, чтобы сказать оптимизатору
предполагать, что никакой ключевой просмотр не вызывает больше, чем 1000
ключевых поисков, см. раздел
6.1.5.Этот раздел объясняет, как ускорить запросы изменения данных:
INSERT
,
UPDATE
и
DELETE
.
Традиционные приложения OLTP и современные веб-приложения, как правило,
делают много маленьких операций изменения данных, где параллелизм жизненно
важен. Анализ данных, как правило, выполняет операции изменения данных,
которые затрагивают много строк сразу, где основные соображения это
ввод/вывод, чтобы написать большие объемы данных и сохранить индекс
современным. Для вставки и обновления больших объемов данных (известный в
промышленности как ETL, от extract-transform-load),
иногда Вы используете другие запросы SQL или внешние команды, которые
имитируют эффекты INSERT
,
UPDATE
и
DELETE
.
Чтобы оптимизировать скорость вставок, комбинируют много маленьких операций в единственную большую работу. Идеально, Вы делаете единственное соединение, посылаете данные многих новых строк сразу и задерживаете все индексные обновления и проверку последовательности до самого конца.
Время, требуемое для того, чтобы вставить строку, определено следующими факторами, где числа указывают на приблизительные пропорции:
Соединение: (3)
Это не учитывает начальные издержки, чтобы открыть таблицы, которые сделаны однажды для каждого одновременно работающего запроса.
Размер таблицы замедляет вставку индексов на
log N
для индексов B-tree.
Вы можете использовать следующие методы, чтобы убыстрить вставку:
Если Вы вставляете много строк от того же самого клиента в то же
самое время, стоит использовать INSERT
со списком VALUES
, чтобы вставить несколько строк за один
раз. Это значительно быстрее (во много раз быстрее в некоторых случаях), чем
использование отдельного однострочного запроса
INSERT
.
Если Вы добавляете данные к непустой таблице, Вы можете настроить переменную
bulk_insert_buffer_size
, чтобы сделать вставку данных еще быстрее.
См. раздел 6.1.5.
LOAD DATA INFILE
.
Это обычно в 20 раз быстрее, чем использование
INSERT
в любом виде.
См. раздел 14.2.6.InnoDB
.MyISAM
.
Запрос обновления оптимизирован как
SELECT
. Скорость записи зависит от
обновляемого объема данных и числа индексов, которые обновлены. Индексы,
которые не изменены, не становятся обновленными.
Другой способ получить быстрые обновления состоит в том, чтобы задержать обновления и затем сделать много обновлений подряд позже. Выполнение многократных обновлений вместе намного более быстро, чем выполнение по одному, если Вы блокируете таблицу.
Для таблицы MyISAM
, которая использует динамический формат
строки, обновление строку к большей полной длине может разделить строку. Если
Вы часто делаете это, очень важно использовать
OPTIMIZE TABLE
.
См. раздел 14.7.2.4.
Время, требуемое, чтобы удалить отдельные строки в
MyISAM
, точно пропорционально числу индексов. Чтобы удалить
строки более быстро, Вы можете увеличить размер ключевого кэша, увеличивая
key_buffer_size
.
См. раздел 6.1.1.
Чтобы удалить все строки из MyISAM
,
TRUNCATE TABLE
быстрей, чем
tbl_name
DELETE FROM
.
Усечение не безопасно для транзакции; ошибка происходит в ходе активной
транзакции или активной табличной блокировки. См.
раздел 14.1.30.tbl_name
Чем более сложна Ваша установка привилегии, тем больше издержек
относится ко всем запросам SQL. Упрощение привилегий, установленных
GRANT
,
позволяет MySQL уменьшить издержки проверки разрешения, когда клиенты
выполняют запросы. Например, если Вы не предоставляете привилегий на уровне
столбца или на уровне таблицы, сервер никогда не должен проверять содержание
таблиц tables_priv
и columns_priv
.
Точно так же, если Вы не устанавливаете границ ресурса для каких-либо учетных
записей, сервер не должен выполнять подсчет ресурса. Если Вы имеете очень
высокую обрабатывающую запрос загрузку, рассмотрите использование упрощенной
структуры полномочий, чтобы уменьшить проверку разрешений.
Приложения, которые контролируют базы данных, могут сделать частое
использование таблиц INFORMATION_SCHEMA
.
Чтобы написать запросы для этих таблиц наиболее эффективно, используйте
следующие общие руководящие принципы:
Попытайтесь запросить только таблицы INFORMATION_SCHEMA
, которые являются представлениями о таблицах словаря данных.
Поведение сравнения для имен базы данных и имен таблиц в
INFORMATION_SCHEMA
могут отличаться от того, что Вы ожидаете.
Для деталей см.
раздел 11.1.8.7.
Эти таблицы INFORMATION_SCHEMA
осуществлены как представления
о таблицах словаря данных, таким образом, запросы к ним получают информацию
из словаря данных:
CHARACTER_SETS COLLATIONS COLLATION_CHARACTER_SET_APPLICABILITY COLUMNS KEY_COLUMN_USAGE SCHEMATA STATISTICS TABLES TABLE_CONSTRAINTS VIEWS
Некоторые типы значений, даже не для представлений
INFORMATION_SCHEMA
, получены поисками из словаря данных. Это
включает такие значения, как имена базы данных и имена таблиц, табличные
типы и механизмы хранения.
Некоторые таблицы INFORMATION_SCHEMA
содержат столбцы, которые обеспечивают табличную статистику:
STATISTICS.CARDINALITY TABLES.AUTO_INCREMENT TABLES.AVG_ROW_LENGTH TABLES.CHECKSUM TABLES.CHECK_TIME TABLES.CREATE_TIME TABLES.DATA_FREE TABLES.DATA_LENGTH TABLES.INDEX_LENGTH TABLES.MAX_DATA_LENGTH TABLES.TABLE_ROWS TABLES.UPDATE_TIME
Те столбцы представляют динамические табличные метаданные, то есть, информация, которая изменяется как табличное содержание, изменяется.
У сервера есть два источника, из которых можно получить табличную
статистику. Переменная
information_schema_stats
управляет тем, который табличный
источник статистики использует сервер:
Когда
information_schema_stats
CACHED
(по умолчанию),
сервер использует кэшируемую статистику, сохраненную в табьлицах
STATISTICS
и
TABLES
.
information_schema_stats
LATEST
,
сервер получает статистику непосредственно из механизмов хранения.
В этом случае сервер обрабатывает запросы к
STATISTICS
и
TABLES
как запросы для последней статистики, сохраненной в
STATISTICS_DYNAMIC
и
TABLES_DYNAMIC
.
Сервер выполняет эту замену внутренне, Вы должны написать запросы, используя
имя таблицы без сйффикса _DYNAMIC
. Например, когда
information_schema_stats
LATEST
,
сервер обрабатывает этот запрос:
SELECT * FROM INFORMATION_SCHEMA.TABLES;
как если бы Вы написали этот запрос:
SELECT * FROM INFORMATION_SCHEMA.TABLES_DYNAMIC;
Чтобы определить, как установлена
information_schema_stats
, рассмотрите обмены:
Когда сервер запускается, кэшируемые статистические данные
NULL
. Чтобы обновить их для данной таблицы, надо использовать
ANALYZE TABLE
.
Это подвергается одноразовой стоимости вычисления статистики, но кэшируемые
статистические данные остаются современными пока таблица медленно изменяется.
Чтобы обновить кэшируемую статистику в любое время после этого, надо снова
использовать ANALYZE TABLE
.
ANALYZE TABLE
не необходим, но каждый запрос, который получает статистику, подвергается
несколько более высокой стоимости выполнения, чем для
использования кэшируемой статистики.Установите глобальное значение
information_schema_stats
, чтобы определить значение по умолчанию,
используемое первоначально всеми сеансами. Отдельные сеансы могут установить
сеансовое значение
information_schema_stats
, чтобы переопределить глобальное
значение как надо.
Для таблиц INFORMATION_SCHEMA
, осуществленных как
представления о таблицах словаря данных, индексы на основных таблицах
разрешают оптимизатору создать эффективные планы выполнения запроса.
Чтобы видеть выбор, сделанный оптимизатором, надо использовать
EXPLAIN
. Чтобы также видеть
запрос, используемый сервером, чтобы выполнить запрос
INFORMATION_SCHEMA
, используйте
SHOW WARNINGS
сразу после
EXPLAIN
.
Рассмотрите этот запрос, который идентифицирует сопоставления для
набора символов utf8mb4
:
mysql> SELECT COLLATION_NAME -> FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY -> WHERE CHARACTER_SET_NAME = 'utf8mb4'; +----------------------+ | COLLATION_NAME | +----------------------+ | utf8mb4_general_ci | | utf8mb4_bin | | utf8mb4_unicode_ci | | utf8mb4_icelandic_ci | | utf8mb4_latvian_ci | | utf8mb4_romanian_ci | | utf8mb4_slovenian_ci | ...
Как сервер обрабатывает этот запрос? Чтобы узнать, надо использовать
EXPLAIN
:
mysql> EXPLAIN SELECT COLLATION_NAME -> FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY -> WHERE CHARACTER_SET_NAME = 'utf8mb4'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: cs partitions: NULL type: const possible_keys: PRIMARY,name key: name key_len: 194 ref: const rows: 1 filtered: 100.00 Extra: Using index *************************** 2. row *************************** id: 1 select_type: SIMPLE table: col partitions: NULL type: ref possible_keys: character_set_id key: character_set_id key_len: 8 ref: const rows: 68 filtered: 100.00 Extra: NULL 2 rows in set, 1 warning (0.01 sec)
Чтобы видеть запрос, используемый для статистики, примените
SHOW WARNINGS
:
mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Note Code: 1003 Message: /* select#1 */ select `mysql`.`col`.`name` AS `COLLATION_NAME` from `mysql`.`character_sets` `cs` join `mysql`.`collations` `col` where ((`mysql`.`col`.`character_set_id` = '45') and ('utf8mb4' = 'utf8mb4'))
Как обозначено SHOW WARNINGS
, сервер обрабатывает запрос на
COLLATION_CHARACTER_SET_APPLICABILITY
как запрос на таблицах character_sets
и collations
словаря данных в системной базе данных mysql
.
Приложения, которые контролируют базы данных, могут сделать частое
использование таблиц Performance Schema. Чтобы написать запросы для этих
таблиц наиболее эффективно, используйте в своих интересах их индекс.
Например, включайте WHERE
, который ограничивает полученные
строки, основанные на сравнении с определенными
значениями в индексированном столбце.
Большинство таблиц Performance Schema имеют индекс. Таблицы, которые не
имеют, обычно содержат немного строк или вряд ли будут часто запрашиваться.
Индексы Performance Schema предоставляют оптимизатору доступ к планам
выполнения, кроме полного сканирования таблицы. Они также улучшают работу для
связанных объектов, таких как представления схемы
sys
, которые используют те таблицы.
Чтобы видеть, имеет ли данная таблица индексы и каковы они, надо
использовать SHOW INDEX
или
SHOW CREATE TABLE
:
mysql> SHOW INDEX FROM performance_schema.accounts\G *************************** 1. row *************************** Table: accounts Non_unique: 0 Key_name: ACCOUNT Seq_in_index: 1 Column_name: USER Collation: NULL Cardinality: NULL Sub_part: NULL Packed: NULL Null: YES Index_type: HASH Comment: Index_comment: Visible: YES *************************** 2. row *************************** Table: accounts Non_unique: 0 Key_name: ACCOUNT Seq_in_index: 2 Column_name: HOST Collation: NULL Cardinality: NULL Sub_part: NULL Packed: NULL Null: YES Index_type: HASH Comment: Index_comment: Visible: YES mysql> SHOW CREATE TABLE performance_schema.rwlock_instances\G *************************** 1. row *************************** Table: rwlock_instances Create Table: CREATE TABLE `rwlock_instances` ( `NAME` varchar(128) NOT NULL, `OBJECT_INSTANCE_BEGIN` bigint(20) unsigned NOT NULL, `WRITE_LOCKED_BY_THREAD_ID` bigint(20) unsigned DEFAULT NULL, `READ_LOCKED_BY_COUNT` int(10) unsigned NOT NULL, PRIMARY KEY (`OBJECT_INSTANCE_BEGIN`), KEY `NAME` (`NAME`), KEY `WRITE_LOCKED_BY_THREAD_ID` (`WRITE_LOCKED_BY_THREAD_ID`)) ENGINE=PERFORMANCE_SCHEMA DEFAULT CHARSET=utf8
Чтобы видеть план выполнения для запроса к Performance Schema
и использует ли это любой индекс, стоит использовать
EXPLAIN
:
mysql> EXPLAIN SELECT * FROM performance_schema.accounts -> WHERE (USER,HOST) = ('root','localhost')\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: accounts partitions: NULL type: const possible_keys: ACCOUNT key: ACCOUNT key_len: 278 ref: const,const rows: 1 filtered: 100.00 Extra: NULL
Вывод EXPLAIN
указывает, что оптимизатор использует индекс ACCOUNT
таблицы
accounts
, который
включает столбцы USER
и HOST
.
Индексы Performance Schema являются виртуальными: они конструкция механизма хранения Performance Schema и не используют памяти или дискового хранения. Исполнительные отчеты о Performance Schema индексируют информацию оптимизатору так, чтобы это могло создать эффективные планы выполнения. Performance Schema в свою очередь использует информацию оптимизатора о том, что искать (например, особое значение ключа), так чтобы это могло организовать эффективные поиски, не создавая фактической индексной структуры. Это выполнение обеспечивает две важных выгоды:
Это полностью избегает стоимости обслуживания, обычно понесенной для таблиц, которые подвергаются частым обновлениям.
Индексы Performance Schema предопределены и не могут быть удалены, добавлены или изменены.
Индексы Performance Schema подобны хеш-индексам, например:
Они используются только для сравнений равенства, которые
используют операторы =
или <=>
.
ORDER BY
.См. раздел 9.3.8.
Этот раздел перечисляет много разных подсказок для того, чтобы улучшить скорость обработки запроса:
Если Ваше приложение обращается с несколькими запросами, чтобы выполнить связанные обновления, комбинирование запросов в сохраненную подпрограмму может помочь работе. Точно так же, если Ваше приложение вычисляет единственный результат, основанный на нескольких значениях столбцов, или большие объемы данных, комбинирование вычислений в UDF (определяемая пользователем функция) может помочь работе. Получающиеся быстрые операции базы данных тогда доступны, чтобы быть снова использованными другими запросами, приложениями и даже программам, написанным на различных языках программирования. См. разделы 21.2 и 26.4.
ARCHIVE
, используйте
OPTIMIZE TABLE
. См.
раздел 17.5.BLOB
. В этом случае Вы должны
обеспечить код в своем приложении, чтобы упаковать и распаковать информацию,
но это могло бы сохранить операции ввода/вывода, чтобы считать и написать
наборы связанных значений.InnoDB
или MyISAM
напрямую, Вы можете получить
существенное увеличение скорости по сравнению с
использованием интерфейса SQL.Лучший способ улучшить исполнение
SELECT
это
создать индексы на одном или больше столбцов, которые проверены в запросе.
Индексированные записи действуют как указатели на строки таблицы, позволяя
запросу быстро определить, какие строки соответствуют условию в
WHERE
и получить другие значения столбцов для тех строк. Все
типы данных MySQL могут быть индексированы.
Хотя может быть заманчиво создать индексирование для каждого возможного столбца, используемого в запросе, не нужный индекс тратит ненужное пространство и напрасно тратит время для MySQL, чтобы определить, который индекс использовать. Индексирование также добавляет свой вклад к стоимости вставок, обновлений и удалений, потому что каждый индекс должен быть обновлен. Вы должны найти правильный баланс, чтобы достигнуть быстрых запросов, используя оптимальный набор индексов.
Индекс используются, чтобы найти строки с определенными значениями столбцов быстро. Без индексирования MySQL должен начать с первой строки и затем прочитать всю таблицу, чтобы найти соответствующие строки. Чем больше таблица, тем больше это стоит. Если у таблицы есть индексирование для рассматриваемых столбцов, MySQL может быстро определить позицию, чтобы искать в середине файла с данными, не имея необходимости смотреть на все данные. Это намного быстрее, чем чтение каждой строки последовательно.
Большинство индексов MySQL (PRIMARY KEY
,
UNIQUE
, INDEX
и FULLTEXT
)
сохранены в B-tree.
Исключения: индексы на пространственных типах данных применяют R-tree,
таблицы MEMORY
также поддерживают
hash-индексы, InnoDB
использует инвертированные списки для индексов FULLTEXT
.
Вообще, индексы используются как описано в следующем обсуждении.
Характеристики, определенные для хеш-индекса (как используется в таблицах
MEMORY
), описаны в
разделе 9.3.8.
MySQL применяет индексы для этих операций:
Найти строки, соответствующие WHERE
.
(col1, col2, col3)
, Вы можете искать по индексу на
(col1)
, (col1, col2)
и (col1, col2, col3)
. См. раздел 9.3.5.
VARCHAR
и
CHAR
считаются тем же самым, если они объявлены как тот же самый размер. Например,
VARCHAR(10)
и CHAR(10)
имеют тот же самый размер,
но VARCHAR(10)
и CHAR(15)
нет.
Для сравнений между недвоичными строковыми столбцами оба столбца должны
использовать тот же самый набор символов. Например, сравнение столбцов
utf8
и latin1
устраняет использование индекса.
Сравнение несходных столбцов (сравнение строкового столбца с
временным или числовым столбцу, например) может предотвратить использование
индекса, если значения не могут быть сравнены непосредственно без
преобразования. Для данного значения 1
в числовом столбце это могло бы сравниться с любым числом значений в
строковом столбце, таких как '1'
, ' 1'
,
'00001'
или '01.e1'
.
Это исключает использование любого индекса для строкового столбца.
MIN()
или
MAX()
для определенного индексированного столбца key_col
.
Это оптимизировано препроцессором, который проверяет, используете ли Вы
WHERE key_part_N
=
constant
на всех ключевых частях, которые
происходят прежде key_col
в индексировании. В этом случае MySQL делает единственный ключевой поиск для
каждого выражения MIN()
или
MAX()
и заменяет это константой. Если все выражения заменены константами, запрос
возвращается сразу. Например:
SELECT MIN(key_part2
),MAX(key_part2
) FROMtbl_name
WHEREkey_part1
=10;
Чтобы сортировать или сгруппировать таблицу, если сортировка или
группировка сделаны на левом префиксе применимого индексирования (например,
ORDER BY
). Если все ключевые части
сопровождаются key_part1
,
key_part2
DESC
, ключ считан в обратном порядке. См. разделы
9.2.1.15 и
9.2.1.16.
SELECTkey_part3
FROMtbl_name
WHEREkey_part1
=1
Индексы менее важны для запросов на маленьких таблицах или больших таблицах, где запросы обрабатывают больше всего или все строки. Когда запрос должен получить доступ к большинству строк, читать последовательно быстрее, чем работа посредством индексирования. Последовательные чтения минимизируют дисковый поиск, даже если не все строки необходимы для запроса. См. раздел 9.2.1.21.
Первичный ключ для таблицы представляет столбец или набор столбцов,
которые Вы используете в своих самых жизненных запросах. У этого есть
связанный индекс для быстрой работы запроса. Работа запроса извлекает выгоду
из оптимизации NOT NULL
, поскольку это не может включать
значения NULL
. С InnoDB
табличные данные физически
организованы, чтобы сделать ультрабыстрые поиски и сортировки, основанные на
столбце или столбцах первичного ключа.
Если Ваша таблица является большой и важной, но не имеет очевидного столбца или набора столбцов, чтобы использовать в качестве первичного ключа, Вы могли бы создать отдельный столбец со значениями auto-increment, чтобы использовать в качестве первичного ключа. Эти уникальные ID могут служить указателями на соответствующие строки в других таблицах, когда Вы присоединяетесь к таблицам, используя внешние ключи.
Если у таблицы есть много столбцов, и Вы запрашиваете много различных комбинаций столбцов, могло бы быть эффективно разделить менее часто используемые данные на отдельные таблицы с несколькими столбцами и связать их с основной таблицей, дублируя числовой столбец ID от основной таблицы. Таким путем у каждой маленькой таблицы может быть первичный ключ для быстрых поисков данных, и Вы можете запросить только набор столбцов, в котором Вы нуждаетесь при использовании работы соединения. В зависимости от того, как распределены данные, запросы могли бы выполнить меньше ввода/вывода и занять меньше кэш-памяти, потому что соответствующие столбцы упакованы вместе на диске. Чтобы максимизировать работу, запросы пытаются читать так мало блоков данных, насколько возможно с диска, таблицы только с несколькими столбцами могут приспособить больше строк в каждом блоке данных.
Наиболее распространенный тип индекса вовлекает единственный столбец,
храня копии значений от того столбца в структуре данных, позволяя быстрые
поиски для строк с соответствующими значениями столбцов. Структура данных
B-tree позволяет индексированию быстро находить определенное значение, ряд
значений или диапазона значений, соответствуя таким операторам, как
=
, >
, BETWEEN
, IN
и
т.д. в WHERE
.
Максимальное количество индексов на таблицу и максимум длины индекса определены механизмом хранения. См. главы 16 и 17. Все механизмы хранения поддерживают, по крайней мере, 16 индексов на таблицу и общая длина по крайней мере 256 байтов. У большинства механизмов хранения есть более высокие пределы.
См. раздел 14.1.12.
С
в
определении индекса для строкового столбца, Вы можете создать индексирование,
которое использует только первые
col_name
(N
)N
символов столбца. Индексация только префикса
значений столбцов таким образом может сделать индексный файл намного меньшим.
Когда Вы индексируете столбец BLOB
или TEXT
, Вы должны
определить длину префикса для индексирования. Например:
CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));
Префиксы могут составить до 767 байтов для InnoDB
,
которые используют формат строки
REDUNDANT
или COMPACT
.
Предел длины поднят до 3072 байтов для InnoDB
, которые
используют формат строки
DYNAMIC
или
COMPRESSED
. Для MyISAM размер префикса составляет 1000 байт.
Пределы измерены в байтах, тогда как длина префикса в
CREATE TABLE
,
ALTER TABLE
и
CREATE INDEX
интерпретируется как число символов для недвоичных строковых типов
(CHAR
,
VARCHAR
,
TEXT
)
и число байтов для двоичных строковых типов
(BINARY
,
VARBINARY
,
BLOB
).
Примите это во внимание, определяя длину префикса для недвоичного строкового
столбца, который использует многобайтовый набор символов.
См. раздел 14.1.12.
Индексы FULLTEXT
используются для полнотекстовых поисков.
Только InnoDB
и
MyISAM
поддерживают индексы FULLTEXT
и только для столбцов
CHAR
,
VARCHAR
и
TEXT
.
Индексация всегда имеет место по всему столбцу, префикс не используется.
См. раздел 13.9.
Оптимизация применена к определенным видам запросов FULLTEXT
.
Запросы с этими характеристиками особенно эффективны:
Запросы FULLTEXT
, которые возвращают только
ID документа или ID документа и разряд поиска.
FULLTEXT
, которые сортируют соответствующие строки в
порядке убывания счета и применяют LIMIT
, чтобы взять первые N
соответствий строк. Для этой оптимизации не должно быть WHERE
и
только один ORDER BY
в порядке убывания.FULLTEXT
, которые получают только значение
COUNT(*)
строк, соответствующих критерию поиска, без
дополнительного WHERE
. Кодируйте WHERE
как
WHERE MATCH(text
) AGAINST ('other_text
')
, без любых операторов сравнения > 0
.
Вы можете создать индексы на пространственных типах данных.
MyISAM
и InnoDB
понимают индексы R-tree
на пространственных типах. Другие механизмы хранения используют B-деревья для
того, чтобы индексировать пространственные типы (за исключением
ARCHIVE
, который не поддерживает пространственную индексацию).
MEMORY
применяет по умолчанию индексы
HASH
, но также и BTREE
.
MySQL может создать сводные индексы (то есть, индекс на многих столбцах). Индексирование может состоять из 16 столбцов. Для определенных типов данных Вы можете индексировать префикс столбца (см. раздел 9.3.4).
MySQL может использовать многостолбцовый индекс для запросов, которые проверяют все столбцы в индексировании или запросы, которые проверяют только первый столбец, первые два столбца, первые три столбца и так далее. Если Вы определяете столбцы в правильном порядке в определении индекса, единственный сводный индекс может ускорить несколько видов запросов на той же самой таблице.
Многостолбцовый индекс может считаться сортированным массивом, строки которого содержат значения, которые созданы, связывая значения индексированных столбцов.
Как альтернатива сводному индексу, Вы можете ввести столбец, который является хэшем, основанный на информации от других столбцов. Если этот столбец короток, разумно уникален и индексирован, это могло бы быть быстрее, чем широкий индекс на многих столбцах. В MySQL очень удобен этот дополнительный столбец:
SELECT * FROMtbl_name
WHEREhash_col
=MD5(CONCAT(val1
,val2
)) ANDcol1
=val1
ANDcol2
=val2
;
Предположите, что у таблицы есть следующая спецификация:
CREATE TABLE test (id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name, first_name));
Индекс name
создан на столбцах
last_name
и first_name
.
Индексирование может использоваться для поисков в запросах, которые
определяют значения в известном диапазоне для комбинаций
last_name
и first_name
.
Это может также использоваться для запросов, которые определяют только
last_name
, потому что этот столбец префикс
индексирования (как описано позже в этом разделе). Поэтому индекс
name
используется для поисков в следующих запросах:
SELECT * FROM test WHERE last_name='Widenius'; SELECT * FROM test WHERE last_name='Widenius' AND first_name='Michael'; SELECT * FROM test WHERE last_name='Widenius' AND (first_name='Michael' OR first_name='Monty'); SELECT * FROM test WHERE last_name='Widenius' AND first_name >='M' AND first_name < 'N';
Однако, name
не используется для
поисков в следующих запросах:
SELECT * FROM test WHERE first_name='Michael'; SELECT * FROM test WHERE last_name='Widenius' OR first_name='Michael';
Предположите, что Вы создаете такой
SELECT
:
SELECT * FROMtbl_name
WHERE col1=val1
AND col2=val2
;
Если многостолбцовый индекс существует на col1
и
col2
, соответствующие строки могут быть принесены
непосредственно. Если отдельный одностолбцовый индекс существует на
col1
и col2
, оптимизатор пытается использовать
оптимизацию Index Merge (см.
раздел 9.2.1.4) или пытается найти самые строгие индексы, решая, который
индекс исключает больше строк и используя этот индекс, чтобы принести строки.
Если у таблицы есть многостолбцовый индекс, любой префикс может
использоваться оптимизатором, чтобы искать строки. Например, если у Вас есть
индекс на три столбца (col1, col2, col3)
, Вы имеете возможности
поиска на (col1)
, (col1, col2)
и
(col1, col2, col3)
.
MySQL не может использовать индексирование, чтобы выполнить поиски, если
столбцы не формируют префикс индексирования. Предположите, что Вы имеете
SELECT
, как показано:
SELECT * FROMtbl_name
WHERE col1=val1
; SELECT * FROMtbl_name
WHERE col1=val1
AND col2=val2
; SELECT * FROMtbl_name
WHERE col2=val2
; SELECT * FROMtbl_name
WHERE col2=val2
AND col3=val3
;
Если индексирование существует на (col1, col2, col3)
,
только первые два запроса используют индексирование. Третий и четвертый
запросы действительно вовлекают индексированные столбцы, но
(col2)
и (col2, col3)
не префиксы для
(col1, col2, col3)
.
Всегда проверяйте, используют ли все Ваши запросы действительно
индексирование, которое Вы создали в таблицах. Используйте
EXPLAIN
, как описано в
разделе 9.8.1.
Механизмы хранения собирают статистические данные о таблицах для использования оптимизатором. Табличные статистические данные основаны на группах значения, где группа значения это ряд строк с тем же самым ключевым значением префикса. В целях оптимизатора важная статистическая величина это групповой размер среднего значения.
MySQL использует групповой размер среднего значения следующими способами:
Чтобы оценить, как строки должны быть считаны для каждого
доступа ref
.
(...) JOINtbl_name
ONtbl_name
.key
=expr
Как групповой размер среднего значения для увеличения индекса, индексирование менее полезно в этих двух целях, потому что среднее число строк за поиск увеличивается: для хорошего индексирования в целях оптимизации лучше, чтобы каждый индекс обрабатывал небольшое количество строк в таблице. Когда данный индекс приводит к большому количеству строк, индексирование менее полезно, и MySQL, менее вероятно, будет использовать это.
Групповой размер среднего значения связан с табличным количеством
элементов, которое является числом групп значения.
SHOW INDEX
выводит на экран значение количества элементов, основанное на
N/S
, где N
число строк в таблице
и S
групповой размер среднего значения. Это отношение
приводит к приблизительному количеству групп значения в таблице.
Для соединения, основанного на операторе сравнения
<=>
, NULL
не обработан по-другому ни от какого другого значения:
NULL <=> NULL
так же, как
для любого другого N
<=> N
N
.
Однако, для соединения, основанного на операторе =
,
NULL
отличается от не-NULL
:
не истина, когда expr1
= expr2
expr1
или
expr2
(или оба) NULL
. Это затрагивает
доступы ref
для сравнений формы
: MySQL не будет получать доступ к таблице,
если текущее значение tbl_name.key
=
expr
expr
= NULL
,
потому что сравнение не может быть истиной.
Для сравнения =
не имеет значения, сколько
NULL
находятся в таблице. В целях оптимизации соответствующее
значение это средний размер группы значения не-NULL
.
Однако, MySQL в настоящее время не позволяет этому среднему размеру быть
собранным или использоваться.
Для InnoDB
и MyISAM
Вы имеете некоторый контроль
над сбором табличной статистики посредством переменных
innodb_stats_method
и
myisam_stats_method
, соответственно. У этих переменных есть три
возможных значения, которые отличаются следующим образом:
Когда переменная установлена в nulls_equal
,
значения NULL
обработаны как идентичные (то есть, они все
формируют единственную группу значения).
Если групповой размер значения NULL
намного выше, чем среднее число значений не-NULL
,
этот метод искажает групповой размер среднего значения вверх. Это делает
индекс менее полезным оптимизатору, чем это действительно для соединений,
которые ищут значения не-NULL
. Следовательно, метод
nulls_equal
может заставить оптимизатор не использовать
индексирование для доступов ref
, когда должен бы.
nulls_unequal
,
NULL
не считают теми же самыми. Вместо этого каждый
NULL
формирует отдельную группу значения размера 1.
Если у Вас есть много NULL
, этот метод искажает групповой
размер среднего значения вниз. Если среднее число не-NULL
является большим, рассчет каждого значения NULL
как группы
размера 1 заставляет оптимизатор оценить слишком высоко значение
индексирования для соединений, которые ищут не-NULL
.
Следовательно, метод nulls_unequal
может заставить оптимизатор использовать этот индекс для поиска
ref
, когда другие методы
могут быть лучше.
nulls_ignored
,
значения NULL
проигнорированы.Если Вы склонны использовать много соединений то используйте
<=>
виесто =
, значения NULL
не являются особенными в сравнениях и один NULL
равен другим.
В этом случае nulls_equal
соответствующий метод статистики.
Переменная
innodb_stats_method
имеет глобальное значение,
myisam_stats_method
имеет глобальное и значение сеанса. Установка глобального
значения затрагивает сбор статистики для таблиц от соответствующего
механизма хранения. Установка сеанса оценивает сбор статистики
только для текущего соединения клиента. Это означает, что Вы можете вынудить
статистику таблицы быть собранной с данным методом, не затрагивая других
клиентов, устанавливая значение сеанса
myisam_stats_method
.
Восстановить табличную статистику MyISAM
можно любым
из следующих методов:
Выполнить myisamchk
--stats_method=method_name
--analyze
.
myisam_stats_method
и скомандовать ANALYZE
TABLE
.Некоторые протесты относительно использования
innodb_stats_method
и
myisam_stats_method
:
Вы можете вынудить табличную статистику быть собранной явно, как
только что описано. Однако, MySQL может также собрать статистические данные
автоматически. Например, если в течение выполнения запросов для таблицы,
некоторые из тех запросов изменяют таблицу, MySQL может собрать
статистические данные. Это может произойти для большей части вставок или
удалений или некоторых ALTER TABLE
. Если это происходит, статистические данные собраны, используя любое
значение
innodb_stats_method
или
myisam_stats_method
. Таким образом, если Вы соберете статистические данные, используя
один метод, но системная переменная установлена в другой метод, когда
статистические данные таблицы будут собраны автоматически позже, то другой
метод будет использоваться.
InnoDB
и
MyISAM
. У других механизмов хранения есть только один метод для
того, чтобы собрать табличные статистические данные. Обычно это ближе к
методу nulls_equal
.Понимание B-дерева и структур данных хеша может помочь предсказать, как
различные запросы выступают на различных механизмах хранения, которые
используют эти структуры данных, особенно для механизма хранения
MEMORY
, который позволяет Вам выбирать B-дерево или хеш-индекс.
B-tree может использоваться для сравнений столбца в выражениях, которые
используют операторы =
,
>
,
>=
,
<
,
<=
или
BETWEEN
.
Индексирование также может использоваться для
LIKE
, если параметр
LIKE
постоянная строка,
которая не начинается с подстановочного символа. Например, следующий
SELECT
использует индекс:
SELECT * FROMtbl_name
WHEREkey_col
LIKE 'Patrick%'; SELECT * FROMtbl_name
WHEREkey_col
LIKE 'Pat%_ck%';
В первом запросе только строки с 'Patrick' <=
рассмотрены. Во втором запросе только строки с key_col
< 'Patricl''Pat' <=
рассмотрены.
key_col
< 'Pau'
Следующие SELECT
не используют индексы:
SELECT * FROMtbl_name
WHEREkey_col
LIKE '%Patrick%'; SELECT * FROMtbl_name
WHEREkey_col
LIKEother_col
;
В первом запросе LIKE
начинается с с подстановочного символа. Во втором запросе значение
LIKE
не константа.
Если Вы используете ... LIKE '%
и string
%'string
более длинно чем три символа, MySQL использует
алгоритм Turbo Boyer-Moore, чтобы инициализировать образец для
строки и затем использует этот образец, чтобы выполнить поиск более быстро.
Использование поиска
использует индекс, если col_name
IS NULLcol_name
индексирован.
Любой индекс, который не охватывает все уровни
AND
в WHERE
,
не используется, чтобы оптимизировать запрос. Другими словами, чтобы быть в
состоянии использовать индексирование, префикс должен использоваться в каждой
группе AND
.
Следующий WHERE
использует индекс:
... WHEREindex_part1
=1 ANDindex_part2
=2 ANDother_column
=3 /*index
= 1 ORindex
= 2 */ ... WHEREindex
=1 OR A=10 ANDindex
=2 /* optimized like "index_part1
='hello'" */ ... WHEREindex_part1
='hello' ANDindex_part3
=5 /* Can use index onindex1
but not onindex2
orindex3
*/ ... WHEREindex1
=1 ANDindex2
=2 ORindex1
=3 ANDindex3
=3;
Эти WHERE
не используют индекс:
/*index_part1
is not used */ ... WHEREindex_part2
=1 ANDindex_part3
=2 /* Index is not used in both parts of the WHERE clause */ ... WHEREindex
=1 OR A=10 /* No index spans all rows */ ... WHEREindex_part1
=1 ORindex_part2
=10
Иногда MySQL не использует индексирование, даже если можно.
Одно обстоятельство, при котором это происходит, когда оптимизатор оценивает,
что использование индексирования потребовало бы, чтобы MySQL получил доступ к
очень большому проценту строк в таблице. В этом случае сканирование таблицы,
вероятно, будет намного быстрее, потому что оно требует меньше поисков.
Однако, если такой запрос использует LIMIT
, чтобы получить
только некоторые из строк, MySQL использует индексирование так или иначе,
потому что он может намного более быстро найти немного строк.
Hash-индекс имеет несколько иные характеристики:
Они используются только для сравнений равенства, которые
используют операторы =
или <=>
(зато очень быстро).
Они не используются для таких операторов сравнения, как <
,
для диапазона значений. Системы, которые полагаются на этот тип поиска
единственного значения, известны как значение ключа
.
ORDER BY
. Этот тип индекса не может использоваться, чтобы искать
следующую запись в порядке.MyISAM
или InnoDB
на MEMORY
.
MySQL понимает индексы на произведенных столбцах. Например:
CREATE TABLE t1 (f1 INT, gc INT AS (f1 + 1) STORED, INDEX (gc));
Произведенный столбец gc
определен как выражение
f1 + 1
. Столбец также индексирован, и оптимизатор может взять
индекс во внимание во время конструкции плана выполнения. В следующем запросе
WHERE
относится к gc
, а оптимизатор рассматривает,
приводит ли индексирование на том столбце к более эффективному плану:
SELECT * FROM t1 WHERE gc > 9;
Оптимизатор может использовать индекс на произведенных столбцах, чтобы
произвести планы выполнения, даже в отсутствие прямых ссылок в запросах к тем
столбцам по имени. Это происходит, если WHERE
,
ORDER BY
или GROUP BY
относится к выражению,
которое соответствует определению некоторого индексированного произведенного
столбца. Следующий запрос не обращается непосредственно к gc
,
но действительно использует выражение, которое
соответствует определению gc
:
SELECT * FROM t1 WHERE f1 + 1 > 9;
Оптимизатор признает что выражение f1 + 1
соответствует определению gc
, и что gc
индексирован, таким образом, это учитывает индекс во время конструкции плана
выполнения. Вы можете видеть это через использование
EXPLAIN
:
mysql> EXPLAIN SELECT * FROM t1 WHERE f1 + 1 > 9\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: range possible_keys: gc key: gc key_len: 5 ref: NULL rows: 1 filtered: 100.00 Extra: Using index condition
В действительности, оптимизатор заменил выражение f1 + 1
названием произведенного столбца, который соответствует выражению. Это также
очевидно в переписанном запросе, доступном в расширенном
EXPLAIN
с помощью
SHOW WARNINGS
:
mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Note Code: 1003 Message: /* select#1 */ select `test`.`t1`.`f1` AS `f1`,`test`.`t1`.`gc` AS `gc` from `test`.`t1` where (`test`.`t1`.`gc` > 9)
Следующие ограничения и условия относятся к использованию оптимизатором произведенного столбца:
Для выражения запроса, чтобы соответствовать произведенному
определению столбца, выражение должно быть идентичным, и у него должен быть
тот же самый тип результата. Например, если произведенное выражение столбца
f1 + 1
, оптимизатор не будет признавать соответствия, если
запрос будет использовать 1 + f1
, или если f1 + 1
(выражение целого числа) по сравнению со строкой.
=
,
<
,
<=
,
>
,
>=
,
BETWEEN
и
IN()
.
Для операторов кроме
BETWEEN
и
IN()
любой операнд может быть заменен произведенным столбцом соответствия. Для
BETWEEN
и
IN()
только первый параметр может быть заменен произведенным столбцом
соответствия, и у других параметров должен быть тот же самый тип результата.
BETWEEN
и
IN()
еще не поддержаны для сравнений, вовлекающих значения JSON.
gc INT AS (f1) STORED
состоит только из ссылки столбца, поэтому индекс на
gc
не рассмотрен.
JSON_UNQUOTE()
необходим в определении столбца, чтобы удалить
дополнительные кавычки из функционального значения. Для прямого сравнения
строки с функциональным результатом JSON обрабтывает удаление кавычки, но
для индексного это не происходит. Например, вместо того, чтобы писать
определение столбца так:
doc_name TEXT AS (JSON_EXTRACT(jdoc, '$.name')) STORED
Напишите это так:
doc_name TEXT AS (JSON_UNQUOTE(JSON_EXTRACT(jdoc, '$.name'))) STORED
С последним определением оптимизатор может обнаружить совпадение обоих этих сравнений:
... WHERE JSON_EXTRACT(jdoc, '$.name') = 'some_string
' ... ... WHERE JSON_UNQUOTE(JSON_EXTRACT(jdoc, '$.name')) = 'some_string
' ...
Ьез JSON_UNQUOTE()
в определении столбца оптимизатор обнаруживает соответствие только для
первого из этих сравнений.
MySQL поддерживает невидимый индекс, то есть, индекс, который не используется оптимизатором. Особенность относится к индексам, кроме первичных ключей.
Индексы видимы по умолчанию. Чтобы управлять невидимостью явно для нового
индекса, используют VISIBLE
или INVISIBLE
как
часть индексного определения для CREATE
TABLE
, CREATE INDEX
или ALTER TABLE
:
CREATE TABLE t1 (i INT, j INT, k INT, INDEX i_idx (i) INVISIBLE) ENGINE = InnoDB; CREATE INDEX j_idx ON t1 (j) INVISIBLE; ALTER TABLE t1 ADD INDEX k_idx (k) INVISIBLE;
Чтобы изменить невидимость существующего индекса используют
VISIBLE
или INVISIBLE
в
ALTER TABLE ... ALTER INDEX
:
ALTER TABLE t1 ALTER INDEX i_idx INVISIBLE; ALTER TABLE t1 ALTER INDEX i_idx VISIBLE;
Информация о том, видимо ли индексирование или нет, доступна из таблицы
INFORMATION_SCHEMA.STATISTICS
или вывода SHOW INDEX
:
mysql> SELECT INDEX_NAME, IS_VISIBLE -> FROM INFORMATION_SCHEMA.STATISTICS -> WHERE TABLE_SCHEMA = 'db1' AND TABLE_NAME = 't1'; +------------+------------+ | INDEX_NAME | IS_VISIBLE | +------------+------------+ | i_idx | YES | | j_idx | NO | | k_idx | NO | +------------+------------+
Невидимый индекс позволяют проверить эффект удаления индексирования на работе запроса, не производя разрушительное изменение. Передобавление индексирования может быть дорогим для большой таблицы, тогда как переключение невидимости индекса является быстрыми оперативными операциями.
Если невидимый индекс фактически необходим или используется оптимизатором, есть несколько способов заметить эффект его отсутствия на запросах для таблицы:
Ошибки происходят для запросов, которые включают индексные подсказки, которые обращаются к невидимому индексу.
EXPLAIN
.Невидимость не затрагивает обслуживание. Например, индексирование продолжает обновляться при изменении строк таблицы, и уникальный индекс предотвращает вставку дубликатов в столбец, независимо от того, видимо ли индексирование или невидимо.
У таблицы без явного первичного ключа может все еще быть
эффективный неявный первичный ключ, если у этого есть индекс
UNIQUE
на столбцах NOT NULL
.
В этом случае первый такой индекс имеет то же самое ограничение на строки
таблицы как явный первичный ключ и индекс не может быть сделан невидимым.
Рассмотрите следующее табличное определение:
CREATE TABLE t2 (i INT NOT NULL, j INT NOT NULL, UNIQUE j_idx (j)) ENGINE = InnoDB;
Определение не включает явного первичного ключа, но индекс на
столбце j
NOT NULL
помещает то же самое ограничение
на строки, как первичный ключ и не может быть сделан невидимым:
mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE; ERROR 3522 (HY000): A primary key index cannot be invisible.
Теперь предположите, что явный первичный ключ добавлен к таблице:
ALTER TABLE t2 ADD PRIMARY KEY (i);
Явный первичный ключ не может быть сделан невидимым. Кроме того,
уникальные индексы на j
действуют как неявный
первичный ключ и в результате не могут быть сделаны невидимыми:
mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE; Query OK, 0 rows affected (0.03 sec)
В Вашей роли проектировщика базы данных, ищите самый эффективный способ организовать Ваши схемы, таблицы и столбцы. Настраивая код программы, Вы минимизируете ввод/вывод, держите связанные элементы вместе и планируете заранее, чтобы работа осталась высокой, когда объем данных увеличивается.
Разработайте свои таблицы, чтобы минимизировать их место на диске. Это может привести к огромным усовершенствованиям, уменьшая объем данных, написанный и прочитанный с диска. Меньшие таблицы обычно требуют менее основной памяти, в то время как их содержание активно обрабатывается во время выполнения запроса. Любое сокращение пространства для табличных данных также приводит к меньшему индексу, который может быть обработан быстрее.
MySQL поддерживает много различных механизмов хранения (табличные типы) и форматов строк. Для каждой таблицы Вы можете решить, который метод хранения и индексации использовать. Выбор надлежащего формата таблицы для Вашего приложения может дать Вам большой прирост производительности. См. главы 16 и 17.
Вы можете получить лучшую работу для таблицы и минимизировать место для хранения при использовании методов, перечисленных здесь:
Используйте самые эффективные (самые маленькие) возможные типы
данных. У MySQL есть много специализированных типов, которые сохраняют
дисковое пространство и память. Например, используйте меньшие типы целого
числа если возможно, чтобы получить меньшие таблицы.
MEDIUMINT
часто лучший выбор, чем
INT
, поскольку столбец
MEDIUMINT
использует на 25% меньше пространства.
NOT NULL
, если возможно.
Это делает операции SQL быстрее, включая лучшее использование индексов
и устранение издержек для того, чтобы проверить, является ли каждое значение
NULL
. Вы также сохраняете некоторое место для хранения, один бит
на столбец. Если Вы действительно нуждаетесь в NULL
в Ваших таблицах, используйте их. Только избегайте настройки по умолчанию,
которая позволяет NULL
в каждом столбце.InnoDB
составлены, используя формат строки
DYNAMIC
по умолчанию, и формат строки значения по умолчанию
конфигурируем с применением опции
innodb_default_row_format
.
Чтобы запросить формат строки не DYNAMIC
, Вы можете
сконфигурировать
innodb_default_row_format
или указать опцию ROW_FORMAT
явно в CREATE TABLE
или ALTER TABLE
.
Компактная строка уменьшает место для хранения строки приблизительно на 20% за счет увеличивающегося использования центрального процессора для некоторых операций. Если Ваша рабочая нагрузка будет типичной, которая ограничена частотами успешных обращений кэша и дисковой скоростью, то это, вероятно, будет быстрее. Если это редкий случай, который ограничен скоростью центрального процессора, это могло бы быть медленнее.
Компактный формат также изменяет, как столбцы
CHAR
хранят
utf8
или utf8mb4
. С ROW_FORMAT=REDUNDANT
столбец utf8
или utf8mb4
CHAR(
занимает максимальную символьную
длину в байтах*N
)N
байт.
Много языков могут быть написаны, используя однобайтовые символы
utf8
или utf8mb4
, таким образом, фиксированная
длина хранения часто тратит впустую пространство. С
ROW_FORMAT=COMPACT
InnoDB
выделяет переменное количество места для этих столбцов, отрезая конечные
пробелы в случае необходимости. Минимальная длина хранения сохранена как
N
байт, чтобы облегчить оперативные обновления в
типичных случаях. Для получения дополнительной информации см.
раздел 16.8.2.
ROW_FORMAT=COMPRESSED
при создании
таблицы InnoDB
или выполнить
myisampack
на существующей таблице MyISAM
. Сжатые таблицы
InnoDB
читаемы и перезаписываемы в то время, как сжатые таблицы
MyISAM
только для чтения.MyISAM
, если у Вас нет никаких столбцов
переменной длины (VARCHAR
,
TEXT
или
BLOB
), формат строки фиксированного
размера используется. Это быстрее, но может потратить впустую некоторое
пространство. См. раздел 17.2.3
. Вы можете подсказать, что хотите иметь строки фиксированной длины, даже
если Вы имеете столбцы VARCHAR
,
опцией CREATE TABLE
ROW_FORMAT=FIXED
.Первичный индекс таблицы, должен быть настолько коротким,
насколько возможно. Это делает идентификацию каждой строки легкой и
эффективной. Для InnoDB
столбцы primary key
дублированы в каждом вторичном индексе, таким образом, короткий первичный
ключ оставляет значительное свободное место, если Вы имеете
много вторичных индексов.
При некоторых обстоятельствах может быть выгодно разделить на две таблицу, которая просматривается очень часто. Это особенно верно, если это таблица динамического формата, и возможно использовать меньшую статическую таблицу, которая может использоваться, чтобы найти соответствующие строки, просматривая таблицу.
customer
используйте столбец
name
вместо customer_name
.
Чтобы сделать Ваши имена переносимыми к другим SQL-серверам, рассмотрите
их сохранение короче 18 символов.Обычно попытайтесь сохранить все данные безызбыточными (то, что упомянуто в теории базы данных как third normal form). Вместо того, чтобы повторить длинные значения, такие как имена и адреса, назначьте им уникальные ID, повторите эти ID где надо через меньшие таблицы и присоединитесь к таблицам в запросах, ссылаясь на ID в join.
Для уникальных ID или других значений, которые могут быть представлены как строки или как числа, предпочтите, чтобы числовые столбцы представили столбцы в виде строки. Так как большие числовые значения могут быть сохранены в меньшем количестве байтов чем соответствующие строки, это быстрее и берет меньше памяти, чтобы передать и сравнить их.
Используйте двоичный порядок сопоставления для быстрого сравнения
и сортировки, когда Вы не нуждаетесь в определенных для языка особенностях
сопоставления. Вы можете использовать оператор
BINARY
, чтобы
использовать двоичное сопоставление в пределах особого запроса.
VARCHAR
вместо BLOB
. GROUP BY
и
ORDER BY
могут произвести временные таблицы, и эти временные
таблицы могут использовать MEMORY
,
если оригинальная таблица не содержит BLOB
.InnoDB
, примените префикс
со значением возрастания, таким как текущая дата и время, если возможно.
Когда последовательные основные значения физически сохранены друг около
друга, InnoDB
может вставить и получить их быстрее.Храня большой blob, содержащий текстовые данные, рассмотрите
сжатие этого сначала. Не используйте этот метод, когда вся таблица сжата
InnoDB
или MyISAM
.
MD5()
или CRC32()
, чтобы
произвести значение хеша. Так как функции хеша могут привести к двойным
результатам для различных вводов, Вы все еще включаете пункт
AND blob_column
=
long_string_value
в запрос, чтобы принять меры
против ложных соответствий, исполнительная выгода прибывает из меньшего,
легко просматриваемого индекса для хешированных значений.ANALYSE([
max_elements
[,max_memory
]])
ANALYSE()
исследует следствие запроса и возвращает анализ
результатов, который предлагает оптимальные типы данных для каждого столбца,
который может помочь уменьшить табличные размеры.
Чтобы получить этот анализ, добавьте PROCEDURE ANALYSE
к концу запроса SELECT
:
SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements
,[max_memory
]])
Например:
SELECT col1, col2 FROM table1 PROCEDURE ANALYSE(10, 2000);
Результаты показывают немного статистики для значений, возвращенных
запросом, и предлагают оптимальный тип данных для столбцов. Это может быть
полезно для того, чтобы проверить Ваши существующие таблицы, или после
импортирования новых данных. Вы, возможно, должны попробовать различные
настройки так, чтобы PROCEDURE ANALYSE()
не предлагал тип данных
ENUM
, когда это
не является соответствующим.
Параметры являются дополнительными и используются следующим образом:
max_elements
(по умолчанию 256)
максимальное количество отличных значений уведомлений ANALYSE()
на столбец. Это используется ANALYSE()
, чтобы
проверять, должен ли оптимальный тип данных иметь тип
ENUM
, если есть больше, чем
max_elements
отличных значений, тогда
ENUM
не подходит.
max_memory
(по умолчанию 8192) задает
максимальный объем памяти, который ANALYSE()
должен выделить столбцу, пытаясь найти все отличные значения.Некоторые методы для того, чтобы сохранить отдельные запросы быстрыми, вовлекают данные о разделении через многие таблицы. Когда число таблиц сталкивается с тысячами или даже миллионами, издержки контакта со всеми этими таблицами становятся новым исполнительным соображением.
Когда Вы выполняете mysqladmin status, Вы должны видеть что-то вроде этого:
Uptime: 426 Running threads: 1 Questions: 11082 Reloads: 1 Open tables: 12
Open tables
= 12 может быть несколько озадачивающим, если у
Вас есть только шесть таблиц.
MySQL мультипоточен, таким образом может быть много клиентов, выпускающих
запросы для данной таблицы одновременно. Чтобы минимизировать проблему с
многократными сеансами клиента, имеющими различные состояния на той же самой
таблице, таблица открыта независимо каждым параллельным сеансом.
Это использует дополнительную память, но обычно ускоряет работу. С
MyISAM
один дополнительный описатель файла требуется для файла с
данными для каждого клиента, у которого есть открытая таблица. В отличие от
этого, описатель индексного файла совместно использован всеми сеансами.
Переменные
table_open_cache
и
max_connections
затрагивают максимальное количество файлов,
которые сервер сохраняет открытыми. Если Вы увеличиваете одно или оба из этих
значений, Вы можете столкнуться с пределом, наложенным Вашей операционной
системой на число открытых описателей файла на процесс. Много операционных
систем разрешают Вам увеличивать предел открытых файлов, хотя метод
значительно различается от системы к системе. Консультируйтесь со своей
документацией операционной системы, чтобы определить, возможно ли увеличить
предел, и как это сделать.
table_open_cache
относится к
max_connections
. Например, для 200 параллельных рабочих
соединений, определите табличный размер кэша, по крайней мере, в 200 *
, где N
N
это
максимальное количество таблиц, участвующих в любом из запросов, который Вы
выполняете. Вы должны также зарезервировать некоторые дополнительные
описатели файла для временных таблиц и файлов.
Удостоверьтесь, что Ваша операционная система может обработать число
открытых описателей файла, подразумеваемых
table_open_cache
. Если
table_open_cache
установлен слишком высоко, MySQL может исчерпать
описатели файла и отказаться от соединений или быть не в состоянии выполнить
запросы, и быть очень ненадежным.
Вы должны также принять во внимание факт, что MyISAM
нуждается в двух описателях файла для каждой уникальной открытой таблицы. Для
разделенной таблицы MyISAM
два описателя файла требуются для
каждого раздела открытой таблицы. Отметьте что, когда
MyISAM
открывает разделенную таблицу, это открывает каждый
раздел этой таблицы, используется ли данный раздел фактически. См.
MyISAM and partition file descriptor usage.
Вы можете увеличить число описателей файла, доступных MySQL, используя опцию
--open-files-limit
при запуске mysqld
. См. раздел
B.5.2.17.
Кэш открытых таблиц сохранен на уровне
table_open_cache
. Сервер автоматически меняет размер кэша при запуске. Чтобы установить
размер явно, установите
table_open_cache
при запуске. Отметьте, что MySQL может временно
открыть больше таблиц, чем это значение, чтобы выполнить запросы.
MySQL закрывает неиспользованную таблицу и удаляет это из табличного кэша при следующих обстоятельствах:
Когда кэш полон, и поток пытается открыть таблицу, которая не находится в кэше.
table_open_cache
записей, и таблица в кэше больше не используются никакими потоками.FLUSH TABLES
или
mysqladmin flush-tables
(или
mysqladmin refresh).Когда табличный кэш заполняется, сервер использует следующую процедуру, чтобы определить местонахождение использумой записи кэша:
Таблицы, которые не используются в настоящее время, выпущены, начиная с последней использованной таблицы.
MyISAM
открыта для каждого параллельного доступа.
Это означает потребность открыть таблицу дважды, если два потока получают
доступ к той же самой таблице, или если поток получает доступ к таблице
дважды в том же самом запросе (например, соединяя таблицу с собой). Каждое
параллельное открытие требует записи в табличном кэше. Первое открытие любой
MyISAM
берет два описателя файла: один для файла с данными и
один для индексного файла. Каждое дополнительное использование таблицы берет
только один описатель файла для файла с данными. Описатель индексного файла
совместно использован среди всех потоков.
Если Вы открываете таблицу с HANDLER
, специализированный табличный
объект выделен для потока. Этот табличный объект не использован совместно
другими потоками и не закрыт до требований потока tbl_name
OPENHANDLER
или завершения потока.
Когда это происходит, таблица отложена в табличном кэше (если кэш не полон).
См. раздел 14.2.4.tbl_name
CLOSE
Вы можете определить, является ли Ваш табличный кэш слишком маленьким,
проверяя переменную
Opened_tables
, которая указывает на число открывающих таблицу
операций с момента запуска сервера:
mysql> SHOW GLOBAL STATUS LIKE 'Opened_tables'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Opened_tables | 2741 | +---------------+-------+
Если значение является очень большим или увеличивается быстро, даже когда
Вы не выполняли много FLUSH TABLES
,
увеличьте размер табличного кэша. См. разделы
6.1.5 и
6.1.7.
Если у Вас есть много таблиц MyISAM
в том же самом каталоге базы данных, операции открытия, создания и закрытия
являются медленными. Если Вы выполняете SELECT
на многих различных таблицах, добавляется небольшая издержка,
когда табличный кэш полон, потому что для каждой таблицы, которая должна быть
открыта, другая должна быть закрыта. Вы можете уменьшить эти издержки,
увеличивая число записей, разрешенных в табличном кэше.
В некоторых случаях сервер составляет внутренние временные таблицы, обрабатывая запросы. У пользователей нет никакого прямого управления, когда это происходит.
Сервер составляет временные таблицы при таких условиях:
Оценка UNION
с некоторыми исключениями, описанными позже.
TEMPTABLE
, UNION
или разную агрегацию.FROM
).ORDER
BY
и разные GROUP BY
или для которых
ORDER BY
или GROUP BY
содержит столбцы из таблиц, кроме первой таблицы в очереди соединения.DISTINCT
объединенный с ORDER BY
может потребовать временной таблицы.SQL_SMALL_RESULT
,
MySQL использует временную таблицу в памяти, если запрос также не содержит
элементов (описаны позже), которые требуют хранения на диске.UPDATE
.
GROUP_CONCAT()
или COUNT(DISTINCT)
.Чтобы определить, требует ли запрос временной таблицы, надо использовать
EXPLAIN
и проверьте столбец
Extra
, чтобы видеть, говорит ли это
Using temporary
(см. раздел
9.8.1). EXPLAIN
не обязательно скажет Using temporary
для полученных или осуществленных временных таблиц.
Когда сервер составляет внутреннюю временную таблицу (в памяти или на
диске), это постепенно увеличивает переменную
Created_tmp_tables
. Если сервер составляет таблицу на диске (первоначально или
преобразовывая таблицу в памяти), это постепенно увеличивает переменную
Created_tmp_disk_tables
.
Некоторые условия запроса предотвращают использование временной таблицы в памяти, когда сервер использует таблицу на диске вместо этого:
SELECT
, если применено
UNION
или
UNION ALL
.SHOW COLUMNS
и
DESCRIBE
используют
BLOB
как тип столбца, таким образом временная таблица,
используемая для результатов, является таблицей на диске.Сервер не использует временную таблицу для
UNION
, которые встречают
определенные квалификации. Вместо этого это сохраняет из временной таблицы
только структуры данных, необходимые, чтобы выполнить подбор типов.
Таблица не полностью инстанцирует, и никакие строки не написаны или считаны
из нее, строки посылают непосредственно клиенту. Результат уменьшение
требований к памяти и диску и меньшая задержка прежде, чем первую строку
пошлют клиенту, потому что сервер не должен ждать, пока последний блок
запроса будет выполнен. EXPLAIN
и
трассировка оптимизатора отражают эту стратегию выполнения: блок запроса
UNION RESULT
не присутствует, потому что тот блок соответствует
части, которая читает из временной таблицы.
Эти условия квалифицируют UNION
для оценки без временной таблицы:
Союз UNION ALL
, но не
UNION
или UNION DISTINCT
.
ORDER BY
.{INSERT | REPLACE} ... SELECT ...
.Внутренняя временная таблица может быть проведена в памяти и обработана
MEMORY
или сохранена на диске как
InnoDB
или MyISAM
.
Если внутренняя временная таблица составлена как таблица в памяти,
но становится слишком большой, MySQL автоматически преобразовывает ее в
таблицу на диске. Максимальный размер для временных таблиц в памяти определен
от значений переменных
tmp_table_size
и
max_heap_table_size
(какое меньше). Это отличается от MEMORY
, явно
составленных с CREATE TABLE
:
для таких таблиц, только переменная
max_heap_table_size
определяет, как сильно разрешают вырасти большой таблице и нет
никакого преобразования в формат на диске.
Переменная
internal_tmp_disk_storage_engine
определяет, который
механизм хранения использует сервер, чтобы управлять внутренними временными
таблицами на диске. Разрешенные значения INNODB
(по умолчанию) и MYISAM
.
Используя
internal_tmp_disk_storage_engine=INNODB
,
запросы, которые производят временное табличное превышение
лимита строк или столбцов в
InnoDB
вернут ошибку Row size too large или
Too many columns. Обходное решение должно установить
internal_tmp_disk_storage_engine
в MYISAM
.
В памяти временными таблицами управляет механизм хранения
MEMORY
, который использует формат строки фиксированной длины.
VARCHAR
и VARBINARY
дополнены к максимальной длине столбца, в действительности храня их как
столбцы CHAR
и BINARY
.
На диске временными таблицами управляют
InnoDB
или MyISAM
(в зависимости от
internal_tmp_disk_storage_engine
).
Оба механизма хранят временные таблицы, используя формат строки динамической
ширины. Столбцы берут только столько места, сколько надо, что уменьшает
дисковый ввод/вывод, требования пространства и время обработки по сравнению с
таблицами на диске с использованием фиксированной длины строки.
Для запросов, которые первоначально составляют внутреннюю временную
таблицу в памяти, затем преобразуют ее в таблицу на диске, лучшая работа
могла бы быть достигнута, пропуская конверсионный шаг и составляя таблицу на
диске для начала. Системная переменная
big_tables
может
использоваться, чтобы вызвать дисковое хранение внутренних временных таблиц.
InnoDB
это механизм хранения,
который клиенты MySQL, как правило, используют в производственных базах
данных, где надежность и параллелизм важны. Это механизм хранения по
умолчанию в MySQL. Этот раздел объясняет, как оптимизировать операции базы
данных для InnoDB
.
Как только Ваши данные достигают устойчивого размера
или растущая таблица увеличилась на десятки или сотни мегабайтов,
рассмотрите использование OPTIMIZE TABLE
, чтобы реорганизовать
таблицу и сжать любое потраченное впустую пространство. Реорганизованные
таблицы требуют меньше дискового ввода/вывода для полного сканирования
таблицы. Это прямой метод, который может улучшить работу, когда другие
методы, такие как улучшение индекса или настраивающийся код
программы не практичны.
OPTIMIZE TABLE
копирует часть данных таблицы и
восстанавливает индексирование. Выгода прибывает из улучшенной упаковки
данных в пределах индекса и уменьшения фрагментации в пределах табличных
пространств и на диске. Выгода изменяется в зависимости от данных по каждой
таблице. Вы можете найти, что есть существенная прибыль для некоторых, а не
для других, или что прибыль уменьшается в течение долгого времени, пока Вы
затем не оптимизируете таблицу. Эта работа может быть медленной, если таблица
большая или индекс не вписывается в буферный пул. Первый раз после добавления
большого количества данных к таблице часто намного медленнее, чем
более поздние выполнения.
InnoDB
наличие длинного PRIMARY KEY
(единственный столбец с длинным значением или несколько столбцов, которые
формируют длинное сложное значение) тратит впустую много дискового
пространства. Значение первичного ключа для строки дублировано во всех
вторичных индексах, которые указывают на ту же самую строку. См.
раздел 16.8.8.
Создайте столбец AUTO_INCREMENT
как первичный ключ, если Ваш
первичный ключ длинен, или индексируйте префикс VARCHAR
вместо всего столбца.VARCHAR
вместо
CHAR
, чтобы
сохранить строки переменной длины или для столбцов со многими
NULL
. CHAR(N
)
всегда берет N
символов, чтобы
хранить данные, даже если строка короче или ее значение NULL
.
Используя формат строки COMPACT
(значение по умолчанию в
InnoDB
) и наборы символов переменной длины, например,
utf8
или sjis
,
CHAR(
занимают переменное количество пространства, но все еще по крайней мере,
N
)N
байт.
COMPRESSED
. Меньше дискового ввода/вывода
нужно, чтобы приносить данные в буферный пул или выполнять полное
сканирование таблицы. Прежде, чем принять постоянное решение, измерьте
коэффициент сжатия, при использовании фората COMPRESSED
.
Чтобы оптимизировать обработку транзакций, найдите идеальный баланс между издержками транзакционных особенностей и рабочей нагрузкой Вашего сервера. Например, приложение могло бы столкнуться с исполнительными проблемами, если оно передает тысячи раз в секунду и совсем другими проблемами, если оно передает только каждые 2-3 часа.
Установка MySQL значения по умолчанию AUTOCOMMIT=1
может наложить исполнительные ограничения на занятый сервер базы данных.
Где практично, оберните несколько связанных операций изменения данных в
единственную транзакцию через SET AUTOCOMMIT=0
или START
TRANSACTION
, сопровождаемый COMMIT
.
InnoDB
должен сбросить журнал на диск в каждой передаче
транзакции, если та транзакция сделала модификации базы данных. Когда каждое
изменение сопровождается передачей, пропускная способность ввода/вывода
устройства хранения данных имеет решающее значение.
SELECT
, включение
AUTOCOMMIT
помогает InnoDB
признать транзакции только для чтения и оптимизировать их. См.
раздел 9.5.3.Минимизировать шанс этого проявления проблемы:
Увеличьте размер буферного пула так, чтобы все изменения изменения данных могли кэшироваться, а не немедленно были записаны на диск.
innodb_change_buffering=all
так, чтобы операции обновления и
удаления были буферизованы в дополнение к вставкам.COMMIT
периодически во время большой работы изменения данных, возможно деля
одиночный запрос на несколько, которые воздействуют на меньшее число строк.
Чтобы избавиться от безудержной обратной перемотки, как только это
происходит, увеличьте буферный пул так, чтобы обратная перемотка стала
ограничиваться центральным процессором и работала быстро, или уничтожьте
сервер и перезапустите его с
innodb_force_recovery=3
, см.
раздел 16.17.1.
Эта проблема, как ожидают, будет нечастой с настройкой по умолчанию
innodb_change_buffering=all
, которая позволяет операции
обновления и удаления, которые будут кэшироваться в памяти, делая их быстрее,
и также быстрее откатиться, если нужно. Удостоверьтесь, что использовали эту
установку параметра на серверах, которые обрабатывают продолжительные
транзакции со многими вставками, обновлениями или удалениями.
innodb_flush_log_at_trx_commit
в 0. InnoDB
будет
пытаться сбросить журнал однажды в секунду, хотя сброс не гарантируется.READ COMMITTED
и REPEATABLE READ
должны сделать больше работы, чтобы восстановить более старые данные,
если они читают те же самые строки.Если у вторичных индексных страниц есть
PAGE_MAX_TRX_ID
, который слишком новый, или
если записи во вторичном индексе отмечены как удаленные, InnoDB
,
возможно, должен искать записи, используя кластеризируемый индекс.
InnoDB
может избежать издержек, связанных с
transaction ID
(поле TRX_ID
) для транзакций, которые только для чтения.
Операционный ID необходим для
транзакции, которая могла бы выполнить записи или
блокирующие чтения, например,
SELECT ... FOR UPDATE
. Устранение ненужных операционных ID
уменьшает размер внутренних структур данных, с которыми консультируются
каждый раз, когда запрос изменения запроса или данных
создает представление чтения.
InnoDB
обнаруживает транзакции только для чтения когда:
Транзакция запущена с START
TRANSACTION READ ONLY
. В этом случае попытка произвести изменения
в базе данных (для InnoDB
, MyISAM
или других типов
таблиц) вызывает ошибку, и транзакция продолжается в статусе
только для чтения:
ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.
Вы можете все еще произвести изменения в определенных для сеанса временных таблицах в транзакции только для чтения или запросить блокировки для них, потому что те изменения и блокировки невидимы любой другой транзакции.
autocommit
включен, чтобы транзакция точно была единственным запросм, а единственный
запрос, составляющий транзакцию, является неблокирующим
SELECT
. Таким образом,
этот SELECT
не использует FOR UPDATE
или
LOCK IN SHARED MODE
.READ ONLY
,
но никакие обновления или запросы, которые явно блокируют строки, не были
выполнены. Пока обновления или явные блокировки не требуются, транзакция
остается в режиме только для чтения.Таким образом, для интенсивного чтения, такого как генератор отчетов, Вы
можете настроить последовательность запросов, группируя их в
START TRANSACTION READ ONLY
и
COMMIT
или включая
autocommit
прежде, чем выполнить SELECT
, или просто избегая любых
изменяющих данные запросов между ними.
См. раздел 14.3.1.
Транзакции, которые готовятся, как auto-commit без блокировки и только для
чтения (AC-NL-RO), не допущены к внутренней структуре данных
InnoDB
и поэтому не перечислены в выводе
SHOW ENGINE INNODB STATUS
.
Сделайте файлы журнала redo размером как
буферный пул. Когда
InnoDB
пишет в полные файлы системного журнала, это должно
записать измененное содержание буферного пула на диск в
контрольную точку.
Маленький файл приводит к большому числу лишних дисковых записей.
Хотя исторически большой файл вызывал долгое время восстановления файлов
системного журнала, восстановление теперь намного быстрее, и Вы можете
уверенно использовать большой файл системного журнала.
Размер и число файлов системного журнала сконфигурированы, используя
опции
innodb_log_file_size
и
innodb_log_files_in_group
, см.
раздел 16.7.2.
innodb_log_buffer_size
.Эти исполнительные подсказки добавляют общие руководящие принципы для быстрых вставок в разделе 9.2.2.1.
Импортируя данные в InnoDB
, выключите autocommit,
потому что он выполняет сброс данных журнала на диск для каждой вставки.
Чтобы отключить autocommit во время Вашей работы импорта, используйте
SET autocommit
и
COMMIT
:
SET autocommit=0;
... SQL import statements ...
COMMIT;
mysqldump
с опцией --opt
создает файлы дампа, которые быстры, чтобы импортировать в
InnoDB
, даже не обертывая их с
SET autocommit
и
COMMIT
.
UNIQUE
на вторичные ключи, Вы
можете ускорить табличный импорт, временно выключая проверки уникальности во
время сеанса импорта:
SET unique_checks=0;
... SQL import statements ...
SET unique_checks=1;
Для больших таблиц это сохраняет много дискового ввода/вывода, потому что
InnoDB
может использовать его буфер изменения, чтобы написать
вторичные индексные записи в пакете. Будьте уверенны, что данные не
содержат дубликаты ключей.
FOREIGN KEY
в Ваших таблицах, Вы можете ускорить табличный импорт, выключая проверки
внешнего ключа на продолжительность сеанса импорта:
SET foreign_key_checks=0;
... SQL import statements ...
SET foreign_key_checks=1;
Для больших таблиц это может сохранить много дискового ввода/вывода.
INSERT
, чтобы уменьшить издержки связи между клиентом и сервером, если Вы
должны вставить много строк:
INSERT INTO yourtable VALUES (1,2), (5,5), ...;
Эта подсказка допустима для вставок в любую таблицу, не только
InnoDB
.
innodb_autoinc_lock_mode
в 2 вместо значения по умолчанию 1. См.
раздел 16.8.5.PRIMARY KEY
. Таблицы InnoDB
используют кластеризируемый
индекс,который делает относительно быстрым использование данных в порядке
PRIMARY KEY
. Выполнение большой вставки в порядке
PRIMARY KEY
особенно важно для таблиц, которые не помещаются
полностью в буферном пуле.InnoDB
FULLTEXT
,
следуйте за этим набором шагов:
Определите столбец FTS_DOC_ID
при создании
таблицы, тип BIGINT UNSIGNED NOT NULL
, с уникальным индексом
FTS_DOC_ID_INDEX
:
CREATE TABLE t1 (FTS_DOC_ID BIGINT unsigned NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL DEFAULT , text mediumtext NOT NULL, PRIMARY KEY (`FTS_DOC_ID`)) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE UNIQUE INDEX FTS_DOC_ID_INDEX on t1(FTS_DOC_ID);
FULLTEXT
после того, как данные загружены.
Добавляя столбец FTS_DOC_ID
во время создания таблиы, гарантируйте, что столбец FTS_DOC_ID
обновлен, когда индексированный FULLTEXT
столбец обновлен.
FTS_DOC_ID
должен увеличиться монотонно с каждым вызовом
INSERT
или
UPDATE
.
Если Вы хотите не добавлять FTS_DOC_ID
при создании таблицы
и имеете управление InnoDB
DOC ID, InnoDB
добавит
FTS_DOC_ID
как скрытый столбец со следующим вызовом
CREATE FULLTEXT INDEX
. Этот
подход, однако, требует пересоздания таблицы, что будет
воздействовать на работу.
Чтобы настроить запросы для InnoDB
,
создайте соответствующий набор индексов на каждой таблице. См.
раздел 9.3.1.
Поскольку у каждой таблицы InnoDB
есть
первичный ключ (запрашиваете его
или нет), определите ряд столбцов первичного ключа для каждой таблицы,
столбцы которой используются в самых важных и срочных запросах.
NULL
,
определите его как NOT NULL
, когда Вы составляете таблицу.
Оптимизатор может лучше определить, который индекс является самым
эффективным, чтобы использовать для запроса, когда он знает, содержит ли
каждый столбец NULL
.InnoDB
, используя метод в
разделе 9.5.3.[mysqld] query_cache_type = 1 query_cache_size = 10M
Для операций DDL на таблицах и индексах
(CREATE
, ALTER
и DROP
),
очень значащий аспект для таблиц InnoDB
то, что создание и
удаление вторичных индексов намного быстрее в MySQL 5.5 и выше, чем в более
ранних выпусках. См. раздел
16.12.1.
TRUNCATE TABLE
, чтобы освободить таблицу, а не DELETE FROM
tbl_name
. Ограничения внешнего ключа могут
сделать TRUNCATE
похожим на DELETE
, когда
последовательность команд DROP TABLE
и CREATE TABLE
могло бы быть самым быстрым решением.InnoDB
и изменение определения
первичного ключа вовлекает реорганизацию целой таблицы, всегда настраивайте
первичный ключ как часть CREATE TABLE
и планируйте заранее так, чтобы Вы не нуждались в
ALTER
или DROP
для primary key.Если Вы следуете за лучшими методами для проектирования баз данных и
настройки операций SQL, но Вашу базу данных все еще замедляет тяжелая
дисковая деятельность ввода/вывода, исследуйте эти низкоуровневые методы,
связанные с дисковым вводом/выводом. Если в Unix команда top
или
Windows Task Manager показывают, что процент использования центрального
процессора с Вашей рабочей нагрузкой составляет меньше 70%,
Ваша рабочая нагрузка является, вероятно, связанной с диском.
Когда табличные данные кэшируются в буферном пуле
InnoDB
, к этому могут неоднократно получать доступ запросы, не
требуя никакого дискового ввода/вывода. Определите размер буферного пула с
опцией
innodb_buffer_pool_size
. Эта область памяти достаточно важна, что,
как правило, рекомендуют установить
innodb_buffer_pool_size
от 50 до 75
процентов системной памяти. Для получения дополнительной информации см.
раздел 9.12.3.1.
fsync()
(InnoDB
использует его по
умолчанию) и подобные методы является удивительно медленным.
Если запись базы данных проблема, проверьте, не поможет ли изменение
innodb_flush_method
на O_DSYNC
.InnoDB
на
Solaris 10 для x86_64 (AMD Opteron), используйте direct
I/O для файлов InnoDB
, чтобы избежать деградации
производительности InnoDB
. Чтобы использовать прямой ввод/вывод
для всей файловой системы UFS, используемой для того, чтобы сохранить
файлы InnoDB
, смонтируйте ее с опцией
forcedirectio
, см. mount_ufs(1M)
. Значение по
умолчанию на Solaris 10/x86_64 не
использует эту опцию. Чтобы применить прямой ввод/вывод только к файлам
InnoDB
, а не целой файловой системе, установите
innodb_flush_method =
O_DIRECT
. С этой установкой InnoDB
вызывает
directio()
вместо fcntl()
для ввода/вывода к
файлам с данными (не для ввода/вывода к файлам системного журнала).InnoDB
с большим значением
innodb_buffer_pool_size
на Solaris 2.6 и выше на любой платформе
(sparc/x86/x64/amd64), проведите точки отсчета с файлами InnoDB
на сырых устройствах или на отдельном прямом вводе/выводе файловой системы
UFS, используя опцию монтирования forcedirectio
.
Необходимо использовать опцию вместо установки
innodb_flush_method
, если Вы хотите прямой ввод/вывод для файлов системного журнала.
Пользователи файловой системы Veritas VxFS должны использовать опцию
монтирования convosync=direct
.
Не помещайте другие файлы с данными MySQL на файловой системе прямого ввода/вывода.
InnoDB
, рассмотрите увеличение значения
innodb_io_capacity
. Более высокие значения вызывают более частый
сброс, избегая отставания в работе,
которое может вызвать падения в пропускной способности.InnoDB
,
рассмотрите понижение значения
innodb_io_capacity
. Как правило, Вы сохраняете это значение опции столь низким, как
практично, но не настолько низким, что оно вызывает периодические падения
пропускной способности, как упомянуто выше. В типичном скрипте, где Вы могли
понизить значение опции, Вы могли бы видеть это в выводе
SHOW ENGINE INNODB STATUS
:
Длина списка истории низкая, ниже нескольких тысяч.
innodb_max_dirty_pages_pct
из буферного пула в то время, когда
сервер не делает больших вставок, это нормально во время большой вставки.
Log sequence number - Last checkpoint
меньше, чем 7/8 или
идеально меньше, чем 6/8 полного размера
файлов системного журнала
InnoDB
.innodb_doublewrite
) автоматически отключен и используется атомная запись Fusion-io
для всех файлов с данными. Эта функция поддерживается только на аппаратных
средствах Fusion-io и включена только для Fusion-io NVMFS в Linux.
Чтобы в полной мере воспользоваться этой особенностью, рекомендуется
innodb_flush_method
O_DIRECT
.
Поскольку буферная установка doublewrite глобальна, буферизация doublewrite также отключена для файлов с данными, находящихся на устройствах не-Fusion-io.
InnoDB
сжатие таблиц, образы пересжатых
страниц написаны в
журнал redo, когда изменения
произведены в сжатых данных. Этим поведением управляет параметр
innodb_log_compressed_pages
, который включен по умолчанию, чтобы
предотвратить повреждение, которое может произойти, если различная версия
zlib
используется во время восстановления. Если Вы уверены, что
версия zlib
не будет изменяться, отключите
innodb_log_compressed_pages
, чтобы уменьшить журнал redo
для рабочих нагрузок, которые изменяют сжатые данные.InnoDB
, которые стоит
рассмотреть, настраивая рабочие нагрузки I/O, включают
innodb_adaptive_flushing
,
innodb_change_buffer_max_size
,
innodb_change_buffering
,
innodb_flush_neighbors
,
innodb_log_buffer_size
,
innodb_log_file_size
,
innodb_lru_scan_depth
,
innodb_max_dirty_pages_pct
,
innodb_max_purge_lag
,
innodb_open_files
,
innodb_page_size
,
innodb_random_read_ahead
,
innodb_read_ahead_threshold
,
innodb_read_io_threads
,
innodb_rollback_segments
,
innodb_write_io_threads
и
sync_binlog
.
Различные настройки работают лучше всего на серверах с легкими и предсказуемыми загрузками.
Поскольку InnoDB
многие из своих оптимизаций автоматически,
много настраивающих работу задач вовлекают контроль, чтобы гарантировать, что
база данных работает хорошо и изменять параметры конфигурации, когда работа
нарушается. См. раздел 16.15
.
Основные шаги конфигурации, которые Вы можете выполнить, включают:
Управление типами данных изменяет операции для которых
InnoDB
буферизует измененные данные, чтобы избежать
частых маленьких операций записи на диск. См.
раздел 16.6.4.
Поскольку значение по умолчанию должно буферизовать все типы операций
изменения данных, надо изменить только эти настройки, если Вы должны
уменьшить объем буферизации.
innodb_adaptive_hash_index
. См.
раздел 16.4.3.
Вы могли бы изменить эти настройки во время периодов необычной
деятельности, затем восстановить к оригинальной установке.InnoDB
, если переключение контекста узкое место. См.
раздел 16.6.5.
InnoDB
.
Когда у системы есть неиспользованная способность ввода/вывода, чтение вперед
может улучшить исполнение запросов. Слишком большая предвыборка может вызвать
периодические падения работы на сильно загруженной системе. См.
раздел 16.6.3.5.InnoDB
идет в фоне. См.
раздел 16.6.7.
Вы могли бы вычислить эту установку, если Вы наблюдаете
периодические падения работы.InnoDB
использует определенные типы фоновой записи. См.
раздел 16.6.3.6
. Алгоритм для некоторых типов рабочих нагрузок, но не других, мог бы
выключить эту установку, если Вы наблюдаете периодические падения работы.
InnoDB
. См. подробности в
разделе 16.6.3.4
.InnoDB
часто сохранялись маленькими, чтобы избежать
долгих времен запуска после катастрофического отказа. Оптимизация, введенная
в MySQL 5.5, ускоряет определенные шаги процесса
восстановления. В частности,
просмотр и применение журнала redo
происходят быстрее из-за улучшенных алгоритмов для управления памятью. Если
Вы сохранили свои файлы системного журнала искусственно маленькими, чтобы
избежать долгих времен запуска, Вы можете теперь полагать, что
увеличивающийся размер файла системного журнала уменьшает ввод/вывод.InnoDB
, особенно важно для систем с буферными пулами в
несколько гигабайт. См.
раздел 16.6.3.3.InnoDB
делает между параллельными потоками, так, чтобы операции SQL на занятом
сервере не стояли в очереди и сформировали поток трафика
. Установите значение для опции
innodb_thread_concurrency
до приблизительно 32 для мощной современной системы. Увеличьте значение
опции
innodb_concurrency_tickets
, как правило к 5000 или около этого.
Эта комбинация опций устанавливает ограничение на число потоков, которые
InnoDB
обрабатывает в любой момент и позволяет каждому потоку
сделать существенную работу прежде, чем быть переключенным, чтобы число
потоков ожидания осталось низким, и операции могут завершиться без
чрезмерного переключения контекста.InnoDB
вычисляет индексные значения
количества элементов
для таблицы в первый раз, когда к таблице получают доступ после запуска,
вместо того, чтобы хранить такие значения в таблице. Этот шаг может занять
время на системах, которые делят данные на многие таблицы. Так как это
относится только к начальной работе открытия таблицы, к
нагретым для более позднего использования таблицам
немедленно получают доступ после запуска, делая такой запрос, как
SELECT 1 FROM
.
tbl_name
LIMIT 1
MyISAM
выступает лучше всего с данными главным образом для чтения или с операциями
низкого параллелизма, потому что табличные блокировки ограничивают
способность выполнить одновременные обновления. В MySQL
InnoDB
механизм хранения по умолчанию, а не MyISAM
.
Некоторые общие советы для того, чтобы ускорить запросы
на таблицах MyISAM
:
Чтобы помочь MySQL лучше оптимизировать запросы, стоит
использовать ANALYZE TABLE
или myisamchk --analyze
на таблице после того, как это было загружено данными.
Это обновляет значение для каждой части индекса, которая указывает на среднее
число строк, у которых есть то же самое значение, поскольку уникальный индекс
это всегда 1. MySQL использует это, чтобы решить, который индекс выбрать,
когда Вы присоединяетесь к двум таблицам, основанным на непостоянном
выражении. Вы можете проверить следствие табличного анализа при использовании
SHOW INDEX FROM
и исследуя
значение tbl_name
Cardinality
.
myisamchk --description --verbose показывает
информацию о распределении индекса.
SELECT
на MyISAM
, которые часто обновляются, чтобы избежать
проблем с блокировкой таблицы.MyISAM
допускает параллельные вставки: если у таблицы нет
никаких свободных блоков в середине файла с данными, Вы можете
INSERT
новые строки в то же самое
время, когда другие потоки читают из таблицы. Если важно быть в состоянии
сделать это, рассмотрите использование таблицы способами, которые избегают
удалять строки. Другая возможность состоит в том, чтобы выполнить
OPTIMIZE TABLE
, чтобы
дефрагментировать таблицу после того, как Вы удалили много строк.
Это поведение изменено, устанавливая
concurrent_insert
. Вы можете вынудить новые строки быть добавленными
(и поэтому разрешить параллельные вставки), даже в таблицах, которые удалили
строки. См. раздел 9.11.3.MyISAM
таблицы, которые часто изменяются, пытаются
избежать всех столбцов переменной длины
(VARCHAR
,
BLOB
и
TEXT
).
Таблица использует динамический формат строки, если это включает даже
единственный столбец переменной длины. См. главу 17
.ALTER TABLE ... ORDER BY expr1
,
expr2
, ...
, если Вы обычно получаете строки в
порядке expr1
,
expr2
, ...
. При использовании этой опции после
обширных изменений таблицы Вы можете быть в состоянии получить
более высокую работу.UPDATEtbl_name
SETcount_col
=count_col
+1 WHEREkey_col
=constant
;
Это очень важно, когда Вы используете механизмы хранения MySQL, такие как
MyISAM
, у которых есть только блокировка на уровне таблицы.
Это также дает лучшую работу с большинством систем базы данных, потому что
менеджер блокировки строк в этом случае загружен меньше.
OPTIMIZE
TABLE
, чтобы избегать фрагментации с динамическим форматом
таблиц MyISAM
, см.
раздел 17.2.3.MyISAM
с опцией
DELAY_KEY_WRITE=1
делает индексные обновления быстрее, потому
что они не сбрасываются на диск, пока таблица не закрыта. Проблема в
том, что если что-то уничтожает сервер в то время как, такая таблица открыта,
Вы должны гарантировать, что таблица в порядке, выполняя сервер с опцией
--myisam-recover-options
или выполняя
myisamchk
прежде, чем перезапустить сервер. Однако, даже в этом случае, Вы ничего не
должны потерять при использовании DELAY_KEY_WRITE
, потому что
ключевая информация может всегда производиться от строк данных.Эти исполнительные подсказки добавляют общие руководящие принципы для быстрых вставок в разделе 9.2.2.1.
Для MyISAM
Вы можете использовать параллельные
вставки, чтобы добавить строки в то же самое время, когда работает
SELECT
,
если нет никаких удаленных строк в середине файла с данными. См.
раздел 9.11.3.
LOAD DATA INFILE
еще быстрее для
MyISAM
, когда у таблицы есть много индексов.
Используйте следующую процедуру:
Выполните FLUSH
TABLES
или
mysqladmin flush-tables.
/path/to/db/tbl_name
, чтобы удалить все индексы для таблицы.LOAD DATA INFILE
.
Это не обновляет индексы и поэтому очень быстро./path/to/db/tbl_name
.
Это создает индексное дерево в памяти прежде, чем написать на диск, что
намного быстрее, чем обновление индексирования во время
LOAD DATA INFILE
,
потому что это избегает многих дисковых поисков. Получающиеся индексное
дерево также отлично сбалансировано.FLUSH TABLES
или
mysqladmin flush-tables
.LOAD DATA INFILE
выполняет предыдущую оптимизацию автоматически, если таблица, в которую Вы
вставляете данные, пуста. Основное различие между автоматической оптимизацией
и использованием процедуры явно то, что Вы можете позволить
myisamchk
выделять намного больше временной памяти для создания индекса, чем Вы могли
бы хотеть, чтобы сервер выделил для воссоздания индекса, когда это выполняет
LOAD DATA INFILE
.
Вы можете также отключить или включить групповые индексы для таблиц
MyISAM
при использовании следующих запросов, а не
myisamchk.
Если Вы используете эти запросы, Вы можете пропустить шаг
FLUSH TABLE
:
ALTER TABLEtbl_name
DISABLE KEYS; ALTER TABLEtbl_name
ENABLE KEYS;
INSERT
, которые
выполнены с многими запросами для нетранзакционных таблиц,
блокируйте Ваши таблицы:
LOCK TABLES a WRITE; INSERT INTO a VALUES (1,23),(2,34),(4,33); INSERT INTO a VALUES (8,26),(6,29); ... UNLOCK TABLES;
Это приносит пользу работе, потому что индексный буфер сбрасывается на
диск только однажды, в конце всех
INSERT
.
Обычно было бы так много индексных буферных сбросов, сколько
INSERT
.
Явные запросы блокировки не необходимы, если Вы можете вставить все строки
одним вызовом INSERT
.
Блокировка также удаляет полное время для тестов многократного соединения, хотя максимум ожидания для отдельных соединений могло бы повыситься, потому что они ждут блокировок. Предположите, что пять клиентов пытаются вставлять одновременно следующим образом:
Соединение 1 делает 1000 вставок.
Если Вы не используете блокировку, соединения 2, 3 и 4 закончат до 1 и 5. Если Вы используете блокировку, соединения 2, 3 и 4, вероятно, не заканчиваются прежде 1 или 5, но полное время должно быть приблизительно на 40% быстрее.
INSERT
,
UPDATE
и
DELETE
очень быстры в MySQL, но Вы можете получить лучшую эффективность работы,
добавляя блокировки вокруг всего, что делает больше, чем приблизительно пять
последовательных вставок или обновлений. Если Вы делаете очень много
последовательных вставок, Вы могли бы сделать
LOCK TABLES
, сопровождаемый
UNLOCK TABLES
время от времени (приблизительно каждые 1000 строк), чтобы разрешить другим
потокам получать доступ к таблице. Это все еще привело бы к
хорошему приросту производительности.
INSERT
все еще намного медленнее для того, чтобы загрузить данные, чем
LOAD DATA INFILE
,
даже когда использование стратегий только обрисовано в общих чертах.
MyISAM
, для обоих
LOAD DATA INFILE
и
INSERT
, увеличьте ключевой кэш,
увеличивая переменную
key_buffer_size
, см.
раздел 6.1.1.REPAIR TABLE
для
MyISAM
подобен использованию
myisamchk
для операций ремонта, и часть той же самой
исполнительной оптимизации применяется:
myisamchk имеет переменные для управления распределением памяти. Вы можете быть в состоянии улучшить работу, устанавливая эти переменные, как описано в разделе 5.6.4.6.
REPAIR TABLE
тот же самый принцип применяется, но потому что ремонт сделан сервером, Вы
устанавливаете системные переменные сервера вместо
myisamchk.
Кроме того, в дополнение к установке переменных распределения памяти,
увеличение
myisam_max_sort_file_size
увеличивает вероятность, что ремонт будет использовать быстрый метод filesort
и избегать более медленного ремонта методом ключевого кэша.
Установите переменную в максимальный размер файла для Вашей системы, после
проверки, что есть достаточное свободное пространство, чтобы разместить копию
табличных файлов. Свободное пространство должно быть доступным в файловой
системе, содержащей оригинальные табличные файлы.Предположите, что myisamchk работает, используя следующие опции, чтобы установить переменные распределения памяти:
--key_buffer_size=128M --myisam_sort_buffer_size=256M --read_buffer_size=64M --write_buffer_size=64M
Некоторые из тех переменных myisamchk соответствуют переменным сервера:
Переменная myisamchk | Системная переменная |
---|---|
key_buffer_size |
key_buffer_size
|
myisam_sort_buffer_size |
myisam_sort_buffer_size |
read_buffer_size |
read_buffer_size
|
write_buffer_size | Нет |
Каждая из системных переменных сервера может быть установлена во время
выполнения, и некоторые из них
(
myisam_sort_buffer_size
,
read_buffer_size
) имеют значение сеанса в дополнение к глобальному значению. Установка
значения сеанса ограничивает эффект изменения Вашего текущего сеанса и не
затрагивает других пользователей. Замена глобальной переменной
(key_buffer_size
,
myisam_max_sort_file_size
)
затрагивает других пользователей также. Для
key_buffer_size
Вы должны принять во внимание, что буфер совместно использован с теми
пользователями. Например, если Вы устанавливаете переменную
myisamchk
key_buffer_size
в 128MB, Вы могли установить
key_buffer_size
больше чем это (если это еще не установлено больше), чтобы разрешить ключевое
буферное использование деятельностью в других сеансах. Однако, изменение
глобального ключевого размера буфера лишает законной силы буфер, вызывая
увеличенный дисковый ввод/вывод и замедление для других сеансов.
Альтернатива, которая избегает этой проблемы, должна использовать отдельный
ключевой кэш для индексов из восстанавливаемой таблицы, который надо
освободить, когда ремонт закончен. См.
раздел 9.10.2.2.
Основанный на предыдущих замечаниях,
REPAIR TABLE
может быть сделан следующим образом, чтобы
использовать настройки, подобные
myisamchk. Здесь отдельный ключевой буфер 128 МБ
выделен, и файловая система, как предполагается, разрешает размер файла по
крайней мере 100 GB.
SET SESSION myisam_sort_buffer_size = 256*1024*1024; SET SESSION read_buffer_size = 64*1024*1024; SET GLOBAL myisam_max_sort_file_size = 100*1024*1024*1024; SET GLOBAL repair_cache.key_buffer_size = 128*1024*1024; CACHE INDEXtbl_name
IN repair_cache; LOAD INDEX INTO CACHEtbl_name
; REPAIR TABLEtbl_name
; SET GLOBAL repair_cache.key_buffer_size = 0;
Если Вы намереваетесь заменить глобальную переменную, но хотите
сделать это только для работы REPAIR
TABLE
, чтобы минимально затронуть других пользователей, сохраните
ее значение в пользовательской переменной и восстановите ее позже:
SET @old_myisam_sort_buffer_size = @@global.myisam_max_sort_file_size; SET GLOBAL myisam_max_sort_file_size = 100*1024*1024*1024; REPAIR TABLE tbl_name; SET GLOBAL myisam_max_sort_file_size = @old_myisam_max_sort_file_size;
Системные переменные, влияющие на
REPAIR TABLE
, может быть установлен глобально при запуске сервера,
если Вы хотите, чтобы значения были заданы по умолчанию. Например, добавьте
эти строки к файлу my.cnf
:
[mysqld] myisam_sort_buffer_size=256M key_buffer_size=1G myisam_max_sort_file_size=100G
Эти настройки не включают
read_buffer_size
. Установка
read_buffer_size
глобально к большому значению делает это для всех
сеансов и может заставить работу пострадать из-за чрезмерного распределения
памяти для сервера с многими одновременными сеансами.
Рассмотрите использование MEMORY
для некритических данных, к
которым часто получают доступ только для чтения (или они редко обновлены).
Для лучшей работы с MEMORY
исследуйте виды запросов каждой
таблицы и определите тип для каждого связанного индекса, B-tree или hash.
В CREATE INDEX
используйте
USING BTREE
или USING HASH
. B-tree
быстры для запросов, которые делают сравнения через такие операторы, как
>
или BETWEEN
. Хеш-индексы быстры для запросов,
которые ищут единственные значения через =
или ограниченный
набор значений через IN
. USING BTREE
часто лучший
выбор, чем значение по умолчанию USING HASH
, см.
раздел 9.2.1.21.
Для деталей выполнения различных типов индексов в MEMORY
см.
раздел 9.3.8.
В зависимости от деталей Ваших таблиц, столбцов, индексов и условий в
Вашем WHERE
, оптимизатор MySQL имеет много методов эффективно
выполняют поиски, вовлеченные в запрос SQL. Запрос на огромной таблице может
быть выполнен, не читая все строки; соединение, вовлекающее несколько таблиц,
может быть выполнено, не сравнивая каждую комбинацию строк. Набор операций,
с помощью которых оптимизатор хочет выполнять самый эффективный запрос,
называют планом выполнения запроса, также он
известен как план EXPLAIN
.
Ваши цели состоят в том, чтобы признать аспекты плана
EXPLAIN
, которые указывают на то,
что запрос оптимизирован хорошо, и изучить синтаксис SQL и методы индексации,
чтобы улучшить план, если Вы видите некоторые неэффективные операции.
EXPLAIN
может использоваться,
чтобы получить информацию о том, как MySQL выполняет запрос:
Разрешенные объяснимые запросы для
EXPLAIN
:
SELECT
,
DELETE
,
INSERT
,
REPLACE
и
UPDATE
.
EXPLAIN
используется
с объяснимым запросом, MySQL выводит на экран информацию от оптимизатора о
плане выполнения запроса. Таким образом, MySQL объясняет, как он обработал бы
запрос, включая информацию о том, как к таблицам присоединяются и в каком
порядке. Для информации об использовании
EXPLAIN
, чтобы получить информацию
о плане выполнения, см. раздел 9.8.2
.EXPLAIN
используется с
FOR CONNECTION connection_id
вместо
объяснимого запроса, это выводит на экран план выполнения относительно
запроса в названном соединении. См.
раздел 9.8.4.FORMAT
может использоваться, чтобы выбрать выходной
формат. TRADITIONAL
представляет вывод в табличном формате. Это
значение по умолчанию, если нет опции FORMAT
. Формат
JSON
выводит на экран информацию в формате JSON. С
FORMAT = JSON
вывод включает расширенные
данные и информацию о разделеС помощью EXPLAIN
Вы можете
видеть, где Вы должны добавить индекс к таблицам так, чтобы запрос выполнился
быстрее при использовании индекса, чтобы найти строки. Вы можете также
использовать EXPLAIN
, чтобы
проверить, присоединяется ли оптимизатор к таблицам в оптимальном порядке.
Чтобы дать подсказку оптимизатор, чтобы использовать порядок соединения,
соответствующий порядку, в котором таблицы называют в
SELECT
, начните запрос с
SELECT STRAIGHT_JOIN
вместо
SELECT
, см.
раздел 14.2.9. Однако,
STRAIGHT_JOIN
может не дать использовать индексы, потому что это
отключает преобразования полусоединения. См.
раздел 9.2.1.18.1.
Оптимизатор может иногда предоставлять информацию, дополнительную к
EXPLAIN
.
Однако, формат трассировки оптимизатора и контент подвержены изменениям между
версиями. Для деталей см.
MySQL Internals: Tracing the Optimizer.
Если у Вас есть проблема с индексом, выполните
ANALYZE TABLE
, чтобы
обновить табличную статистику, такую как количество элементов ключей, которые
могут затронуть выбор, который делает оптимизатор. См.
раздел 14.7.2.1.
EXPLAIN
может также использоваться, чтобы получить информацию о столбцах в таблице.
EXPLAIN
синоним
with tbl_name
DESCRIBE
и
tbl_name
SHOW COLUMNS FROM
. См. разделы
information, see 14.8.1 и
14.7.5.5.tbl_name
EXPLAIN
предоставляет информацию о плане выполнения относительно
SELECT
.
EXPLAIN
возвращает строку информации для каждой таблицы, используемой в
SELECT
.
Это перечисляет таблицы в выводе в порядке, в котором
MySQL считал бы их, обрабатывая запрос. MySQL решает все соединения,
используя метод соединения вложенной петли. Это означает, что MySQL читает
строку из первой таблицы, а затем находит соответствующую строку во второй
таблице, третьей и так далее. Когда все таблицы обработаны, MySQL выводит
выбранные столбцы и отступления через табличный список, пока таблица не
найдена, для которой более соответствуют строки. Следующая строка считана из
этой таблицы, и процесс продолжается со следующей таблицей.
Нельзя использовать устаревшие параметры EXTENDED
и
PARTITIONS
вместе в том же самом
EXPLAIN
. Кроме того, ни одно из
этих ключевых слов не может использоваться вместе с опцией
FORMAT
. FORMAT=JSON
предписывает
EXPLAIN
вывести на экран расширенную информацию и данные о
разделении автоматически, использование FORMAT=TRADITIONAL
не
влияет на вывод EXPLAIN
.
Столбцы вывода
EXPLAIN
.
EXPLAIN
.EXPLAIN
.EXPLAIN
.Этот раздел описывает выходные столбцы, произведенные
EXPLAIN
.
Более поздние разделы обеспечивают дополнительную информацию о столбцах
type
и
Extra
.
Каждая выходная строка EXPLAIN
предоставляет информацию об одной таблице. Каждая строка содержит значения,
полученные в итоге в
таблице 9.1, и описанные более подробно после таблицы. Имена столбцов
показывают в первом столбце таблицы, второй столбец обеспечивает
эквивалентное имя свойства, показанное в выводе, когда
используется FORMAT=JSON
.
Таблица 9.1. Столбцы вывода EXPLAIN
Столбец | Имя JSON | Значение |
---|---|---|
id
| select_id |
Идентификатор SELECT |
select_type | Нет | Тип SELECT |
table
| table_name | Таблица для выходной строки |
partitions | partitions | Соответствующее разделение |
type
| access_type | Тип соединения |
possible_keys | possible_keys | Возможные индексы, чтобы выбрать |
key
| key | Индекс, который фактически выбран |
key_len
| key_length | Длина выбранного ключа |
ref
| ref | Столбцы по сравнению с индексом |
rows
| rows | Оценка строк, которые будут исследованы |
filtered
| filtered | Процент строк, которые фильтруются по табличному условию |
Extra
| Нет | Дополнительная информация |
Свойства JSON, которые являются NULL
,
не выведены на экран в JSON-формате вывода EXPLAIN
.
Идентификатор SELECT
.
Это последовательное число
SELECT
в запросе.
Значение может быть NULL
, если строка обращается к результату
союза других строк. В этом случае столбец table
показывает
значение как <union
, чтобы указать, что строка обращается к союзу строк с
M
,N
>id
M
и N
.
select_type
(Имя JSON: нет)
Тип SELECT
,
который может быть любым из показанных в следующей таблице.
JSON-отформатированный вывод EXPLAIN
показывает тип
SELECT
как свойство query_block
, если это не
SIMPLE
или PRIMARY
. Имена JSON (где применимы)
также показываются в таблице.
select_type |
Имя JSON | Смысл |
---|---|---|
SIMPLE | Нет | Простой SELECT (не использует
UNION или подзапросы). |
PRIMARY | Нет | Наиболее удаленный SELECT .
|
UNION |
Нет | Второй или более дальний
SELECT в UNION . |
DEPENDENT UNION |
dependent (true ) |
Второй или более дальний SELECT в
UNION , зависит от внешнего запроса.
|
UNION RESULT |
union_result | Результат
UNION . |
SUBQUERY | Нет | Первый SELECT в подзапросе.
|
DEPENDENT SUBQUERY |
dependent (true ) |
Первый SELECT в подзапросе,
зависит от внешнего запроса. |
DERIVED | Нет |
Полученная таблица SELECT
(подзапрос в FROM ). |
MATERIALIZED |
materialized_from_subquery | Осуществленный подзапрос. |
UNCACHEABLE SUBQUERY |
cacheable (false ) | Подзапрос, для которого результат не может кэшироваться и должен быть переоценен для каждой строки внешнего запроса. |
UNCACHEABLE UNION |
cacheable (false ) |
Второй или более дальний select в UNION
, который принадлежит некэшируемому подзапросу (см.
UNCACHEABLE SUBQUERY ). |
DEPENDENT
как правило показывает использование
коррелированого подзапроса. См.
раздел 14.2.10.7.
DEPENDENT SUBQUERY
отличается от
UNCACHEABLE SUBQUERY
. Для DEPENDENT SUBQUERY
подзапрос переоценен только однажды для каждого набора различных значений
переменных от его внешнего контекста. Для UNCACHEABLE SUBQUERY
подзапрос переоценен для каждой строки внешнего контекста.
Кэшируемость подзапросов отличается от кэширования результатов запроса в кэше запроса (которое описано в разделе 9.10.3.1). Кэширование подзапроса происходит во время выполнения запроса, тогда как кэш запроса используется, чтобы сохранить результаты только после того, как выполнение запроса заканчивается.
Когда Вы определяете FORMAT=JSON
,
у вывода нет никакого единственного свойства, непосредственно эквивалентного
select_type
, свойство query_block
соответствует данному SELECT
. Свойства, эквивалентные
большинству типов подзапросов SELECT
, доступны (например,
materialized_from_subquery
для MATERIALIZED
). Нет
никаких эквивалентов JSON для SIMPLE
или PRIMARY
.
select_type
для не-SELECT
выводят на экран тип запроса для затронутых таблиц. Например,
select_type
это DELETE
для запроса
DELETE
.
table
(Имя JSON:
table_name
)
Название таблицы, к которой обращается строка вывода. Это может также быть одним из следующих значений:
<union
: Строка обращается к союзу строк с
M
,N
>
id
M
и N
.
<derivedN
>
:
Строка обращается к полученному табличному результату для строки с
id
N
. Полученная таблица может
закончиться, например, от подзапроса в FROM
.<subqueryN
>
:
Строка обращается к результату осуществленного подзапроса для строки с
id
N
. См.
раздел 9.2.1.18.2.
partitions
(Имя JSON:
partitions
)
Раздел, от которого записи были бы соответствующими запросу.
Этот столбец выведен на экран только, если использован параметр
PARTITIONS
. NULL
для неразделенных таблиц. См.
раздел 20.3.5.
type
(Имя JSON:
access_type
)
Тип соединения. Для описаний различных типов см.
Типы соединения EXPLAIN
.
possible_keys
(Имя JSON: possible_keys
)
Столбец possible_keys
указывает, который индекс MySQL
может использоваться, чтобы находить строки в этой таблице. Отметьте, что
этот столбец полностью независим от порядка таблиц, как выведено на экран в
выводе EXPLAIN
.
Это означает, что некоторые из ключей possible_keys
не могут быть применимы практически с произведенным табличным порядком.
Если этот столбец NULL
(или неопределенный в JSON),
там нет релевантных индексов. В этом случае Вы можете быть в состоянии
улучшить исполнение своего запроса, исследуя WHERE
, чтобы
проверить, обращается ли это к некоторому столбцу или столбцам, которые были
бы подходящими для того, чтобы индексировать. Если так, создайте
соответствующий индекс и проверьте запрос с
EXPLAIN
, см.
раздел 14.1.7.
Чтобы видеть все индексы таблицы, используйте
SHOW INDEX FROM
.tbl_name
key
(Имя JSON: key
)
Столбец key
указывает ключ (индекс), который
MySQL фактически решил использовать. Если MySQL решает использовать один из
possible_keys
, чтобы искать строки, индекс перечислен
как значение ключа.
Возможно, что key
назовет индекс, который не присутствует в
possible_keys
. Это может произойти, если ни один из
possible_keys
не подходящий для того, чтобы искать строки, но
все столбцы, выбранные запросом, являются столбцами некоторого другого
индекса. Таким образом, названный индекс покрывает выбранные столбцы,
так что, хотя это не используется, чтобы определить, какие строки получить,
индексный просмотр более эффективен, чем просмотр строк данных.
Для InnoDB
вторичный индекс мог бы
покрыть выбранные столбцы, даже если запрос также выбирает первичный ключ,
потому что InnoDB
хранит значение первичного ключа в каждом
вторичном индексе. Если key
= NULL
, MySQL
не имеет индекса для того, чтобы выполнить запрос более эффективно.
Чтобы вынудить MySQL использовать или проигнорировать индекс
перечисленный в possible_keys
, укажите в запросе FORCE
INDEX
, USE INDEX
или IGNORE INDEX
, см.
раздел 9.9.4.
Для MyISAM
ANALYZE
TABLE
помогает оптимизатору выбрать лучший индекс. Для
MyISAM
myisamchk
--analyze делает то же самое. См. разделы
14.7.2.1 и
8.6.
key_len
(Имя JSON: key_length
)
key_len
указывает на длину ключа, который MySQL решил
использовать. Значение key_len
позволяет Вам определить, сколько
частей ключа MySQL фактически использует. Если key
= NULL
, len_len
тоже NULL
.
Из-за ключевого формата хранения, длина ключа
больше для столбца, который может быть NULL
,
чем для NOT NULL
.
ref
(Имя JSON: ref
)
ref
показывает, которые столбцы или константы сравниваются с
индексом, названным в key
, чтобы выбрать строки из таблицы.
Если значение равно func
, используемое значение является
результатом некоторой функции. Чтобы видеть, которая это функция, надо
использовать EXPLAIN EXTENDED
вместе с SHOW WARNINGS
. Функция могла бы фактически быть оператором, таким
как арифметический оператор.
rows
(Имя JSON: rows
)
rows
указывает на число строк, которые MySQL должен
исследовать, чтобы выполнить запрос.
Для InnoDB
это число только
оценка и, возможно, всегда не точно.
filtered
(Имя JSON: filtered
)
filtered
указывает на предполагаемый процент строк таблицы,
которые будут фильтроваться по табличному условию. Таким образом,
rows
показывает предполагаемое число исследованных строк, а
rows
* filtered
/100
показывает
число строк, к которым присоединятся с предыдущими таблицами.
Extra
(Имя JSON: нет)
Этот столбец содержит дополнительную информацию о том, как MySQL решает
запрос. Для описания различных значений см.
EXPLAIN
Extra Information.
Нет никакого единственного свойства JSON, соответствующего
Extra
, однако, значения, которые могут произойти в этом столбце,
выставлены как свойства JSON или как текст свойства message
.
type
EXPLAIN
описывает, как присоединяются к таблицам. В формате JSON
они найдены как значения свойства access_type
. Следующий список
описывает типы соединения, упорядоченные от лучшего типа до худшего:
У таблицы есть только одна строка (= системная таблица). Это особый случай
типа соединения const
.
const
У таблицы есть самое большее одна строка соответствия, которая считана
в начале запроса. Поскольку есть только одна строка, значения столбца в этой
строке могут быть расценены как константы остальной частью оптимизатора.
Таблицы const
очень быстры, потому что они читаются только один раз.
const
используется, когда Вы сравниваете все части индекса PRIMARY KEY
или UNIQUE
с постоянными величинами. В следующих запросах
tbl_name
может использоваться в качестве таблицы
const
:
SELECT * FROMtbl_name
WHEREprimary_key
=1; SELECT * FROMtbl_name
WHEREprimary_key_part1
=1 ANDprimary_key_part2
=2;
eq_ref
Одна строка считана из этой таблицы для каждой комбинации строк от
предыдущих таблиц. Кроме типов
system
и
const
это самый лучший тип соединения. Это используется, когда все части
индексирования используются соединением, и индексирование является индексом
PRIMARY KEY
или UNIQUE NOT NULL
.
eq_ref
может использоваться для индексированных столбцов, которые сравнены,
используя оператор =
. Сравнительное значение может быть
константой или выражением, которое использует столбцы от таблиц, которые
считаны перед этой таблицей. В следующих примерах MySQL может использовать
соединение eq_ref
для обработки ref_table
:
SELECT * FROMref_table
,other_table
WHEREref_table
.key_column
=other_table
.column
; SELECT * FROMref_table
,other_table
WHEREref_table
.key_column_part1
=other_table
.column
ANDref_table
.key_column_part2
=1;
ref
Все строки с соответствием индексу значения
считаны из этой таблицы для каждой комбинации строк от предыдущих таблиц.
ref
используется, если соединение использует только префикс
ключа или если ключ не индекс PRIMARY KEY
или
UNIQUE
(другими словами, если соединение не может выбрать
единственную строку, основанную на значении ключа). Если ключу, который
используется, соответствует только несколько строк, это
хороший тип соединения.
ref
может использоваться для индексированных столбцов, которые сравнены,
используя =
или <=>
.
В следующих примерах MySQL может использовать соединение
ref
для обработки
ref_table
:
SELECT * FROMref_table
WHEREkey_column
=expr
; SELECT * FROMref_table
,other_table
WHEREref_table
.key_column
=other_table
.column
; SELECT * FROMref_table
,other_table
WHEREref_table
.key_column_part1
=other_table
.column
ANDref_table
.key_column_part2
=1;
fulltext
Соединение выполнено, используя индекс FULLTEXT
.
ref_or_null
Этот тип соединения похож на
ref
,
но с дополнением, что MySQL делает дополнительный поиск строк, которые
содержат NULL
. Эта оптимизация типа соединения используется чаще
всего в решении подзапросов. В следующих примерах MySQL может использовать
ref_or_null
для работы с ref_table
:
SELECT * FROMref_table
WHEREkey_column
=expr
ORkey_column
IS NULL;
См. раздел 9.2.1.8.
index_merge
Этот тип соединения указывает, что оптимизация Index Merge
используется. В этом случае key
в выходной строке содержит
список используемых индексов, в key_len
содержит список самых длинных ключевых частей для используемого
индексирования. Для получения дополнительной информации см.
раздел 9.2.1.4.
unique_subquery
Этот тип заменяет
eq_ref
для некоторых подзапросов
for some IN
следующей формы:
value
IN (SELECTprimary_key
FROMsingle_table
WHEREsome_expr
)
unique_subquery
только индексная функция поиска, которая заменяет подзапрос
полностью для лучшей эффективности.
index_subquery
Этот тип соединения подобен
unique_subquery
. Заменяет подзапросы IN
, но это работает на групповом
индексе в подзапросах следующей формы:
value
IN (SELECTkey_column
FROMsingle_table
WHEREsome_expr
)
range
Только строки, которые находятся в данном диапазоне, получены,
используя индекс, чтобы выбрать строки. key
в выходной строке указывает, которые индексы используются.
key_len
содержит самую длинную ключевую часть, которая
использовалась. ref
= NULL
для этого типа.
range
может использоваться, когда ключевой столбец сравнивается с постоянной с
использованием любого из операторов
=
,
<>
,
>
,
>=
,
<
,
<=
,
IS NULL
,
<=>
,
BETWEEN
или
IN()
:
SELECT * FROMtbl_name
WHEREkey_column
= 10; SELECT * FROMtbl_name
WHEREkey_column
BETWEEN 10 and 20; SELECT * FROMtbl_name
WHEREkey_column
IN (10,20,30); SELECT * FROMtbl_name
WHEREkey_part1
= 10 ANDkey_part2
IN (10,20,30);
index
Тип соединения index
то же самое, как
ALL
за исключением того, что индексное дерево просмотрено.
Это происходит двумя путями:
Если индексирование является покрытием для запросов и
может использоваться, чтобы удовлетворить все данные, требуемые от таблицы,
только индексное дерево просмотрено. В этом случае столбец
Extra
сообщает Using index
.
Только индексированный просмотр обычно быстрее, чем
ALL
, поскольку
размер индексирования обычно меньше, чем табличные данные.
Uses index
не появляется в столбце Extra
.MySQL может использовать этот тип соединения, когда запрос использует только столбцы, которые являются частью одного индекса.
ALL
Полное сканирование таблицы сделано для каждой комбинации строк от
предыдущих таблиц. Это обычно не хорошо, если таблица первая таблица,
не отмеченная как const
и обычно очень плохо во всех других случаях.
Обычно Вы можете избежать ALL
добавлением индексов, которые включают извлечение строки от таблицы,
основанной на постоянных величинах или значениях столбцов от
более ранних таблиц.
Столбец Extra
в выводе
EXPLAIN
содержит дополнительную информацию о том, как MySQL решает запрос. Следующий
список объясняет значения, которые могут появиться в этом столбце. Каждый
элемент также указывает для формата JSON, который выводит на экран свойство
Extra
. Для некоторых из них есть определенное свойство. Другие
выводят на экран как текст свойства message
.
Если Вы хотите сделать свои запросы с такой скоростью, как возможныо,
см. в Extra
значения Using
filesort
и Using temporary
или для формата
JSON свойства using_filesort
и
using_temporary_table
, равные true
.
const row not found
(Свойство JSON:
const_row_not_found
)
Для такого запроса, как SELECT ... FROM
, таблица была пуста.tbl_name
Deleting all rows
(Свойство JSON: message
)
Для DELETE
некоторые механизмы
хранения, например, MyISAM
поддерживают метод обработчика, который удаляет все строки
таблицы простым и быстрым способом. Это значение Extra
выведено на экран, если механизм использует эту оптимизацию.
Distinct
(Свойство JSON: distinct
)
MySQL ищет отличные значения, таким образом, он прекращает искать больше строк для текущей комбинации строки после того, как только он нашел первую строку соответствия.
FirstMatch(tbl_name
)
(Свойство JSON: first_match
)
Полусоединение FirstMatch к сокращенной стратегии, используется для
tbl_name
.
Full scan on NULL key
(Свойство JSON: message
)
Это происходит для оптимизации подзапроса как стратегия отступления, когда оптимизатор не может использовать метод доступа поиска по индексу.
Impossible HAVING
(Свойство JSON: message
)
HAVING
всегда false и не может выбрать строки.
Impossible WHERE
(Свойство JSON: message
)
WHERE
всегда false и не может выбрать строки.
Impossible WHERE noticed after reading const tables
(Свойство JSON: message
)
MySQL считал все таблицы
const
(и system
)
и уведомляет, что WHERE
всегда ложен.
LooseScan(m
..n
)
(Свойство JSON: message
)
Стратегия LooseScan используется. m
и
n
номера частей ключа.
No matching min/max row
(Свойство JSON: message
)
Никакая строка не удовлетворяет условие для такого запроса, как
SELECT MIN(...) FROM ... WHERE
.
condition
no matching row in const table
(Свойство JSON:
message
)
Для запроса с соединением была пустая таблица, или таблица без строк, удовлетворяющих уникальное индексное условие.
No matching rows after partition pruning
(Свойство JSON: message
)
Для DELETE
или
UPDATE
оптимизатор не нашел ничего для удаления или обновления после сокращения
разделения. Это подобно значению Impossible WHERE
для SELECT
.
No tables used
(Свойство JSON: message
)
Запрос не имеет FROM
или имеет FROM DUAL
.
Для INSERT
или
REPLACE
EXPLAIN
покажет это значение,
когда нет части SELECT
.
Например, это появляется для EXPLAIN INSERT INTO t VALUES(10)
,
потому что это эквивалентно EXPLAIN INSERT INTO t SELECT 10 FROM DUAL
.
Not exists
(Свойство JSON: message
)
MySQL смог сделать оптимизацию LEFT JOIN
на запросе и не исследует больше строк в этой таблице для предыдущей
комбинации строки после того, как это находит одну строку, которая
соответствует LEFT JOIN
. Вот пример типа запроса, который может
быть оптимизирован так:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;
Предположим, t2.id
определен как NOT NULL
.
В этом случае MySQL просмотрит t1
и ищет строки в
t2
, используя t1.id
.
Если MySQL находит соответствующую строку в t2
,
это знает, что t2.id
никогда не может быть NULL
и не просматривает через остальную часть строк в t2
,
у которых есть тот же самый id
. Другими словами, для каждой
строки в t1
MySQL MySQL должен сделать только единственный
поиск в t2
независимо от того, сколько строк фактически
соответствуют в t2
.
Plan isn't ready yet
(Свойство JSON: нет)
Это значение происходит с
EXPLAIN FOR CONNECTION
, когда оптимизатор не закончил
создавать план выполнения относительно запроса в названном соединении.
Если вывод плана выполнения включает многократные строки, у любых из них
могло бы быть это значение Extra
, в зависимости от продвижения
оптимизатора в определении полного плана выполнения.
Range checked for each record (index map:
N
)
(Свойство JSON: message
)
MySQL не нашел хороший индекс, но нашел, что часть из индексов могла бы
использоваться после того, как значения столбцов от предыдущих таблиц будут
известны. Для каждой комбинации строк в предыдущих таблицах MySQL проверяет,
возможно ли использовать метод доступа
range
или
index_merge
,
чтобы получить строки. Это не очень быстро, но быстрее, чем выполнение
соединения без индексов вообще. Критерии применимости описаны в разделах
9.2.1.3 и
9.2.1.4
за исключением того, что все значения столбцов для предыдущей таблицы
известны и, как полагают, константы.
Индексы пронумерованы, начиная с 1, в том же самом порядке, как показано
SHOW INDEX
для таблицы.
Значение индексной карты N
это значение битовой маски,
которое указывает, который индекс кандидат. Например, значение
0x19
(двоичное 11001) означает, что рассмотрят индексы 1, 4 и 5.
Scanned N
databases
(Свойство JSON: message
)
Это указывает, сколько просмотров каталога сервер выполняет, обрабатывая
запрос для таблиц INFORMATION_SCHEMA
, см.
раздел 9.2.4.
Значение N
может быть 0, 1 или all
.
Select tables optimized away
(Свойство JSON: message
)
Оптимизатор решил, что: 1) самое большее одна строка должна быть возвращена, и 2) чтобы произвести эту строку, детерминированный набор строк должен быть считан. Когда строки, которые будут считаны, могут быть считаны во время фазы оптимизации (например, чтением индекса строк), нет никакой потребности читать любые таблицы во время выполнения запроса.
Первое условие выполнено, когда запрос неявно сгруппирован
(содержит совокупную функцию, но нет GROUP BY
).
Второе условие выполнено, когда один поиск строки выполнен на применение
индекса. Число прочитанных индексов определяет число строк, чтобы читать.
Рассмотрите следующий неявно сгруппированный запрос:
SELECT MIN(c1), MIN(c2) FROM t1;
Предположите, что MIN(c1)
может быть получен, читая одну
строку индекса и MIN(c2)
может быть получен, читая одну строку
из другого индекса. Таким образом, для каждого столбца c1
и
c2
там существует индекс, где столбец это первый столбец
индекса. В этом случае одна строка возвращена (и произведена), читая
две детерминированных строки.
Это значение Extra
не происходит, если строки для чтения не
детерминированы. Рассмотрите этот запрос:
SELECT MIN(c2) FROM t1 WHERE c1 <= 10;
Допустим, (c1, c2)
покрывающий индекс. Используя этот индекс,
все строки с c1 <= 10
должны быть просмотрены, чтобы найти
минимум c2
. В отличие от этого, рассмотрите этот запрос:
SELECT MIN(c2) FROM t1 WHERE c1 = 10;
В этом случае первая индексная строка с c1 = 10
содержит минимум c2
. Только одна строка должна быть считана,
чтобы произвести возвращенную строку.
Для механизмов хранения, которые поддерживают точное количество строк на
таблицу (такой как MyISAM
, но не InnoDB
), это
значение Extra
может произойти для запросов
COUNT(*)
, для которых WHERE
отсутствует или всегда истина и нет GROUP BY
.
Это случай неявно сгруппированного запроса, где механизм хранения влияет на
то, может ли быть считано детерминированное число строк.
Skip_open_table
, Open_frm_only
,
Open_full_table
(Свойство JSON: message
)
Эти значения указывают на открывающую файл оптимизацию, которая относится
к запросам к таблицам INFORMATION_SCHEMA
.
Skip_open_table
: Табличные файлы не должны быть
открыты. Информация уже доступна из словаря данных.
Open_frm_only
: Только словарь данных должен быть считан
для информации о таблице.Open_full_table
: Неоптимизированный информационный поиск.
Информация о таблице должна быть считана из словаря данных и
читая табличные файлы.Start temporary
, End temporary
(Свойство JSON:
message
)
Это указывает на временное табличное использование для стратегии Duplicate Weedout.
unique row not found
(Свойство JSON: message
)
Для такого запроса, как SELECT ... FROM
, никакие строки не удовлетворяют
условию для индекса tbl_name
UNIQUE
или PRIMARY
KEY
на таблице.
Using filesort
(Свойство JSON: using_filesort
)
MySQL должен сделать дополнительный проход, чтобы узнать, как получить
строки в сортированном порядке. Сортировка сделана, проходя все строки,
согласно типу соединения и храня ключ сортировки
и указатель на строку для всех строк, которые соответствуют
WHERE
. Ключи тогда отсортированы, и строки получены в
сортированном порядке. См. раздел
9.2.1.15.
Using index
(Свойство JSON: using_index
)
Информация о столбце получена от таблицы, используя только информацию в индексном дереве, не имея необходимости делать дополнительный поиск, чтобы читать фактическую строку. Эта стратегия может использоваться, когда запрос использует только столбцы, которые являются частью одного индекса.
Для InnoDB
с определяемым пользователем кластеризуемым
индексом могут использоваться, даже когда Using index
отсутствует в столбце Extra
. Дело обстоит так, если
type
= index
, а key
= PRIMARY
.
Using index condition
(Свойство JSON:
using_index_condition
)
Таблицы считаны доступом к индексным кортежам и проводится тестирование их сначала, чтобы определить, читать ли полные строки таблицы. Таким образом, индексная информация используется, чтобы задержать (push down) чтение полных строк таблицы, если это не необходимо. См. раздел 9.2.1.6 .
Using index for group-by
(Свойство JSON:
using_index_for_group_by
)
Подобно методу доступа Using index
,
Using index for group-by
указывает, что MySQL нашел индекс,
который может использоваться, чтобы получить все столбцы запроса
GROUP BY
или DISTINCT
без любого дополнительного дискового доступа к фактической таблице.
Дополнительно, индексирование используется самым эффективным способом так,
чтобы для каждой группы были считаны только некоторые индексные записи.
Для деталей см. раздел 9.2.1.16
.
Using join buffer (Block Nested Loop)
,
Using join buffer (Batched Key Access)
(Свойство JSON: using_join_buffer
)
Таблицы от более ранних соединений считаны
в буфер соединения, а затем их строки используются, чтобы выполнить
соединение с текущей таблицей. (Block Nested Loop)
указывает на использование алгоритма Block Nested-Loop и (Batched Key
Access)
указывает на использование алгоритма Batched Key Access.
Таким образом, ключи от таблицы на предыдущей строке вывода
EXPLAIN
будут буферизованы, и соответствующие строки будут принесены в пакетах от
таблицы, представленной строкой, в которой появляется
Using join buffer
.
В JSON-отформатированном выводе значение
using_join_buffer
всегда
Block Nested Loop
или Batched Key Access
.
Using MRR
(Свойство JSON: message
)
Таблицы считаны, используя стратегию оптимизации Multi-Range Read, см. раздел 9.2.1.13.
Using sort_union(...)
, Using union(...)
,
Using intersect(...)
(Свойство JSON: message
)
Они указывают, как индексные просмотры слиты для типа соединения
index_merge
.
См. раздел 9.2.1.4.
Using temporary
(Свойство JSON:
using_temporary_table
)
Чтобы решить запрос, MySQL должен составить временную таблицу, чтобы
держать результат. Это как правило происходит, если запрос содержит
GROUP BY
и ORDER BY
.
Using where
(Свойство JSON:
attached_condition
)
WHERE
используется, чтобы ограничить, которые строки
соответствуют следующей таблице или посланы клиенту. Если Вы определенно не
намереваетесь принести или исследовать все строки от таблицы, у Вас может
быть что-то не так в Вашем запросе, если Extra
не Using
where
и табличный тип соединения
ALL
или
index
.
Using where
не имеет никакой прямой копии в
JSON-отформатированном выводе, свойство attached_condition
содержит любые условия WHERE
.
Using where with pushed condition
(JSON
property: message
)
Этот элемент относится только к
NDB
. Это означает, что MySQL Cluster
использует оптимизацию Condition Pushdown, чтобы улучшить эффективность
прямого сравнения между неиндексированным столбцом и константой. В таких
случаях условие продвинуто вниз узлам данных
кластера и оценено на всех узлах данных одновременно. Это избавляет от
необходимости посылать несоответствие строк по сети, и может ускорить такие
запросы в 5-10 раз. Для получения дополнительной информации см.
раздел 9.2.1.5.
Zero limit
(Свойство JSON: message
)
У запроса был LIMIT 0
, невозможно выбрать строки.
Вы можете получить хороший признак того, насколько хорошо соединение,
принимая произведение значений в столбце rows
вывода
EXPLAIN
. Это должно сказать Вам
примерно, сколько строк MySQL должен исследовать, чтобы выполнить запрос.
Если Вы ограничиваете запросы через
max_join_size
,
это также используется, чтобы определить, который мультитабличный
SELECT
выполнять. См.
раздел 6.1.1.
Следующий пример показывает, как многотабличное соединение может быть
оптимизировано прогрессивно, основываясь на информации, предоставленной
EXPLAIN
.
Предположите, что Вы имеете
SELECT
, показанный здесь, и что Вы
планируете исследовать это с использованием
EXPLAIN
:
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, tt.ProjectReference, tt.EstimatedShipDate, tt.ActualShipDate, tt.ClientID, tt.ServiceCodes, tt.RepetitiveID, tt.CurrentProcess, tt.CurrentDPPerson, tt.RecordVolume, tt.DPPrinted, et.COUNTRY, et_1.COUNTRY, do.CUSTNAME FROM tt, et, et AS et_1, do WHERE tt.SubmitTime IS NULL AND tt.ActualPC = et.EMPLOYID AND tt.AssignedPC = et_1.EMPLOYID AND tt.ClientID = do.CUSTNMBR;
Для этого примера, сделайте следующие предположения:
Сравниваемые столбцы были объявлены следующим образом.
Таблица | Столбец | Тип данных |
---|---|---|
tt | ActualPC |
CHAR(10) |
tt | AssignedPC |
CHAR(10) |
tt | ClientID |
CHAR(10) |
et | EMPLOYID |
CHAR(15) |
do | CUSTNMBR |
CHAR(15) |
Таблица | Индекс |
---|---|
tt | ActualPC
|
tt | AssignedPC |
tt | ClientID |
et | EMPLOYID
(primary key) |
do | CUSTNMBR
(primary key) |
tt.ActualPC
равномерно не распределены.
Первоначально, прежде, чем любая оптимизация была выполнена,
EXPLAIN
производит следующую информацию:
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 do ALL PRIMARY NULL NULL NULL 2135 et_1 ALL PRIMARY NULL NULL NULL 74 tt ALL AssignedPC, NULL NULL NULL 3872 ClientID, ActualPC Range checked for each record (index map: 0x23)
Так как type
= ALL
для каждой таблицы этот вывод указывает, что MySQL производит
декартово произведение всех таблиц, то есть, каждой комбинация строк. Это
занимает длительное время, потому что произведение числа строк в каждой
таблице должно быть исследовано. Для данного случая это 74*2135*74*3872=
45268558720 строк. Если бы таблицы были больше, Вы можете только вообразить,
сколько времени это заняло бы...
Одна проблема здесь состоит в том, что MySQL может использовать индекс
на столбцах более эффективно, если они объявлены как тот же самый тип и
размер. В этом контексте VARCHAR
и
CHAR
считаются тем же самым, если они объявлены как тот же самый размер.
tt.ActualPC
объявлен как
CHAR(10)
и et.EMPLOYID
как CHAR(15)
,
таким образом, есть несоответствие длины.
Чтобы устранить это неравенство между длинами столбца, надо использовать
ALTER TABLE
и удлинить
ActualPC
с 10 символов до 15:
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
Теперь tt.ActualPC
и et.EMPLOYID
оба
VARCHAR(15)
. Выполнение
EXPLAIN
приводит к этому результату:
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC, NULL NULL NULL 3872 Using ClientID, where ActualPC do ALL PRIMARY NULL NULL NULL 2135 Range checked for each record (index map: 0x1) et_1 ALL PRIMARY NULL NULL NULL 74 Range checked for each record (index map: 0x1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
Это не прекрасно, но намного лучше: произведение значений
rows
меньше в 74 раза. Эта версия выполняется за пару секунд.
Второе изменение может быть сделано, чтобы устранить несоответствия длины
столбца для tt.AssignedPC = et_1.EMPLOYID
и
tt.ClientID = do.CUSTNMBR
:
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), -> MODIFY ClientID VARCHAR(15);
После этой модификации EXPLAIN
производит вывод, показанный здесь:
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using ClientID, where ActualPC et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
В этом пункте запрос оптимизирован почти так, как возможно. Остающаяся
проблема состоит в том, что по умолчанию MySQL предполагает, что значения в
tt.ActualPC
равномерно распределены, но дело обстоит не так для
tt
. К счастью, легко сказать MySQL
анализировать ключевое распределение:
mysql> ANALYZE TABLE tt;
С дополнительной индексной информацией соединение прекрасно и
EXPLAIN
приводит к этому результату:
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 Using where ClientID, ActualPC et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1 et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
Столбец rows
в выводе EXPLAIN
образован предположением от оптимизатора соединения MySQL.
Проверьте, являются ли числа близко к правде, сравнивая произведение
rows
с фактическим числом строк, которые возвращает запрос. Если
числа очень отличаются, Вы могли бы получить лучшую работу при использовании
STRAIGHT_JOIN
в SELECT
и пытаясь перечислить таблицы в различном порядке в
FROM
. Но STRAIGHT_JOIN
может блокировать
использование индексов, потому что это отключает преобразования
полусоединения. См. раздел 9.2.1.18.1.
Возможно в некоторых случаях выполнить запросы, которые изменяют данные,
когда EXPLAIN SELECT
используется с подзапросом, для получения дополнительной информации см.
раздел 14.2.10.8.
Когда EXPLAIN
используется с
EXTENDED
, вывод включает столбец filtered
.
Этот столбец указывает на предполагаемый процент строк таблицы, которые будут
фильтроваться по табличному условию. Кроме того, запрос производит
дополнительную информацию, которая может быть просмотрена через
SHOW WARNINGS
после
EXPLAIN
. Значение
Message
в выводе SHOW
WARNINGS
выводит на экран, как оптимизатор квалифицирует имена
таблиц и имена столбцов в SELECT
,
на что SELECT
похож после применения правил перезаписи и оптимизации, и возможно другие
примечания о процессе оптимизации.
С MySQL 5.7.3 EXPLAIN
изменено так, что эффект EXTENDED
всегда включается.
Параметр EXTENDED
все еще признан, но лишний и устарел.
Это будет удалено из EXPLAIN
в будущем выпуске MySQL.
Вот пример расширенного вывода:
mysql> EXPLAIN EXTENDED SELECT t1.a, t1.a IN -> (SELECT t2.a FROM t2) FROM t1\G *************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 type: index possible_keys: NULL key: PRIMARY key_len: 4 ref: NULL rows: 4 filtered: 100.00 Extra: Using index *************************** 2. row *************************** id: 2 select_type: SUBQUERY table: t2 type: index possible_keys: a key: a key_len: 5 ref: NULL rows: 3 filtered: 100.00 Extra: Using index 2 rows in set, 1 warning (0.00 sec) mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Note Code: 1003 Message: /* select#1 */ select `test`.`t1`.`a` AS `a`, <in_optimizer>(`test`.`t1`.`a`,`test`.`t1`.`a` in ( <materialize> (/* select#2 */ select `test`.`t2`.`a` from `test`.`t2` where 1 having 1 ), <primary_index_lookup>(`test`.`t1`.`a` in <temporary table> on <auto_key> where ((`test`.`t1`.`a` = `materialized-subquery`.`a`))))) AS `t1.a IN (SELECT t2.a FROM t2)` from `test`.`t1` 1 row in set (0.00 sec)
EXPLAIN EXTENDED
может использоваться с SELECT
,
DELETE
,
INSERT
,
REPLACE
и
UPDATE
. Однако, следующий
SHOW WARNINGS
выводит на экран непустой результат только для
SELECT
.
Поскольку запрос, выведенный на экран
SHOW WARNINGS
, может содержать специальные маркеры, чтобы
предоставить информацию о перезаписи запроса или действиях оптимизатора,
запрос не обязательно допустимый SQL и не предназначен, чтобы быть
выполненным. Вывод может также включать строки с Message
,
который обеспечивает дополнительные не-SQL примечания о
мерах, предпринятых оптимизатором.
Следующий список описывает специальные маркеры, которые могут появиться в
выводе EXTENDED
и SHOW
WARNINGS
:
<auto_key>
Автоматически произведенный ключ для временной таблицы.
<cache>(expr
)
Выражение (такое как скалярный подзапрос) выполнено однажды, и
получающееся значение сохранено в памяти для более позднего использования.
Для результатов, состоящих из многократных значений, может быть составлена
временная таблица, и Вы будете видеть <temporary table>
.
<exists>(query fragment
)
Предикат подзапроса преобразован в предикат EXISTS
и подзапрос преобразован так, чтобы это могло использоваться
вместе с предикатом EXISTS
.
<in_optimizer>(query fragment
)
Это внутренний объект оптимизатора без пользовательского значения.
<index_lookup>(query fragment
)
Фрагмент запроса обработан, используя индексный поиск, чтобы найти готовящиеся строки.
<if>(condition
,
expr1
, expr2
)
Если условие истина, оценить к expr1
, иначе к
expr2
.
<is_not_null_test>(expr
)
Тест, чтобы проверить, что выражение не оценивается к NULL
.
<materialize>(query fragment
)
Материализация подзапроса используется.
`materialized-subquery`.col_name
Ссылка на столбец col_name
во внутренней временной
таблице, осуществленной, чтобы держать следствие оценки подзапроса.
<primary_index_lookup>(query fragment
)
Фрагмент запроса обработан, используя поиск первичного ключа, чтобы найти готовящиеся строки.
<ref_null_helper>(expr
)
Это внутренний объект оптимизатора без пользовательского значения.
/* select#N
*/
select_stmt
SELECT
связан со строкой в не-EXTENDED
выводе
EXPLAIN
, который имеет
id
N
.
outer_tables
semi join
(inner_tables
)
Работа полусоединения. inner_tables
показывает таблицы, которые не были вытащены. См.
раздел 9.2.1.18.1.
<temporary table>
Это представляет внутреннюю временную таблицу, составленную, чтобы кэшировать промежуточный результат.
Когда некоторые таблицы имеют тип
const
или
system
,
столбцы вовлечения выражений от этих таблиц оценены оптимизатором рано и
не являются частью выведенного на экран запроса. Однако, с
FORMAT=JSON
некоторые табличные доступы
const
выведены на экран как ref
, который использует значение константы.
Чтобы получить план выполнения относительно объяснимого выполнения запроса в названном соединении, используйте это запрос:
EXPLAIN [options
] FOR CONNECTIONconnection_id
;
EXPLAIN FOR CONNECTION
вернет данные EXPLAIN
,
которые в настоящее время используются, чтобы выполнить запрос в данном
соединении. Из-за изменений данных (и статистики) это может произвести
различное следствие выполнения EXPLAIN
на эквивалентном тексте запроса. Это различие в поведении может быть
полезным в диагностировании большего количества переходных исполнительных
проблем. Например, если Вы выполняете запрос в одном сеансе, который занимает
много времени, чтобы завершиться, использование
EXPLAIN FOR CONNECTION
в другом сеансе может привести к полезной
информации о причине задержки.
connection_id
это идентификатор соединения, как
получено из таблицы INFORMATION_SCHEMA
PROCESSLIST
или из вывода SHOW PROCESSLIST
. Если есть привилегия
PROCESS
,
Вы можете определить идентификатор для любого соединения. Иначе Вы можете
определить идентификатор только для Ваших собственных соединений.
Если названное соединение не выполняет запрос, результат пуст. Иначе
EXPLAIN FOR CONNECTION
применяется только, если запрос,
выполняемый в названном соединении, объясним. Это включает
SELECT
,
DELETE
,
INSERT
,
REPLACE
и
UPDATE
.
Но EXPLAIN FOR CONNECTION
не работает на готовых запросах, даже
подготовленных запросах тех типов.
Если названное соединение выполняет объяснимый запрос,
вывод состоит в том, при использовании чего Вы получили бы
EXPLAIN
на запрос непосредственно.
Если названное соединение выполняет запрос, который не объясним,
ошибка происходит. Например, Вы не можете назвать идентификатор соединения
для своего текущего сеанса, потому что EXPLAIN
не объясним:
mysql> SELECT CONNECTION_ID(); +-----------------+ | CONNECTION_ID() | +-----------------+ | 373 | +-----------------+ 1 row in set (0.00 sec) mysql> EXPLAIN FOR CONNECTION 373; ERROR 1889 (HY000): EXPLAIN FOR CONNECTION command is supported only for SELECT/UPDATE/INSERT/DELETE/REPLACE
Переменная Com_explain_other
указывает на число выполненных
запросов EXPLAIN FOR CONNECTION
.
В большинстве случаев Вы можете оценить работу запроса, считая дисковые
поиски. Для маленьких таблиц Вы можете обычно находить строку в один дисковый
поиск (потому что индекс, вероятно, кэшируется). Для больших таблиц Вы можете
оценить, что, используя B-дерево, Вы нуждаетесь в нескольких поисках,
чтобы найти строку:
log(
.row_count
) /
log(index_block_length
/ 3 * 2 /
(index_length
+
data_pointer_length
)) + 1
В MySQL индексный блок обычно 1024 байт и указатель данных обычно 4 байта.
Для таблицы с 500000 строк с длиной значения ключа 3 байта (размер
MEDIUMINT
),
формула указывает log(500000)/log(1024/3*2/(3+4)) + 1
=
4
поиска.
Этот индекс потребовал бы хранения приблизительно 500000 * 7 * 3/2 = 5.2MB (считая, что типичный индексный буфер заполняется с отношением 2/3), таким образом, у Вас, вероятно, есть большая часть индекса в памяти и реально надо только один или два запроса чтения данных, чтобы найти строку.
Для записи, однако, Вы нуждаетесь в четырех поисках, чтобы найти, куда поместить новое индексное значение, и обычно два, чтобы обновить индекс и написать строку.
Предыдущее обсуждение не означает, что Ваши потребительские
свойства медленно ухудшаются по log N
.
Пока все кэшируется OS или сервером MySQL, операции становятся только
незначительно медленнее, поскольку таблица становится больше.
После того, как данные становятся слишком большими, чтобы кэшироваться, дела
начинают идти намного медленнее, пока Ваши приложения не связаны только
дисковы поиском (который увеличивается по log N
).
Чтобы избежать этого, увеличьте размер ключевого кэша, поскольку данные
растут. Для MyISAM
ключевым размером кэша управляет переменная
key_buffer_size
,
см. раздел 6.1.1.
MySQL обеспечивает управление оптимизатором через системные переменные, которые затрагивают, как планы запроса оценены, переключают оптимизацию, подсказки оптимизатора и индекса и модель стоимости.
Сервер также поддерживает статистику о значениях столбцов, хотя оптимизатор еще не использует эту информацию.
Задача оптимизатора состоит в том, чтобы найти оптимальный план относительно выполнения запроса SQL. Поскольку разница в производительности между хорошим и плохом планами может быть порядками величины (то есть, секунды против часов или даже дней), большинство оптимизаторов, включая MySQL, выполняют более или менее исчерпывающий поиск оптимального плана среди всех возможных планов оценки запроса. Для запросов соединения число возможных планов, исследованных MySQL, растет по экспоненте с числом таблиц, на которые ссылаются в запросе. Для небольших количеств таблиц (как правило, меньше 7-10) это не проблема. Однако, когда большие запросы представлены, время, проведенное в оптимизации запроса, может легко стать главным узким местом.
Более гибкий метод для оптимизации запроса позволяет пользователю управлять, насколько исчерпывающе оптимизатор ищет оптимальный план оценки запроса. Общее представление состоит в том, что чем меньше планов, которые исследованы оптимизатор, тем меньше времени он тратит на компиляцию запроса. С другой стороны, потому что оптимизатор пропускает некоторые планы, он может не найти оптимальный план.
Поведением оптимизатор относительно числа планов, которые он оценивает, можно управлять, используя две системных переменные:
optimizer_prune_level
говорит оптимизатору пропустить определенные
планы, основанные на оценках числа строк для каждой таблицы. Наш опыт
показывает, что этот вариант редко пропускает оптимальные планы и может резко
уменьшить времена компиляции запроса. Именно поэтому эта опция идет
по умолчанию (optimizer_prune_level=1
).
Однако, если Вы полагаете, что оптимизатор пропустил лучший план запроса, эта
опция может быть выключена (optimizer_prune_level=0
)
с риском, что компиляция запроса может занять намного больше времени.
Отметьте, что даже с использованием этой эвристики оптимизатор все еще
исследует примерно экспоненциальное число планов.
optimizer_search_depth
говорит, как далеко в
будущее каждого неполного плана оптимизатор должен
оценивать, должно ли это быть расширено далее. Меньшие значения
optimizer_search_depth
могут привести к сильно меньшему времени
компиляции запроса. Например, запросы с 12, 13
или больше таблиц могут легко потребовать часы и даже дни анализа, если
optimizer_search_depth
близко к числу таблиц в запросе. В то же
самое время, если собрано с
optimizer_search_depth
= 3 или 4, оптимизатор может собрать
запрос меньше, чем через минуту для того же самого запроса. Если Вы не
уверены в том, какое значение разумно для
optimizer_search_depth
, эта переменная может быть установлена в 0,
чтобы сказать оптимизатору определить значение автоматически.optimizer_switch
включает управление поведением оптимизатора. Ее значение это ряд флагов,
у каждого из которых есть значение on
или off
,
чтобы указать, включено ли соответствующее поведение оптимизатора или
отключено. Эта переменная имеет глобальное и значение сеанса и может быть
изменена во время выполнения. Глобальное значение по умолчанию может быть
установлено при запуске сервера.
Чтобы видеть текущий набор флагов оптимизатор, выберите значение:
mysql> SELECT @@optimizer_switch\G *************************** 1. row *************************** @@optimizer_switch: index_merge=on,index_merge_union=on, index_merge_sort_union=on, index_merge_interраздел=on, engine_condition_pushdown=on, index_condition_pushdown=on, mrr=on,mrr_cost_based=on, block_nested_loop=on,batched_key_access=off, materialization=on,semijoin=on,loosescan=on, firstmatch=on,duplicateweedout=on, subquery_materialization_cost_based=on, use_index_extensions=on, condition_fanout_filter=on,derived_merge=on
Чтобы изменить значение
optimizer_switch
, назначьте значение, состоящее из списка
разделенных запятой значений одной или более команд:
SET [GLOBAL|SESSION] optimizer_switch='command
[,command
]...';
Каждое значение command
должно быть одной из форм, показанных в следующей таблице.
Синтаксис | Смысл |
---|---|
default | Сбросьте каждую оптимизацию к ее значению по умолчанию. |
|
Установите названную оптимизацию в ее значение по умолчанию. |
| Отключите названную оптимизацию. |
| Включите названную оптимизацию. |
Порядок команд в значении не имеет значения, хотя
default
выполнена сначала, если есть. Установка
opt_name
в default
установит к on
или off
по умолчанию. Определение любого opt_name
не раз в значении не разрешено и вызывает ошибку.
Любые ошибки в значении заставляют назначение терпеть неудачу с ошибкой,
оставляя значение
optimizer_switch
прежним.
Следующая таблица приводит допустимые имена
opt_name
, сгруппированные стратегией оптимизации.
Оптимизация | Имя флага | Смысл | Значение по умолчанию |
---|---|---|---|
Batched Key Access |
batched_key_access | Использование алгоритма BKA join | OFF |
Block Nested-Loop | block_nested_loop
| Использование алгоритма BNL join | ON |
Condition Filtering | condition_fanout_filter
| Использование фильтрации условия | ON
|
Engine Condition Pushdown |
engine_condition_pushdown | Условие механизма pushdown | ON |
Index Condition Pushdown |
index_condition_pushdown | Управление индексным pushdown | ON |
Index Extensions | use_index_extensions
| Использование средств индексного расширения |
ON |
Index Merge | index_merge | Управляет Index Merge | ON |
index_merge_intersection | Управляет Index Merge Intersection Access | ON | |
index_merge_sort_union | Управляет Index Merge Sort-Union Access | ON | |
index_merge_union | Управляет Index Merge Union Access optimization | ON
| |
Multi-Range Read | mrr | Управляет стратегией Multi-Range Read | ON |
mrr_cost_based |
Управляет MRR на основе издержек, если mrr=on |
ON | |
Semi-join | semijoin | Контролирует все стратегии полусоединения | ON |
firstmatch | Управляет стратегией FirstMatch | ON | |
loosescan |
Управляет стратегией LooseScan strategy (не путать с
LooseScan для GROUP BY ) | ON | |
duplicateweedout | Управляет стратегией Duplicate Weedout | ON | |
Subquery materialization | materialization
| Управляет материализацией (включая материализацию полусоединения) | ON |
subquery_materialization_cost_based
| Используемый выбор материализации на основе издержек | ON | |
Derived table merging | derived_merge
| Слияние полученных таблиц и представлений во внешний блок запроса | ON |
Для batched_key_access
, чтобы иметь любой эффект, когда
установлено в on
, mrr
должен также быть
on
. В настоящее время оценка стоимости для MRR слишком
пессимистична. Следовательно, также необходимо установить
mrr_cost_based
в off
для BKA.
semijoin
, firstmatch
,
loosescan
, duplicateweedout
и
materialization
включают управление полусоединением и
подзапросами стратегии материализации. semijoin
управляет,
используются ли полусоединения. Если firstmatch
и
loosescan
оба on
,
полусоединения также используют материализацию, где применимо.
Эти флаги on
по умолчанию.
Если стратегия duplicateweedout
выключена, она не
используется, если все другие применимые стратегии также не отключены.
subquery_materialization_cost_based
управляет
выбором между материализацией подзапроса и преобразованием подзапроса
IN
-to-EXISTS
. Если on
(по умолчанию),
оптимизатор выполняет выбор на основе издержек между материализацией
подзапроса и преобразованием подзапроса IN
-to-EXISTS
, если любой метод мог бы использоваться. Если флаг off
,
оптимизатор выбирает преобразование подзапроса IN -> EXISTS
.
derived_merge
управляет, пытается ли оптимизатор слить
полученные таблицы и представления во внешний блок запроса, предполагая, что
никакое другое правило не предотвращает слияние, например,
ALGORITHM
для представления имеет приоритет перед
derived_merge
. По умолчанию флаг on
, чтобы
позволять слиться. Для получения дополнительной информации см.
раздел 9.2.1.18.3.
Для получения дополнительной информации об отдельных стратегиях оптимизации см. следующие разделы:
Когда Вы назначаете значение
optimizer_switch
, флаги, которые не упомянуты, сохраняют свое текущее значение.
Это позволяет включить или отключить определенные поведения оптимизатора в
единственном запросе, не затрагивая другие поведения. Запрос не зависит от
того, что существуют другие флаги оптимизатора, и каковы их значения.
Предположите, что все оптимизации Index Merge включены:
mysql> SELECT @@optimizer_switch\G *************************** 1. row *************************** @@optimizer_switch: index_merge=on,index_merge_union=on, index_merge_sort_union=on, engine_condition_pushdown=on, index_condition_pushdown=on, mrr=on,mrr_cost_based=on, block_nested_loop=on,batched_key_access=off, materialization=on,semijoin=on,loosescan=on, firstmatch=on, subquery_materialization_cost_based=on, use_index_extensions=on, condition_fanout_filter=on
Если сервер использует Index Merge Union или Index Merge Sort-Union для определенных запросов, и Вы хотите проверить, выступит ли оптимизатор лучше без них, установите значение:
mysql> SET optimizer_switch='index_merge_union=off, index_merge_sort_union=off'; mysql> SELECT @@optimizer_switch\G *************************** 1. row *************************** @@optimizer_switch: index_merge=on,index_merge_union=off, index_merge_sort_union=off, index_merge_interраздел=on, engine_condition_pushdown=on, index_condition_pushdown=on, mrr=on,mrr_cost_based=on, block_nested_loop=on,batched_key_access=off, materialization=on,semijoin=on,loosescan=on, firstmatch=on, subquery_materialization_cost_based=on, use_index_extensions=on, condition_fanout_filter=on
Одно средство контроля стратегий оптимизатора состоит в том, чтобы
установить
optimizer_switch
(см.
раздел 9.9.2).
Изменение этой переменной влияет на все последующие запросы, чтобы затронуть
только один запрос, необходимо изменить
optimizer_switch
строго перед ним.
Другой способ управлять оптимизатором при использовании подсказок
, которые могут быть определены в пределах отдельных запросов. Поскольку
подсказки оптимизатор применяются на основе запроса, они обеспечивают более
гибкое управление планами выполнения запроса, чем можно достигнуть, используя
optimizer_switch
. Например, Вы можете включить оптимизации для одной таблицы в запросе и
отключить для иной таблицы. Подсказки в пределах запроса имеют приоритет над
флагами optimizer_switch
.
Например:
SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2 WHERE ...; SELECT /*+ NO_ICP(t1, t2) */ * FROM t1 INNER JOIN t2 WHERE ...; SELECT /*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) */ * FROM t1 ...; EXPLAIN SELECT /*+ NO_ICP(t1) */ * FROM t1 WHERE ...; SELECT /*+ MERGE(dt) */ * FROM (SELECT * FROM t1) AS dt;
Подсказки оптимизатора, описанные здесь, отличаются от индексных подсказок, описанных в разделе 9.9.4. Оптимизаторные и индексные подсказки могут использоваться отдельно или вместе.
Подсказки оптимизатора применяются на различных уровнях контекста:
Глобальный: подсказка затрагивает весь запрос.
Следующая таблица суммирует доступные подсказки оптимизатора, стратегии, которые они затрагивают, и контекст или контексты, в которых они применяются. Больше деталей дано позже.
Таблица 9.2. Доступные подсказки оптимизатора
Имя подсказки | Описание | Применимые контексты |
BKA ,
NO_BKA |
Обработка Batched Key Access join | Блок запроса, таблица |
BNL ,
NO_BNL |
Block Nested-Loop join | Блок запроса, таблица |
MAX_EXECUTION_TIME |
Время выполнения запроса | Глобально |
MERGE ,
NO_MERGE |
Полученная таблица/представление, сливающяяся во внешний блок запроса | Таблица |
MRR ,
NO_MRR |
Оптимизация Multi-Range Read | Таблица, индекс |
NO_ICP |
Оптимизация Index Condition Pushdown | Таблица, индекс |
NO_RANGE_OPTIMIZATION |
Оптимизация range | Таблица, индекс |
QB_NAME |
Назначает имя блоку запроса | Блок запроса |
SEMIJOIN ,
NO_SEMIJOIN |
Стратегии полуприсоединений | Блок запроса |
SUBQUERY | Материализация и стратегия
подзапросов IN -to-EXISTS |
Блок запроса |
Отключение оптимизации препятствует тому, чтобы оптимизатор использовал ее. Включение оптимизации означает, что оптимизатор свободен использовать стратегию, если это относится к выполнению запроса, а не то, что оптимизатор обязательно будет использовать ее.
MySQL поддерживает комментарии в запросах SQL как описано в
разделе 10.6.
Подсказки оптимизатора используют разновидность /* ... */
C-комментария, который включает символ +
после вводной
последовательности /*
:
/*+ BKA(t1) */ /*+ BNL(t1, t2) */ /*+ NO_RANGE_OPTIMIZATION(t4 PRIMARY) */ /*+ QB_NAME(qb2) */
Пробелы разрешаются после символа +
.
Анализатор признает комментарии подсказки оптимизатора после начального
ключевого слова SELECT
,
UPDATE
,
INSERT
,
REPLACE
и
DELETE
.
Подсказки разрешены в этих контекстах:
В начале запросов и операторов изменения данных:
SELECT /*+ ... */ ... INSERT /*+ ... */ ... REPLACE /*+ ... */ ... UPDATE /*+ ... */ ... DELETE /*+ ... */ ...
(SELECT /*+ ... */ ... ) (SELECT ... ) UNION (SELECT /*+ ... */ ... ) (SELECT /*+ ... */ ... ) UNION (SELECT /*+ ... */ ... ) UPDATE ... WHERE x IN (SELECT /*+ ... */ ...) INSERT ... SELECT /*+ ... */ ...
EXPLAIN
:
EXPLAIN SELECT /*+ ... */ ... EXPLAIN UPDATE ... WHERE x IN (SELECT /*+ ... */ ...)
Значение то, что Вы можете использовать
EXPLAIN
, чтобы
видеть, как оптимизатор подсказывает планы выполнения.
Комментарий подсказки может содержать многократные подсказки, но блок запроса не может содержать многократные комментарии подсказки. Это допустимо:
SELECT /*+ BNL(t1) BKA(t2) */ ...
Но не это:
SELECT /*+ BNL(t1) */ /* BKA(t2) */ ...
Когда комментарий подсказки содержит многократные подсказки, возможность дубликатов и конфликтов существует:
Двойные подсказки: Для такой подсказки, как /*+ MRR(idx1)
MRR(idx1) */
, MySQL использует первую подсказку и выпускает
предупреждение о двойной подсказке.
/*+ MRR(idx1)
NO_MRR(idx1) */
, MySQL использует первую подсказку и выпускает
предупреждение о второй противоречивой подсказке.Имена блока запроса это идентификаторы, они неотступно следуют обычным правилам, какие имена допустимы и как заключить их в кавычки (см. раздел 10.2).
Имена подсказки, имена блока запроса и имена стратегии не являются чувствительными к регистру. Ссылки на имена таблицы и индекса следуют обычным правилам чувствительности к регистру идентификатора (см. раздел 10.2.2).
Эффект подсказок на уровне таблицы:
Использование алгоритмов обработки соединения Block Nested-Loop (BNL) и Batched Key Access (BKA) (см. раздел 9.2.1.14).
Эти типы подсказки относятся к определенным таблицам или всем таблицам в блоке запроса.
Синтаксис подсказок на уровне таблицы:
hint_name
([@query_block_name
] [tbl_name
[,tbl_name
] ...])hint_name
([tbl_name
@query_block_name
[,tbl_name
@query_block_name
] ...])
Синтаксис ссылается на эти термины:
hint_name
: Эти имена подсказки разрешены:
BKA
, NO_BKA
:
Включите или отключите BKA для указанных таблиц.
BNL
, NO_BNL
:
Включите или отключите BNL для указанных таблиц.MERGE
, NO_MERGE
:
Включите или отключите слияние для указанных таблиц или представлений.
tbl_name
: Название таблицы, которая используется в
запрос. Подсказка относится ко всем таблицам, которые она называет. Если
подсказка не называет таблицу, она относится ко всем таблицам блока запроса,
в котором она происходит.
Если у таблицы есть псевдоним, подсказки должны обратиться к псевдониму, не к имени таблицы.
Имена таблиц в подсказках не могут быть квалифицированы с именами схемы.
query_block_name
: Блок запроса, к которому
применяется подсказка. Если подсказка не включает
@query_block_name
,
подсказка относится к блоку запроса, в котором она происходит. Для формата
tbl_name
@query_block_name
подсказка относится к названной таблице в названном блоке запроса.
Чтобы назначить имя блоку запроса, см.
сюда.
Примеры:
SELECT /*+ NO_BKA(t1, t2) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3; SELECT /*+ NO_BNL() BKA(t1) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3; SELECT /*+ NO_MERGE(dt) */ * FROM (SELECT * FROM t1) AS dt;
Подсказка на уровне таблицы относится к таблицам, которые получают отчеты от предыдущих таблиц, а не таблиц отправителя. Рассмотрите этот запрос:
SELECT /*+ BNL(t2) */ FROM t1, t2;
Если оптимизатор хочет обрабатывать t1
, это применяет
соединение Block Nested-Loop к t2
буферизуя строки из
t1
прежде, чем начать читать из t2
.
Если оптимизатор вместо этого хочет обрабатывать сначала t2
,
подсказка не имеет никакого эффекта, потому что t2
это таблица отправителя.
Для подсказок MERGE
и NO_MERGE
эти правила приоритета применяются:
Подсказка имеет приоритет перед любой эвристикой оптимизатора, которая не является техническим ограничением. Если предложение подсказки не имеет никакого эффекта, у оптимизатора есть причина игнорирования ее.
derived_merge
в
переменной
optimizer_switch
.ALGORITHM={MERGE|TEMPTABLE}
в
определении представления имеет приоритет перед подсказкой, определенной в
запросе, ссылающемся на представление.Подсказки оптимизатора уровня индекса указывают, какие стратегии обработки индекса оптимизатор использует для особых таблиц или индексов. Это влияет на использование оптимизаций Index Condition Pushdown (ICP), Multi-Range Read (MRR) и range (см. раздел 9.2.1).
Синтаксис индексного уровня подсказки:
hint_name
([@query_block_name
]tbl_name
[index_name
[,index_name
] ...])hint_name
(tbl_name
@query_block_name
[index_name
[,index_name
] ...])
Синтаксис ссылается на эти термины:
hint_name
: Эти имена подсказки разрешены:
MRR
, NO_MRR
:
Включите или отключите MRR для указанных таблиц или индексов. Подсказки MRR
применяются только к InnoDB
и MyISAM
.
NO_ICP
: Отключите ICP для указанных таблиц или индексов.
По умолчанию ICP стратегия оптимизации кандидата, таким образом нет никакой
подсказки для того, чтобы включить ее.NO_RANGE_OPTIMIZATION
: Отключите индексный доступ диапазона
для указанных таблиц или индексов. Эта подсказка также отключает Index Merge
и Loose Index Scan для таблиц или индексов. По умолчанию доступ диапазона
стратегия оптимизации кандидата, таким образом нет никакой подсказки для
того, чтобы включить ее.
Эта подсказка может быть полезной, когда число диапазонов может быть высоким, и оптимизация диапазона потребовала бы многих ресурсов.
tbl_name
: Таблица, к которой применяется подсказка.
index_name
: Название индексирования в названной
таблице. Подсказка относится ко всем индексам, которые она называет. Если
подсказка не называет индекс, она относится ко всем индексам в таблице.
Чтобы обратиться к первичному ключу, используйте имя
PRIMARY
. Чтобы посмотреть имена индексов, используйте
SHOW INDEX
.
query_block_name
: Блок запроса, к которому
применяется подсказка. Если подсказка не включает
@query_block_name
, она
относится к блоку запроса, в котором она происходит. Для формата
tbl_name
@query_block_name
подсказка относится к названной таблице в названном блоке запроса.
Чтобы назначить имя блоку запроса, см.
сюда.
Примеры:
SELECT /*+ MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3; SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; INSERT INTO t3(f1, f2, f3) (SELECT /*+ NO_ICP(t2) */ t2.f1, t2.f2, t2.f3 FROM t1,t2 WHERE t1.f1=t2.f1 AND t2.f2 BETWEEN t1.f1 AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1);
Подсказки оптимизатора подзапросов указывают, использовать ли
преобразования полусоединения и которые стратегии разрешить,
когда полусоединения не используются, использовать ли материализацию
подзапроса или преобразование IN
-to-EXISTS
.
См. раздел 9.2.1.18.
Синтаксис подсказок стратегии полусоединения такой:
hint_name
([@query_block_name
] [strategy
[,strategy
] ...])
Синтаксис ссылается на эти термины:
hint_name
: Эти имена подсказки разрешены:
SEMIJOIN
, NO_SEMIJOIN
:
Включите или отключите названные стратегии полусоединения.
strategy
: Стратегия полусоединения, которая будет
включена или отключена. Эти имена стратегии разрешены:
DUPSWEEDOUT
, FIRSTMATCH
,
LOOSESCAN
, MATERIALIZATION
.
Для SEMIJOIN()
, если никакие стратегии не называют,
полусоединение используются, если возможн, основаннон на стратегиях,
включенных согласно
optimizer_switch
. Если стратегии называют, но неподходящие для
запроса, используется DUPSWEEDOUT
.
Для NO_SEMIJOIN()
, если если никакие стратегии не называют,
полусоединение не используются. Если стратегии называют, которые исключают
все применимые стратегии запроса, используются DUPSWEEDOUT
.
Если один подзапрос вложен в пределах другого, и оба слиты в
полусоединение внешнего запроса, любая спецификация стратегий полусоединения
самого внутреннего запроса проигнорирована.
SEMIJOIN()
и NO_SEMIJOIN()
могут все еще использоваться, чтобы включить или отключить преобразования
полусоединения для таких вложенных подзапросов.
Если выключена DUPSWEEDOUT
, оптимизатор может произвести план
запроса, который совсем не оптимален. Это происходит из-за эвристического
сокращения во время поиска, которого можно избежать, устанавливая
optimizer_prune_level=0
.
Примеры:
SELECT /*+ NO_SEMIJOIN(@subq1 FIRSTMATCH, LOOSESCAN) */ * FROM t2 WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3); SELECT /*+ SEMIJOIN(@subq1 MATERIALIZATION, DUPSWEEDOUT) */ * FROM t2 WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3);
Синтаксис подсказок, которые затрагивают, использовать ли материализацию
подзапроса или преобразования IN
-to-EXISTS
:
SUBQUERY([@query_block_name
]strategy
)
Имя подсказки всегда SUBQUERY
.
Для SUBQUERY()
разрешены эти значения
strategy
:
INTOEXISTS
, MATERIALIZATION
.
Примеры:
SELECT id, a IN (SELECT /*+ SUBQUERY(MATERIALIZATION) */ a FROM t1) FROM t2; SELECT * FROM t2 WHERE t2.a IN (SELECT /*+ SUBQUERY(INTOEXISTS) */ a FROM t1);
Для полусоединения и SUBQUERY()
@
определяет блок запроса, к которому применяется подсказка. Если подсказка не
включает query_block_name
@
,
подсказка относится к блоку запроса, в котором она происходит.query_block_name
Если комментарий подсказки содержит многократные подсказки подзапроса, используется первая. Если есть другие после подсказок этого типа, они производят предупреждение. Следующие подсказки других типов тихо проигнорированы.
MAX_EXECUTION_TIME()
разрешают только для
SELECT
. Это устанавливает границу
N
(значение тайм-аута в миллисекундах) на то, сколько
времени запросу разрешают выполняться:
MAX_EXECUTION_TIME(N
)
Пример с тайм-аутом в 1 секунду (1000 миллисекунд):
SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM t1 INNER JOIN t2 WHERE ...
MAX_EXECUTION_TIME(
устанавливает тайм-аут выполнения запроса в
N
)N
миллисекунд. Если эта опция отсутствует или
N
0, тайм-аут запроса установлен
max_execution_time
.
MAX_EXECUTION_TIME()
применима следующим образом:
Для запросов с многократным SELECT
, например,
союзы или запросы с подзапросами, MAX_EXECUTION_TIME()
относится ко всему запросу и должна появиться после первого
SELECT
.
SELECT
только
для чтения. Запросы, которые не только для чтения, являются теми, которые
вызывают сохраненную функцию, которая изменяет данные как побочный эффект.
SELECT
в сохраненных программах и проигнорировано.На уровне таблицы, индекса и подзапроса определенные блоки разрешают
подсказки оптимизатора, которые включают имя как часть их синтаксиса
параметра. Чтобы создать эти имена, используйте подсказку
QB_NAME()
, которая назначает имя блоку запроса, в
котором она происходит:
QB_NAME(name
)
QB_NAME()
могут использоваться, чтобы сделать явным
способ, которым запрашивают блоки, к которым относятся другие подсказки.
Они также разрешают, чтобы все имена блока были определены
в пределах единственного комментария подсказки для более легкого понимания
сложных запросов. Рассмотрите следующий запрос:
SELECT ... FROM (SELECT ... FROM (SELECT ... FROM ...)) ...
QB_NAME()
назначает имена блокам в запросе:
SELECT /*+ QB_NAME(qb1) */ ... FROM (SELECT /*+ QB_NAME(qb2) */ ... FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...
Тогда другие подсказки могут использовать те имена, чтобы обратиться к соответствующим блокам запроса:
SELECT /*+ QB_NAME(qb1) MRR(@qb1 t1) BKA(@qb2) NO_MRR(@qb3t1 idx1, id2) */ ... FROM (SELECT /*+ QB_NAME(qb2) */ ... FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...
Получающийся эффект следующий:
MRR(@qb1 t1)
относится к таблице
t1
в блоке запроса qb1
.
BKA(@qb2)
относится к блоку запроса qb2
.NO_MRR(@qb3 t1 idx1, id2)
относится к индексам
idx1
и idx2
в таблице t1
в
блоке запроса qb3
.Имена блока запроса это идентификаторы, они неотступно следуют обычным правилам, какие имена допустимы и как заключить их в кавычки (см. раздел 10.2). Например, имя блока запроса, которое содержит пробелы, должно быть заключено в кавычки, что может быть сделан, например, так:
SELECT /*+ BKA(@`my hint name`) */ ... FROM (SELECT /*+ QB_NAME(`my hint name`) */ ...) ...
Если включен режим SQL
ANSI_QUOTES
, также возможно заключить имена блока запроса в
кавычки в пределах двойных кавычек:
SELECT /*+ BKA(@"my hint name") */ ... FROM (SELECT /*+ QB_NAME("my hint name") */ ...) ...
Индексные подсказки дают оптимизатору информацию о том, как выбрать индекс во время обработки запроса. Индексные подсказки, описанные здесь, отличаются от подсказок оптимизатора, описанных в разделе 9.9.3. Индексные и подсказки оптимизатора могут использоваться отдельно или вместе.
Индексные подсказки определены после имени таблицы. Для общего синтаксиса
для того, чтобы определить таблицы в
SELECT
см.
раздел 14.2.9.2.
Синтаксис для того, чтобы обратиться к отдельной таблице, включая индексные
подсказки, похож на это:
tbl_name
[[AS]alias
] [index_hint_list
]index_hint_list
:index_hint
[,index_hint
] ...index_hint
: USE {INDEX|KEY} [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list
]) | IGNORE {INDEX|KEY} [FOR {JOIN|ORDER BY|GROUP BY}] (index_list
) | FORCE {INDEX|KEY} [FOR {JOIN|ORDER BY|GROUP BY}] (index_list
)index_list
:index_name
[,index_name
] ...
USE INDEX (
говорит MySQL использовать только один из названных индексов, чтобы найти
строки в таблице. Альтернативный синтаксис index_list
)IGNORE INDEX
(
говорит MySQL
не использовать некоторый индекс или индексы. Эти подсказки полезны, если
index_list
)EXPLAIN
показывает, что MySQL
использует не тот индекс.
FORCE INDEX
похожа на USE INDEX
(
с тем дополнением, что сканирование
таблицы очень дорого. Другими словами, сканирование таблицы используется,
только если нет никакого способа использовать один из названных индексов,
чтобы найти строки в таблице.index_list
)
Каждая подсказка требует названия индексов, а не
названия столбцов. Чтобы обратиться к первичному ключу, используйте имя
PRIMARY
. Чтобы посмотреть имена индексов, используйте
SHOW INDEX
.
index_name
не должно быть полным именем индекса.
Это может быть однозначный префикс имени.
Если префикс неоднозначен, ошибка происходит.
Примеры:
SELECT * FROM table1 USE INDEX (col1_index,col2_index) WHERE col1=1 AND col2=2 AND col3=3; SELECT * FROM table1 IGNORE INDEX (col3_index) WHERE col1=1 AND col2=2 AND col3=3;
Синтаксис для индексных подсказок имеет следующие характеристики:
Это синтаксически допустимо, чтобы опустить
index_list
для USE INDEX
,
что означает не использовать индексы. Исключение
index_list
для FORCE INDEX
или
IGNORE INDEX
является ошибкой.
FOR
. Это обеспечивает более точное управление выбором
оптимизатором плана выполнения относительно различных фаз обработки запроса.
Чтобы затронуть только использование индекса, когда MySQL решает, как найти
строки в таблице и как обработать соединения, надо использовать
FOR JOIN
. Чтобы влиять на использование индекса
для сортировки или группировки строк, используют FOR ORDER BY
или FOR GROUP BY
.SELECT * FROM t1 USE INDEX (i1) IGNORE INDEX FOR ORDER BY (i2) ORDER BY a;
Это не ошибка, назвать тот же самый индекс в нескольких подсказках (даже в пределах той же самой подсказки):
SELECT * FROM t1 USE INDEX (i1) USE INDEX (i1,i1);
Однако, ошибка смешать USE INDEX
и
FORCE INDEX
для той же самой таблицы:
SELECT * FROM t1 USE INDEX FOR JOIN (i1) FORCE INDEX FOR JOIN (i2);
Если индексная подсказка не включает FOR
,
контекст подсказки должен относиться ко всем частям запроса.
Например, эта подсказка:
IGNORE INDEX (i1)
эквивалентно этой комбинации подсказок:
IGNORE INDEX FOR JOIN (i1) IGNORE INDEX FOR ORDER BY (i1) IGNORE INDEX FOR GROUP BY (i1)
В MySQL 5.0 контекст подсказки без FOR
должен был примениться только к извлечению строки. Заставить сервер
использовать это более старое поведение, когда нет FOR
, можно,
включив переменную old
при запуске сервера. Заботьтесь о включении этой переменной в установке
репликации. С основанным на запрос двоичным журналированием различные режимы
для ведущего и ведомых устройств моут привести к ошибкам.
Когда индексные подсказки обработаны, они собраны в единственном списке
по типу (USE
,
FORCE
, IGNORE
) и контексту
(FOR JOIN
, FOR ORDER BY
,
FOR GROUP BY
):
SELECT * FROM t1 USE INDEX () IGNORE INDEX (i2) USE INDEX (i1) USE INDEX (i2);
эквивалентно:
SELECT * FROM t1 USE INDEX (i1,i2) IGNORE INDEX (i2);
Индексные подсказки применены для каждого контекста в следующем порядке:
{USE|FORCE} INDEX
применен, если есть.
В противном случае используется набор индексов, определенный оптимизатором.
IGNORE INDEX
применен по результату предыдущего шага.
Например, следующие два запроса эквивалентны:
SELECT * FROM t1 USE INDEX (i1) IGNORE INDEX (i2) USE INDEX (i2); SELECT * FROM t1 USE INDEX (i1);
Для FULLTEXT
индексные подсказки работают так:
Для поисков режима естественного языка подсказки, тихо
проигнорированы. Например, IGNORE INDEX(i1)
проигнорирована без предупреждения, а индекс все еще используется.
FOR ORDER BY
или
FOR GROUP BY
тихо проигнорированы. Подсказки с
FOR JOIN
или без FOR
соблюдают. В отличие от того,
как подсказки применяются для не-FULLTEXT
, здесь
подсказка используется для всех фаз выполнения запроса (поиск строк
и извлечение, группировка и упорядочивание). Это истина, даже если подсказка
дана для не-FULLTEXT
.
Например, следующие два запроса эквивалентны:
SELECT * FROM t USE INDEX (index1) IGNORE INDEX (index1) FOR ORDER BY IGNORE INDEX (index1) FOR GROUP BY WHERE ... IN BOOLEAN MODE ... ; SELECT * FROM t USE INDEX (index1) WHERE ... IN BOOLEAN MODE ... ;
Чтобы произвести планы выполнения, оптимизатор использует модель стоимости, которая основана на оценках стоимости различных операций, которые происходят во время выполнения запроса. Оптимизатор имеет ряд констант стоимости по умолчанию, чтобы принять решения относительно планов выполнения.
У оптимизатора также есть база данных смет, чтобы использовать во время
конструкции плана выполнения. Эти оценки сохранены в таблицах
server_cost
и engine_cost
в системной базе данных
mysql
и конфигурируемы в любое время. Намерение этих таблиц
состоит в том, чтобы позволить легко скорректировать сметы, которые
оптимизатор использует, когда пытается достичь планов выполнения запроса.
Конфигурируемые оптимизатором модели стоимости работают так:
Сервер читает таблицы модели стоимости в память при запуске и
использует значения в памяти во время выполнения. Любая не-NULL
смета, определенная в таблицах, имеет приоритет перед
постоянной стоимостью по умолчанию. Любая NULL
оценка указывает оптимизатору использовать стоимость по умолчанию.
FLUSH OPTIMIZER_COSTS
.NULL
.
Оптимизатор использует значения стоимости в памяти, так что изменения таблиц
должны сопровождаться FLUSH OPTIMIZER_COSTS
.Образцовая база данных стоимости оптимизатора состоит из двух таблиц в
базе данных mysql
, которые содержат информацию о смете для
операций, которые происходят во время выполнения запроса:
server_cost
: Сметы оптимизатора для
общих операций сервера.
engine_cost
: Сметы оптимизатора для операций, определенных
для особых механизмов хранения.Таблица server_cost
содержит эти столбцы:
cost_name
Название сметы используется в модели стоимости. Имя не является чувствительным к регистру. Если сервер не признает имя стоимости, когда читает эту таблицу, он пишет предупреждение в журнал ошибок.
cost_value
Значение сметы. Если значение не-NULL
,
сервер использует это в качестве стоимости. Иначе это использует оценку
по умолчанию. DBA может изменить смету, обновляя этот столбец. Если сервер
находит, что значение стоимости недопустимо (неположительно), когда читает
эту таблицу, он пишет предупреждение в журнал ошибок.
Чтобы переопределить смету по умолчанию (для записи, которая определяет
NULL
), установите стоимость в не-NULL
.
Чтобы вернуться к значению по умолчанию, установите значение в
NULL
. Тогда выполните FLUSH
OPTIMIZER_COSTS
, чтобы сказать серверу перечитать таблицы.
last_update
Время последнего обновления строки.
comment
Описательный комментарий. DBA может использовать этот столбец, чтобы предоставить информацию о том, почему строка сметы хранит особое значение.
Первичный ключ для server_cost
это столбец
cost_name
, таким образом, невозможно создать многократные записи
для любой сметы.
Сервер признает значения cost_name
для
server_cost
:
disk_temptable_create_cost
(по умолчанию 40.0),
disk_temptable_row_cost
(1.0).
Сметы для внутренне составленных временных таблиц, сохраненных
в основанном на диске механизме хранения (InnoDB
или
MyISAM
). Увеличение этих значений увеличивает смету
использования внутренних временных таблиц и заставляет оптимизатор
предпочесть планы запроса с меньшим количеством их использования.
Для информации о таких таблицах см.
раздел 9.4.4.
Большие значения по умолчанию для этих дисковых параметров по
сравнению со значениями по умолчанию для соответствующих параметров памяти
(memory_temptable_create_cost
,
memory_temptable_row_cost
)
отражают большую стоимость обработки основанных на диске таблиц.
key_compare_cost
(по умолчанию 0.1).
Стоимость сравнения ключей. Увеличение этого значения вызывает план
запроса, который сравнивает много ключей, чтобы стать более дорогим.
Например, план запроса, который выполняет filesort
становится относительно более дорогим по сравнению с планом запроса, который
избегает сортировать при использовании индексирования.
memory_temptable_create_cost
(по умолчанию 2.0),
memory_temptable_row_cost
(по умолчанию 0.2).
Сметы для внутренне составленных временных таблиц, сохраненных в
MEMORY
. Увеличение этих значений увеличивает смету использования
внутренних временных таблиц и заставляет оптимизатор предпочесть планы
запроса с меньшим количеством их использования. Для информации о таких
таблицах см. раздел 9.4.4
.
Меньшие значения по умолчанию для этих параметров по сравнению со
значениями по умолчанию для соответствующих дисковых параметров
(disk_temptable_create_cost
,
disk_temptable_row_cost
) отражают меньшую стоимость обработки
основанных на памяти таблиц.
row_evaluate_cost
(по умолчанию 0.2).
Стоимость оценки условий. Увеличение этого значения вызывает план запроса, который исследует много строк, чтобы стать более дорогим по сравнению с планом запроса, который исследует меньше строк. Например, сканирование таблицы становится относительно более дорогим по сравнению с просмотром диапазона, который читает меньше строк.
Таблица engine_cost
содержит эти столбцы:
engine_name
Название механизма хранения, к которому применяется эта смета. Имя не
является чувствительным к регистру. Если значение default
,
это относится ко всем механизмам хранения, у которых нет никакой собственной
записи. Если сервер не признает имя механизма, когда читает эту таблицу, он
пишет предупреждение журналу ошибок.
device_type
Тип устройства, к которому применяется эта смета. Столбец предназначен для того, чтобы определить различные сметы для различных типов устройства хранения данных, таких как жесткие диски против SSD. В настоящее время эта информация не используется, 0 единственное разрешенное значение.
cost_name
То же самое, как в таблице server_cost
.
cost_value
То же самое, как в таблице server_cost
.
last_update
То же самое, как в таблице server_cost
.
comment
То же самое, как в таблице server_cost
.
Первичный ключ для engine_cost
это кортеж, включающий
(cost_name
, engine_name
, device_type
),
таким образом, невозможно создать многократные записи для любой комбинации
значений в тех столбцах.
Сервер признает эти значения cost_name
для engine_cost
:
io_block_read_cost
(по умолчанию 1.0)
Стоимость чтения индексирования или блока данных с диска. Увеличение этого значения вызывает план запроса, который читает много дисковых блоков, чтобы стать более дорогим по сравнению с планом запроса, который читает меньше дисковых блоков. Например, сканирование таблицы становится относительно более дорогим по сравнению с просмотром диапазона, который читает меньше блоков.
memory_block_read_cost
(по умолчанию 1.0)
Подобно io_block_read_cost
, но представляет стоимость чтения
индексирования или блока данных из буфера базы данных в памяти.
Для DBA, кто хочет изменить параметры модели стоимости, попытайтесь удвоить или разделить на два значение и измерить эффект.
Изменения io_block_read_cost
и
memory_block_read_cost
, наиболее вероятно, приведут к стоящим
результатам. Эти значения параметра позволяют моделям стоимости для методов
доступа к данным принять во внимание затраты чтения информации из различных
источников, то есть, стоимость чтения информации с диска против чтения
информации уже в буфере памяти. Например, при прочих равных условиях,
установка io_block_read_cost
к значению, больше чем
memory_block_read_cost
заставляет оптимизатор предпочитать планы
запроса, которые читают информацию, уже имеющуюся в
памяти, планам, которые должны читать с диска.
Этот пример показывает, как изменить значение по умолчанию для
io_block_read_cost
:
UPDATE mysql.engine_cost SET cost_value = 2.0 WHERE cost_name = 'io_block_read_cost'; FLUSH OPTIMIZER_COSTS;
Этот пример показывает, как изменить значение
io_block_read_cost
только для InnoDB
:
INSERT INTO mysql.engine_cost VALUES ('InnoDB', 0, 'io_block_read_cost', 3.0, CURRENT_TIMESTAMP, 'Using a slower disk for InnoDB'); FLUSH OPTIMIZER_COSTS;
Таблица column_stats
базы данных mysql
разработана, чтобы сохранить статистику о значениях столбцов.
В настоящее время оптимизатор еще не консультируется с
column_stats
в ходе выполнения запроса.
У таблицы column_stats
есть эти характеристики:
Таблица содержит статистику для столбцов всех типов данных,
кроме типов геометрии (пространственные данные) и
JSON
.
InnoDB
.column_stats
были выполнены сервером, не пользователями.У таблицы column_stats
есть эти столбцы:
database_name
, table_name
,
column_name
: Названия базы данных, таблицы и столбца, для
которого применяются статистические данные. Эти имена формируют первичный
ключ для строк в column_stats
.
histogram
: Значение JSON
, описывающее статистику для столбца,
сохраненного как гистограмма.column_stats
использует
JSON
, чтобы разрешить гибкость в
представлении статистики столбца. Статистические данные для столбца принимают
форму гистограммы, содержащей buckets для частей
диапазона значений, сохраненных в столбце.
MySQL использует несколько стратегий, которые кэшируют в памяти информацию, чтобы увеличить производительность.
InnoDB
поддерживает область
хранения, названную буферным пулом
для того, чтобы кэшировать данные и индексы в памяти. Знание, как
работает пул и использование его в своих интересах, чтобы сохранить данные,
к которым часто получают доступ, в памяти, это важный аспект настройки MySQL.
Для объяснения внутренних работ пула InnoDB
см.
раздел 16.6.3.1.
Для дополнительной конфигурации пула см. эти разделы:
Чтобы минимизировать дисковый ввод/вывод, MyISAM
эксплуатирует стратегию, которая используется многими системами управления
базой данных. Это использует механизм кэша, чтобы сохранить табличные блоки,
к которым наиболее часто получают доступ, в памяти:
Для индексных блоков специальная структура, названная ключевым кэшем (или ключевым буфером) поддержана. Структура содержит много буферов блоков, где наиболее используемые индексные блоки помещены.
Этот раздел сначала описывает основную работу ключевого кэша
MyISAM
. Далее это обсуждает особенности, которые улучшают
работу ключевого кэша и позволяют Вам лучше управлять работой кэша:
Многократные сеансы могут получить доступ к кэшу одновременно.
Чтобы управлять размером ключевого кэша, используйте переменную
key_buffer_size
.
Если эта переменная установлена равной 0, никакой ключевой кэш не
используется. Ключевой кэш также не используется, если значение
key_buffer_size
является слишком маленьким, чтобы выделить минимальное число буферов блоков.
Когда ключевой кэш не работает, к индексным файлам получают доступ, используя только родную буферизацию файловой системы, обеспеченную операционной системой. Другими словами, индексные блоки получены, используя ту же самую стратегию, как для табличных блоков данных.
Индексный блок это непрерывный модуль доступа к индексным файлам
MyISAM
. Обычно размер индексного блока равен размеру узлов
индексного B-дерева. Индексы представлены на диске, используя структуру
данных B-дерева. Узлы у основания дерева это узлы листа. Узлы выше узлов
листа это узлы нелиста.
Все блоки буферов в ключевой структуре кэша имеют тот же самый размер. Этот размер может быть равным, больше чем, или меньше, чем размер индексного блока таблицы. Обычно эти два значения кратны.
Когда к данным из индексного блока любой таблицы нужно получить доступ, сервер сначала проверяет, доступно ли это в некотором буфере ключевого кэша. Если это так, данные ищутся ключевом кэше, а не на диске. Таким образом, это читает из кэша вместо того, чтобы читать с диска. Иначе сервер выбирает кэш, содержащий иной индексный блок (или блоки) и заменяет данные там копией необходимой информации. Как только новый индексный блок находится в кэше, к индексным данным можно получить доступ.
Если происходит, что блок, выбранный для замены, был изменен, блок считают грязным. В этом случае до замены его содержание сбрасывается на диск.
Обычно сервер следует стратегии LRU (Least Recently Used): выбирая блок для замены, это выбирает последний использованный индексный блок. Чтобы сделать этот выбор легче, ключевой модуль кэша поддерживает все используемые блоки в специальном списке (LRU chain), упорядоченный временем использования. Когда к блоку получают доступ, это используется и помещено в конце списка. Когда блоки должны быть заменены, блоки в начале списка использованы последними и становятся первыми кандидатами на выгрузку.
InnoDB
тоже использует LRU,
чтобы управлять его буферным пулом. См.
раздел 16.6.3.1.
Потоки могут обращаться к кэшу ключей одновременно, согласно следующим условиям:
К буферу, который не обновляется, могут получить доступ много сеансов.
Совместно используемый доступ к ключевому кэшу позволяет серверу улучшить пропускную способность значительно.
Совместно используемый доступ к ключевому кэшу улучшает работу, но не устраняет проблемы среди сеансов полностью. Они все еще конкурируют за структуры управления, которые управляют доступом к ключевым буферам кэша. Чтобы уменьшить проблемы доступа кэша далее, MySQL также обеспечивает многократные ключевые кэши. Эта опция позволяет назначить различные индексы различным ключевым кэшам.
Где есть многократные ключевые кэши, сервер должен знать,
который кэш использовать, обрабатывая запросы для данной таблицы
MyISAM
. По умолчанию все индексы MyISAM
кэшируются в ключевом кэше значения по умолчанию. Чтобы назначить индекс
к определенному ключевому кэшу, используйте
CACHE INDEX
(см. раздел
14.7.6.2). Например, следующий запрос назначает индекс от таблиц
t1
, t2
и t3
к ключевому кэшу hot_cache
:
mysql> CACHE INDEX t1, t2, t3 IN hot_cache; +---------+--------------------+----------+----------+ | Table | Op | Msg_type | Msg_text | +---------+--------------------+----------+----------+ | test.t1 | assign_to_keycache | status | OK | | test.t2 | assign_to_keycache | status | OK | | test.t3 | assign_to_keycache | status | OK | +---------+--------------------+----------+----------+
Ключевой кэш, упомянутый в CACHE
INDEX
, может быть создан, устанавливая его размер с
SET GLOBAL
или при использовании опций запуска сервера. Например:
mysql> SET GLOBAL keycache1.key_buffer_size=128*1024;
Чтобы разрушить ключевой кэш, установите его размер в ноль:
mysql> SET GLOBAL keycache1.key_buffer_size=0;
Вы не можете разрушить ключевой кэш по умолчанию. Любая попытка сделать это, будет проигнорирована:
mysql> SET GLOBAL key_buffer_size = 0; mysql> SHOW VARIABLES LIKE 'key_buffer_size'; +-----------------+---------+ | Variable_name | Value | +-----------------+---------+ | key_buffer_size | 8384512 | +-----------------+---------+
Ключевые переменные кэша это структурированные системные переменные, у
которых есть имя и компоненты. Для
keycache1.key_buffer_size
keycache1
это имя переменной кэша и
key_buffer_size
компонент кэша. См.
раздел 6.1.6.1.
По умолчанию индексы назначены на основной (по умолчанию) ключевой кэш, создаваемый при запуске сервера. Когда ключевой кэш разрушен, все индексы, назначенные на него, повторно назначены на ключевой кэш по умолчанию.
Для занятого сервера Вы можете использовать стратегию, которая вовлекает три ключевых кэша:
hot, который занимает 20% места, выделенного для всех ключевых кэшей. Используйте это для таблиц, которые в большой степени используются для поисков, но не обновлены.
Одна причина использование трех ключевых кэшей состоит в том, что доступ к одной ключевой структуре кэша не блокирует доступ к другим. Запросы, назначенные на один кэш, не конкурируют с запросами, назначенными на другой кэш. Прирост производительности происходит также и по другим причинам:
Горячий кэш используется только для запросов извлечения, таким образом, его содержание никогда не изменяется. Следовательно, всякий раз, когда индексный блок должен быть втянут с диска, содержание блока кэша, выбранного для замены, не должно сбрасываться.
CACHE INDEX
настраивает ассоциацию между таблицей и ключевым кэшем, но ассоциация
потеряна каждый раз при перезапуске сервера. Если Вы хотите, чтобы ассоциация
вступила в силу каждый раз, когда сервер запускается, один способ достигнуть
этого в том, что это должно использовать файл опции: включите
настройки, которые конфигурируют Ваши ключевые кэши, и опцию
init-file
, которая называет файл, содержащий
CACHE INDEX
:
key_buffer_size = 4G hot_cache.key_buffer_size = 2G cold_cache.key_buffer_size = 2G init_file=/path
/to
/data-directory
/mysqld_init.sql
Запросы в mysqld_init.sql
выполнены каждый раз, когда сервер
запускается. Файл должен содержать один запрос SQL на строку. Следующий
пример назначает нескольким таблицам hot_cache
и
cold_cache
:
CACHE INDEX db1.t1, db1.t2, db2.t3 IN hot_cache CACHE INDEX db1.t4, db2.t5, db2.t6 IN cold_cache
По умолчанию ключевая система управления кэша использует простую стратегию LRU выбора ключевых блоков кэша, которые будут вычеркнуты, но она также поддерживает более сложный метод, названный стратегией вставки середины.
Используя стратегию вставки середины, цепочка LRU разделена на две части:
горячий подсписок и теплый подсписок. Пункт подразделения между двумя частями
не установлен, но ключевая система управления кэша заботится,
что теплая часть не слишком короткая, всегда
содержащая, по крайней мере,
key_cache_division_limit
процент ключевых блоков кэша.
key_cache_division_limit
компонент структурированных ключевых
переменных кэша, таким образом, его значение это параметры, которые могут
быть установлены для кэша.
Когда индексный блок считан из таблицы в ключевой кэш, он помещен в конце теплого подсписка. После определенного числа хитов (доступов к блоку), это продвинуто в горячий подсписок. В настоящее время число хитов, требуемых, чтобы продвинуть блок (3), является тем же самым для всех индексных блоков.
Блок, продвинутый в горячий подсписок, помещен в конце списка. Блок
циркулирует в пределах этого подсписка. Если блок остается в начале подсписка
в течение достаточно долгого времени, это понижено к теплому подсписку. Это
время определено значением
key_cache_age_threshold
компонента ключевого кэша.
Пороговое значение предписывает что, для ключевого кэша, содержащего
N
блоков, блок в начале горячего подсписка, не
получивший доступ в пределах последних
хитов должен быть перемещен в начало теплого подсписка. Это становится первым
кандидатом на вычеркивание, потому что блоки для замены всегда берутся из
начала теплого подсписка.N
* key_cache_age_threshold / 100
Стратегия вставки середины позволяет Вам сохранить более ценные
блоки всегда в кэше. Если Вы предпочитаете использовать простую стратегию
LRU, установите
key_cache_division_limit
к его значению по умолчанию 100.
Стратегия вставки середины помогает улучшить работу, когда выполнение
запроса, который требует эффективного просмотра индекса, продвигает из кэша
все индексные блоки, соответствующие ценным высокоуровневым узлам B-дерева.
Чтобы избежать этого, Вы должны использовать стратегию вставки середины с
key_cache_division_limit
намного меньше, чем 100. Тогда ценные
узлы сохранены в горячем подсписке во время работы просмотра индекса.
Если есть достаточно много блоков в ключевом кэше, чтобы разместить блоки всего индекса или по крайней мере блоки, соответствующие его узлам нелиста, имеет смысл предварительно загружать ключевой кэш индексными блоками прежде, чем начать использовать это. Предварительная загрузка позволяет поместить индексные блоки в ключевой буфер кэша самым эффективным способом: читая индексные блоки с диска последовательно.
Без предварительной загрузки блоки все еще помещены в ключевой кэш как необходимо запросам. Хотя блоки останутся в кэше, потому что есть достаточно много буферов для всех них, они забраны с диска в случайном порядке, а не последовательно.
Чтобы предварительно загрузить индексирование в кэш, используйте
LOAD INDEX INTO CACHE
.
Например, следующий запрос предварительно загружает узлы индексов таблиц
t1
и t2
:
mysql> LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES; +---------+--------------+----------+----------+ | Table | Op | Msg_type | Msg_text | +---------+--------------+----------+----------+ | test.t1 | preload_keys | status | OK | | test.t2 | preload_keys | status | OK | +---------+--------------+----------+----------+
IGNORE LEAVES
заставляет только блоки для узлов нелиста
индекса быть предварительно загруженными. Таким образом, запрос
предварительно загрузит все индексные блоки из t1
,
но только блоки для узлов нелиста из t2
.
Если индексирование было назначено на ключевой кэш, используя
CACHE INDEX
, блоки
предварительно загружаются в этот кэш. Иначе индекс загружен в ключевой
кэш по умолчанию.
Возможно определить размер буферов блоков для отдельного ключевого кэша,
используя
key_cache_block_size
. Это разрешает настраивать
исполнение операций ввода/вывода для индексных файлов.
Лучшая работа для операций ввода/вывода достигнута, когда размер буферов чтения равен размеру родных буферов ввода/вывода операционной системы. Но установка размера ключевых узлов, равных размеру буфера ввода/вывода, не всегда гарантирует лучшую эффективность работы. Читая большие узлы листа, сервер вытягивает в большом количестве ненужные данные, эффективно предотвращая чтение других узлов листа.
Чтобы управлять размером блоков в индексных файлах .MYI
,
используйте опцию
--myisam-block-size
при запуске сервера.
Ключевой кэш может быть реструктурирован в любое время, обновляя его значения. Например:
mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;
Если Вы назначаете
key_buffer_size
или
key_cache_block_size
ключевому компоненту кэша значение, которое отличается от
текущего значения компонента, сервер разрушает старую структуру кэша и
создает новый, основанный на новых значениях. Если кэш содержит какие-либо
грязные блоки, сервер сохраняет их на диск прежде, чем разрушить и обновить
кэш. Реструктурирование не происходит, если Вы изменяете другие
ключевые параметры кэша.
Реструктурируя ключевой кэш, сервер сначала сбрасывает содержание любых грязных буферов на диск. После этого содержание кэша становится недоступным. Однако, реструктурирование не блокирует запросы, которые должны использовать индекс, назначенный на кэш. Вместо этого сервер непосредственно получает доступ к индексам таблицы, используя родное кэширование файловой системы. Кэширование файловой системы не столь же эффективно, как использование ключевого кэша, хотя запросы выполняются, замедление может ожидаться. После того, как кэш был реструктурирован, это становится доступным снова для кэширования назначенных индексов, и использование файловой системы для кэширования индексов прекращается.
Кэш запроса хранит текст SELECT
вместе с соответствующим результатом, который послали клиенту.
Если идентичный запрос получен позже, сервер получает результат
запроса из кэша вместо того, чтобы разобрать и выполнить запрос снова.
Кэш запроса совместно использован сеансами, таким образом, набор результатов,
произведенный одним клиентом, можно послать в ответ на тот же самый запрос,
выпущенный другим клиентом.
Кэш запроса может быть полезным в окружающей среде, где у Вас есть таблицы, которые не изменяются очень часто и для которых сервер получает много идентичных запросов. Это типичная ситуация для многих веб-серверов, которые производят много динамических страниц, основанных на контенте базы данных.
Кэш запроса не возвращает устаревшие данные. Когда таблицы изменены, любые соответствующие записи в кэше запроса сбрасываются.
Кэш запроса не работает в окружающей среде, где у Вас есть многократные
серверы mysqld
, обновляющие те же самые таблицы MyISAM
.
Кэш запроса используется для готовых запросов при условиях, описанных в разделе 9.10.3.1.
Кэш запроса не поддержан для разделенных таблиц и автоматически отключен для запросов, вовлекающих разделенные таблицы. Кэш запроса не может быть включен для таких запросов.
Некоторые характеристики для кэша запроса приведены ниже. Эти результаты были произведены, выполняя эталонный набор MySQL на Linux Alpha 2*500MHz 2GB RAM с кэшем запроса в 64MB.
Если все запросы, которые Вы выполняете, просты (такие как выбор строки из таблицы с одной строкой), но все еще отличаются так, чтобы запросы не могли кэшироваться, издержки для того, чтобы иметь активный кэш запроса составляют 13%. Это может быть расценено как худший вариант развития событий. В действительности запросы имеют тенденцию быть намного более сложными, таким образом, издержки обычно значительно ниже.
Чтобы отключить кэш запроса при запуске сервера, установите
query_cache_size
в 0.
Кэш запроса предлагает потенциал для существенного исполнительного усовершенствования, но не предполагайте, что это сделает так при всех обстоятельствах. С некоторыми конфигурациями кэша запроса или рабочими нагрузками сервера, Вы могли бы фактически видеть даже снижение производительности:
Будьте осторожны с чрезмерно большим кэшем запроса, который увеличивает издержки, требуемые, чтобы поддержать кэш, возможно, сверх выгоды от его включения. Размеры в десятки мегабайтов обычно выгодны. Размеры в сотни мегабайтов не нужны.
SELECT
, намного более
вероятно извлечет выгоду из включения кэшу, чем соединение, в котором частый
INSERT
вызывает непрерывное
аннулирование результатов в кэше. В некоторых случаях обходное решение должно
использовать опцию SQL_NO_CACHE
, чтобы предотвратить загрузку
данных в кэш для SELECT
, которые
используют часто изменяемые таблицы. См.
раздел 9.10.3.2.
Чтобы проверить, что включение кэша запроса выгодно, проверьте работу своего сервера MySQL с включенным и отключенным кэшем. Потом повторно проверяйте периодически, потому что эффективность кэша запроса может измениться в зависимости от того, как рабочая нагрузка сервера изменяется.
Этот раздел описывает, как кэш запроса работает, когда это является операционным. Раздел 9.10.3.3 описывает, как управлять, является ли это операционным.
Поступающие запросы сравниваются с кэшем запроса перед парсингом, таким образом, следующие два запроса расценены как отличающиеся:
SELECT * FROMtbl_name
Select * fromtbl_name
Запросы должны быть точно теми же самыми, чтобы быть замеченными как идентичные. Кроме того, строки, которые идентичны, могут быть обработаны как отличающиеся по другим причинам. Запросы, которые используют различные базы данных, различные версии протокола или различные наборы символов по умолчанию, считаются различными запросами и кэшируются отдельно.
Кэш не используется для запросов следующих типов:
Запросы, которые являются подзапросом внешнего запроса.
Прежде, чем результат запроса будет принесен из кэша запроса, MySQL
проверяет, имеет ли пользователь привилегию
SELECT
для всех затронутых баз
данных и таблиц. Если дело обстоит не так, кэшируемый
результат не используется.
Если результат запроса возвращен из кэша запроса, сервер постепенно
увеличивает Qcache_hits
, но не Com_select
. См.
раздел 9.10.3.4.
Если таблица изменяется, все кэшируемые запросы, которые используют
таблицу, становятся недопустимыми и удалены из кэша. Это включает запросы,
которые используют таблицы MERGE
, которые отображаются на
измененную таблицу. Таблица может быть изменена многими типами запросов,
например, INSERT
,
UPDATE
,
DELETE
,
TRUNCATE TABLE
,
ALTER TABLE
,
DROP TABLE
или
DROP DATABASE
.
Кэш запроса также работает в пределах
транзакций, используя InnoDB
.
Результат SELECT
на представлении кэшируется.
Кэш запроса работает на запросах SELECT SQL_CALC_FOUND_ROWS ...
и хранилит значение, которое возвращено следующим
SELECT FOUND_ROWS()
.
FOUND_ROWS()
возвращает правильное значение, даже если
предыдущий запрос был принесен из кэша, потому что число найденных строк
также сохранено в кэше. SELECT FOUND_ROWS()
не может кэшироваться.
Готовые запросы, которые сделаны, используя протокол двоичной синхронной
передачи данных
mysql_stmt_prepare()
и
mysql_stmt_execute()
(см. раздел 25.8.8),
подвергаются ограничениям на кэширование. Сравнение с запросами в кэше
запроса основано на тексте запроса после расширения маркеров параметра
?
. Запрос сравнен только с другими кэшируемыми запросами,
которые были выполнены, используя протокол двоичной синхронной передачи
данных. Таким образом, в целях кэша запроса подготовленные запросы с
использованием протокола двоичной синхронной передачи данных отличны от
готовых запросов, сделанных, используя текстовый протокол (см.
раздел 14.5).
Запрос не может кэшироваться, если он содержит какую-либо из функций, показанных в следующей таблице.
Запрос также не кэшируется при этих условиях:
Это обращается к определяемым пользователем функциям (UDF) или сохраненным функциям.
mysql
,
INFORMATION_SCHEMA
или performance_schema
.SELECT ... LOCK IN SHARE MODE SELECT ... FOR UPDATE SELECT ... INTO OUTFILE ... SELECT ... INTO DUMPFILE ... SELECT * FROM ... WHERE autoincrement_col IS NULL
Последняя форма не кэшируется, потому что она используется в качестве обходного решения ODBC для того, чтобы получить последнее ID вставки. См. главу 25.
Запросы в пределах транзакций с применением уровня изоляции
SERIALIZABLE
также не могут кэшироваться, потому что они используют блокировку LOCK
IN SHARE MODE
.
TEMPORARY
.Два запроса, связанные с кэшем, могут быть определены в
SELECT
:
SQL_CACHE
Результат запроса кэшируется, если это кэшируемо и значение
query_cache_type
ON
или DEMAND
.
SQL_NO_CACHE
Сервер не использует кэш запроса. Это не проверяет кэш запроса, чтобы видеть, кэшируется ли результат уже, и при этом это не кэширует результат запроса.
Примеры:
SELECT SQL_CACHE id, name FROM customer; SELECT SQL_NO_CACHE id, name FROM customer;
have_query_cache
указывает, доступен ли кэш запроса:
mysql> SHOW VARIABLES LIKE 'have_query_cache'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | have_query_cache | YES | +------------------+-------+
Используя стандартный MySQL, это значение всегда
YES
, даже если кэширование запроса отключено.
Несколько других системных переменных управляют работой кэша запроса.
Они могут быть установлены в файле опции или в командной строке, запуская
mysqld.
Системные переменные кэша запроса имеют имена, которые начинаются с
query_cache_
. Они описаны кратко в
разделе 6.1.5.
Чтобы установить размер кэша запроса, установите
query_cache_size
. Установка этого к 0 отключает кэш запроса, что делает установку
query_cache_type=0
. По умолчанию кэш запроса отключен. Это достигнуто, используя размер
значения по умолчанию 1M со значением по умолчанию для
query_cache_type
0.
Чтобы уменьшить издержки значительно, также запустите сервер с
query_cache_type=0
, если Вы не будете использовать кэш запроса.
Используя Windows Configuration Wizard, чтобы установить или
сконфигурировать MySQL, значение по умолчанию для
query_cache_size
будет сконфигурировано автоматически для Вас, основываясь на различных
доступных типах конфигурации. Используя Windows Configuration Wizard,
кэш запроса может быть включен (то есть, установлен в ненулевое значение),
из-за выбранной конфигурации. Кэшем запроса также управляет установка
query_cache_type
. Проверьте значения этих переменных в my.ini
.
Когда Вы устанавливаете
query_cache_size
к ненулевому значению, имейте в виду, что
кэш запроса нуждается в минимальном размере приблизительно 40 КБ, чтобы
выделить его структуры. Точный размер зависит от системной архитектуры.
Если Вы устанавливаете слишком маленькое значение, Вы получите
предупреждение, как в этом примере:
mysql> SET GLOBAL query_cache_size = 40000; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Warning Code: 1282 Message: Query cache failed to set size 39936; new query cache size is 0 mysql> SET GLOBAL query_cache_size = 41984; Query OK, 0 rows affected (0.00 sec) mysql> SHOW VARIABLES LIKE 'query_cache_size'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | query_cache_size | 41984 | +------------------+-------+
Для кэша запроса, чтобы фактически быть в состоянии содержать любые результаты запроса, его размер должен быть установлен больше:
mysql> SET GLOBAL query_cache_size = 1000000; Query OK, 0 rows affected (0.04 sec) mysql> SHOW VARIABLES LIKE 'query_cache_size'; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | query_cache_size | 999424 | +------------------+--------+ 1 row in set (0.00 sec)
query_cache_size
выравнивается к самому близкому 1024-байтовому блоку. Значение может
поэтому отличаться от значения, которое Вы назначаете.
Если размер кэша запроса больше 0,
query_cache_type
влияет, как она работает. Эта переменная может быть
установлена в следующие значения:
0
или OFF
предотвращает кэширование или извлечение кэшируемых результатов.
1
или ON
позволяет кэшировать кроме тех запросов, которые начинаются с
SELECT SQL_NO_CACHE
.2
или DEMAND
включает
кэширование причин только тех запросов, которые начинаются с
SELECT SQL_CACHE
.Если query_cache_size
= 0, Вы должны также установить
query_cache_type
в 0. В этом случае сервер не приобретает mutex для кэша запроса вообще,
что означает, что кэш запроса не может быть включен во время выполнения.
Установка GLOBAL
query_cache_type
определяет поведение кэша запроса для всех
клиентов, которые соединяются после того, как изменение произведено.
Отдельные клиенты могут управлять поведением кэша для своего собственного
соединения, устанавливая SESSION
query_cache_type
. Например, клиент может отключить использование кэша запроса для его
собственных запросов так:
mysql> SET SESSION query_cache_type = OFF;
Если Вы устанавливаете
query_cache_type
при запуске сервера (а не во время выполнения с
помощью SET
),
только числовые значения разрешены.
Чтобы управлять максимальным размером отдельных результатов запроса,
которые могут кэшироваться,
query_cache_limit
. Значение по умолчанию составляет 1 МБ.
Бойтесь устанавливать слишком большой размер кэша. Из-за потребности потоков в блокировке кэша во время обновлений, Вы можете видеть проблемы блокировки с очень большим кэшем.
Вы можете установить максимальный размер, который может быть определен для
кэша запроса во время выполнения с помощью
SET
при использовании
--maximum-query_cache_size=
в
командной строке или в конфигурационном файле.32M
Когда запрос должен кэшироваться, его результат (данные, посланные
клиенту), сохранен в кэше запроса во время извлечения результата. Поэтому
данные обычно не обрабатываются в одном большом куске. Кэш запроса выделяет
блоки для того, чтобы хранить эти данные по требованию, так что когда один
блок заполнен, новый блок выделен. Поскольку работа распределения памяти
является дорогостоящей, кэш запроса выделяет блоки с минимальным размером,
заданным
query_cache_min_res_unit
. Когда запрос выполнен, последний блок
результата обрезан к фактическому размеру данных так, чтобы неиспользованная
память была освобождена. В зависимости от типов запросов, которые Ваш сервер
выполняет, Вы могли бы счесть полезным настроить значение
query_cache_min_res_unit
:
Значение по умолчанию
query_cache_min_res_unit
4KB.
Это должно быть достаточным для большинства случаев.
query_cache_min_res_unit
. Число свободных блоков и запросов,
удаленных из-за сокращения, дано значениями
Qcache_free_blocks
и
Qcache_lowmem_prunes
.
Qcache_total_blocks
и
Qcache_queries_in_cache
), Вы можете увеличить работу, увеличивая
query_cache_min_res_unit
. Однако, делайте все возможное не сделать
это слишком большим (см. предыдущий элемент).Чтобы проверить, присутствует ли кэш запроса в Вашем сервере MySQL, используйте следующий запрос:
mysql> SHOW VARIABLES LIKE 'have_query_cache'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | have_query_cache | YES | +------------------+-------+
Вы можете дефрагментировать кэш запроса, чтобы лучше использовать его
память с FLUSH QUERY CACHE
.
Запрос не удаляет запросы из кэша.
RESET QUERY CACHE
удаляет все результаты запроса из кэша.
FLUSH TABLES
также делает это.
Чтобы контролировать работу кэша запроса, надо использовать
SHOW STATUS
, чтобы
просмотреть переменные состояния кэша:
mysql> SHOW STATUS LIKE 'Qcache%'; +-------------------------+--------+ | Variable_name | Value | +-------------------------+--------+ | Qcache_free_blocks | 36 | | Qcache_free_memory | 138488 | | Qcache_hits | 79570 | | Qcache_inserts | 27087 | | Qcache_lowmem_prunes | 3114 | | Qcache_not_cached | 22989 | | Qcache_queries_in_cache | 415 | | Qcache_total_blocks | 912 | +-------------------------+--------+
Описания каждой из этих переменных даны в разделе 6.1.7. Некоторое использование для них описано здесь.
Общее количество запросов SELECT
дано этой формулой:
Com_select + Qcache_hits + queries with errors found by parser
Com_select
дано этой формулой:
Qcache_inserts + Qcache_not_cached + queries with errors found during the column-privileges check
Кэш запроса использует блоки переменной длины, таким образом,
Qcache_total_blocks
и
Qcache_free_blocks
может указать на фрагментацию кэш-памяти.
После FLUSH QUERY CACHE
только единственный свободный блок остается.
Каждый кэшируемый запрос требует минимум двух блоков (один для текста запроса и один или больше для результатов запроса). Кроме того, каждая таблица, которая используется запросом, требует одного блока. Однако, если два или больше запроса используют ту же самую таблицу, только один табличный блок должен быть выделен.
Информация, предоставленная
Qcache_lowmem_prunes
, может помочь Вам настроить размер кэша
запроса. Это считает число запросов, которые были удалены из кэша
для того, чтобы кэшировать новые запросы. Кэш запроса использует стратегию
LRU, чтобы решить, который запрос удалить из кэша. Настройка информации дана
в разделе 9.10.3.3.
Для определенных запросов, которые клиент мог бы выполнить многократно во время сеанса, сервер преобразовывает запрос к внутренней структуре и кэширует структуру, которая будет использоваться во время выполнения. Кэширование позволяет серверу выступить более эффективно, потому что это избегает издержек перепреобразования запроса, которое должно быть необходимо снова во время сеанса. Преобразование и кэширование происходят для этих запросов:
Готовые запросы, обработанные на уровне SQL (используя
(PREPARE
)
и обработанные с использование двоичного протокола клиент-сервер (используя
mysql_stmt_prepare()
C API). Переменная
max_prepared_stmt_count
управляет общим количеством
запросов, кэшируемых сервером. Это сумма числа готовых запросов всех сеансов.
stored_program_cache
указывает на приблизительное количество сохраненных программ в
кэше на сеанс.Сервер поддерживает кэши для готовых запросов и сохраненных программ на основе сеанса. Запросы, кэшируемые для одного сеанса, недоступны для других сеансов. Когда сеанс заканчивается, сервер отказывается от любых запросов, кэшируемых для него.
Когда сервер использует кэшируемую внутреннюю структуру запроса,
он должен заботиться, что структура современна. Изменения метаданных могут
произойти для объекта, используемого запросом, вызывая несоответствие между
текущим определением объекта и определением как представлено во внутренней
структуре запроса. Изменения метаданных происходят для запросов DDL, таких
как те, которые создают, удаляют, изменяют, переименовывают или усекают
таблицы, или которые анализируют, оптимизируют или ремонтируют таблицы.
Табличные изменения контента (например, с
INSERT
или
UPDATE
) не изменяют метаданные и
не делают SELECT
.
Вот иллюстрация проблемы. Предположите, что клиент готовит этот запрос:
PREPARE s1 FROM 'SELECT * FROM t1';
SELECT *
расширяется во внутренней структуре до списка
столбцов в таблице. Если набор столбцов в таблице изменен с ALTER
TABLE
, готовый запрос терет актуальность. Если сервер не обнаруживает
это изменение в следующий раз, когда клиент выполняет s1
,
готовый запрос возвратит неправильные результаты.
Чтобы избегать проблем, вызванных метаданными,
сервер обнаруживает эти изменения и автоматически повторно готовит запрос,
когда это затем выполнено. Таким образом, сервер повторно разбирает запрос и
восстанавливает внутреннюю структуру. Перепарсинг также происходит
после того, как таблицы или представления, на которые ссылаются, сброшены
из табличного кэша определения неявно, чтобы создать место для новых записей
в кэше, или явно из-за FLUSH TABLES
.
Точно так же, если изменения происходят с объектами, используемыми сохраненной программой, сервер перепарсит затронутые запросы в пределах программы.
Сервер также обнаруживает изменения метаданных для объектов в выражениях.
Они могли бы использоваться в запросах, определенных для сохраненных
программ, например, DECLARE CURSOR
, или
запросы управления потоками, например,
IF
,
CASE
и
RETURN
.
Чтобы избежать повторно разбирать все сохраненные программы, сервер повторно разбирает затронутые запросы или выражения в пределах программы только когда надо. Примеры:
Предположите, что метаданные для таблицы или представления
изменены. Перепарсинг происходит для SELECT *
в пределах программы, которая получает доступ к таблице или представлению,
но не для SELECT *
, который не получает доступ к
таблице или представлению.
CASE
:
CASEcase_expr
WHENwhen_expr1
... WHENwhen_expr2
... WHENwhen_expr3
... ... END CASE
Если изменение метаданных затрагивает только WHEN
, то выражение повторно разобрано.
when_expr3
case_expr
и другие WHEN
повторно не разобраны.
Перепарсинг использует базу данных значения по умолчанию и режим SQL, которые были в действительности для оригинального преобразования во внутреннюю форму.
Сервер пытается повторно разобрать до трех раз. Ошибка происходит, если все попытки терпят неудачу.
Перепарсинг является автоматическим.
Для готовых запросов
Com_stmt_reprepare
отслеживает число переприготовлений.
MySQL управляет контентом для таблиц с использованием блокировки:
Внутренняя блокировка выполнена в пределах сервера MySQL непосредственно, чтобы управлять контентом для таблицы многократными потоками. Этот тип блокировки является внутренним, потому что это выполнено полностью сервером и не вовлекает никакие другие программы. См. раздел 9.11.1.
MyISAM
, чтобы
скоординировать между собой, какая программа может получить доступ к
таблицам. См. раздел 9.11.5.
Этот раздел обсуждает внутреннюю блокировку, то есть, блокировкой, выполненной в пределах сервера MySQL непосредственно, чтобы управлять табличным содержанием многократными сеансами. Этот тип блокировки является внутренним, потому что это выполнено полностью сервером и не вовлекает никакие другие программы. Для того, чтобы блокировать файлы MySQL другими программами, см. раздел 9.11.5.
MySQL использует блокировку на
уровне строки для InnoDB
, чтобы поддержать одновременную
запись многократными сеансами, делая их подходящими для
многопользовательских, очень параллельных приложений OLTP.
Чтобы избежать тупиков,
с многократными параллельными операциями записи на InnoDB
,
приобретите необходимые блокировки в начале транзакции, запросив
SELECT ... FOR UPDATE
для каждой группы строк, которая будет
изменена, даже если запросы изменения данных прибывают позже в транзакцию.
Если транзакции изменяют или блокируют больше, чем одну таблицу, делают
применимые запросы в том же самом порядке в пределах каждой транзакции.
Тупики затрагивают работу вместо того, чтобы представить серьезную ошибку,
потому что InnoDB
автоматически
обнаруживает
условия тупика по умолчанию и откатывает до прежнего уровня одну
из затронутых транзакций.
На системах высокого параллелизма обнаружение тупика может вызвать
замедление, когда многочисленные потоки ждут той же самой блокировки.
Время от времени может быть более эффективно отключить обнаружение тупика и
положиться на настройку
innodb_lock_wait_timeout
для операционной отмены, когда
тупик происходит. Обнаружение тупика может быть отключено, используя опцию
innodb_deadlock_detect
.
Преимущества блокировки на уровне строки:
Меньше блокировки находится в противоречии, когда различные сеансы просят доступ к различным строкам.
MySQL использует блокировку на
уровне таблицы для MyISAM
, MEMORY
и
MERGE
, разрешая только одному сеансу обновить те таблицы за один
раз. Этот уровень блокировки делает эти механизмы хранения более подходящими
для однопользовательских приложений, только для чтения или
чтения главным образом.
Эти механизмы хранения избегают тупиков, всегда прося все необходимые блокировки сразу в начале запроса и всегда блокируя таблицы в том же самом порядке. Проблема состоит в том, что эта стратегия уменьшает параллелизм: другие сеансы, которые хотят изменить таблицу, должны ждать до окончания запроса изменения данных.
Преимущества блокировки на уровне таблицы:
Относительно маленькая требуемая память (блокировка строки требует памяти на строку или группу заблокированных строк).
GROUP BY
на значительной части данных или должны часто просматривать всю таблицу.
MySQL допускает, что таблица блокируется на запись так:
Если нет блокировок на таблице, поместить блокировку.
MySQL допускает, что таблица блокируется на чтение так:
Если нет блокировки записи на таблице, поместить блокировку на чтение.
Табличным обновлениям дают более высокий приоритет, чем табличным
извлечениям. Поэтому, когда блокировка снята, блокировка сделана доступной
для запросов в очереди записи, а уже потом в очереди чтения. Это гарантирует,
что обновления таблицы не виснут, даже когда там
сложный SELECT
.
Однако, если есть много обновлений для таблицы,
SELECT
ждут, пока нет больше обновлений.
См. раздел 9.11.2.
Вы можете проанализировать табличные блокировки на своей системе, проверяя
переменные
Table_locks_immediate
и
Table_locks_waited
, которые указывают на число раз, которое запросы табличных
блокировок можно было немедленно предоставить и число раз, которое нужно
было ждать, соответственно:
mysql> SHOW STATUS LIKE 'Table%'; +-----------------------+---------+ | Variable_name | Value | +-----------------------+---------+ | Table_locks_immediate | 1151552 | | Table_locks_waited | 15324 | +-----------------------+---------+
Таблицы блокировки Performance Schema также предоставляют информацию о блокировке. См. раздел 23.9.12.
MyISAM
поддерживает параллельные вставки, чтобы уменьшить
задержки: если таблица MyISAM
не имеет никаких свободных блоков
в середине файла с данными, строки всегда вставляются в конце файла с
данными. В этом случае Вы можете свободно смешать параллельный
INSERT
и
SELECT
для MyISAM
без блокировок. Таким образом, Вы можете вставить строки в таблицу
MyISAM
в то же самое время, когда другие клиенты читают из нее.
Промежутки могут следовать из удаленных или обновленных строк в середине
таблицы. Если есть промежутки, параллельные вставки отключены, но включены
снова автоматически, когда все промежутки были заполнены новыми данными.
Чтобы управлять этим поведением, используйте переменную
concurrent_insert
, см. раздел 9.11.3.
Если Вы приобретаете табличную блокировку явно с
LOCK TABLES
,
Вы можете просить READ LOCAL
вместо READ
, чтобы
позволить другим сеансам выполнить параллельные вставки в то время, как
Вы заблокировали таблицу.
Выполнить много INSERT
и
SELECT
на таблице t1
, когда параллельные вставки невозможны, Вы можете
вставив строки во временную таблицу temp_t1
и обновив реальную таблицу строками из временной таблицы:
mysql> LOCK TABLES t1 WRITE, temp_t1 WRITE; mysql> INSERT INTO t1 SELECT * FROM temp_t1; mysql> DELETE FROM temp_t1; mysql> UNLOCK TABLES;
Вообще, табличные блокировки превосходят блокировки на уровне строки в следующих случаях:
Большинство запросов для таблицы это чтение.
UPDATEtbl_name
SETcolumn
=value
WHEREunique_key_col
=key_value
; DELETE FROMtbl_name
WHEREunique_key_col
=key_value
;
SELECT
с параллельным
INSERT
и очень немногими
UPDATE
или
DELETE
.GROUP BY
на всей таблице без любых записей.С высокоуровневыми блокировками Вы можете более легко настроить приложения, поддерживая блокировки различных типов, потому что издержки блокировки меньше, чем для блокировок на уровне строки.
Опции кроме блокировки на уровне строки:
Versioning (такой, как используемый в MySQL для параллельных вставок), где возможно иметь много чтений с одной записью. Это означает, что база данных или таблица поддерживают другие представления для данных в зависимости от того, когда доступ начинается. Другие распространенные слова для этого time travel, copy on write или copy on demand.
GET_LOCK()
и
RELEASE_LOCK()
в
MySQL. Это консультативные блокировки, таким образом, они работают только с
приложениями, которые сотрудничают друг с другом. См.
раздел 13.18.InnoDB
использует блокировку на уровне строки так, чтобы
многократные сеансы и приложения могли читать из и писать в ту же самую
таблицу одновременно, не заставляя друг друга ждать. Для этого механизма
хранения избегайте использования LOCK
TABLES
, потому что это не предлагает дополнительной защиты, но
вместо этого уменьшает параллелизм. Автоматическая блокировка на уровне
строки делает эти таблицы подходящими для Ваших самых занятых баз данных с
Вашими самыми важными данными, также упрощая логику приложения, так как Вы не
должны заблокировать таблицы.
MySQL использует табличную блокировку (вместо страницы, строки или
блокировки столбца) для всех механизмов хранения, кроме InnoDB
.
У операций самой блокировки нет многих издержек. Но потому что только один
сеанс может писать таблицу в любой момент, для лучшей работы с этими другими
механизмами хранения, используйте их прежде всего для таблиц, которые
запрашиваются часто и редко обновляются.
Выбирая, создать ли таблицу InnoDB
, имейте в виду следующие
недостатки табличной блокировки:
Табличная блокировка позволяет многим сеансам читать из таблицы в то же самое время, но если сеанс хочет писать таблицу, это должно сначала получить эксклюзивный доступ, означая, что этому, возможно, придется ждать других сеансов, чтобы закончить с таблицей сначала. Во время обновления должны ждать все другие сеансы, которые хотят получить доступ к этой особой таблице, пока обновление не сделано.
SELECT
, который занимает много
времени, препятствует тому, чтобы другие сеансы обновили таблицу тем
временем, заставляя другие сеансы казаться медленными или безразличными.
В то время как сеанс ждет, чтобы получить эксклюзивный доступ к таблице для
обновлений, другие сеансы, создавшие запрос
SELECT
,
будут стоять в очереди позади этого, уменьшая параллелизм даже для сеансов
только для чтения.Следующие элементы описывают некоторые способы избежать или уменьшить проблемы, вызванные табличной блокировкой:
Рассмотрите переключение таблицы к InnoDB
с помощью
CREATE TABLE ... ENGINE=INNODB
при создании или через
ALTER TABLE ... ENGINE=INNODB
для существующей таблицы. См.
главу 16.
SELECT
, чтобы
работать быстрее, чтобы они заблокировали таблицы в течение более короткого
времени. Вам, возможно, придется создать некоторые сводные таблицы,
чтобы сделать это.
--low-priority-updates
. Для механизмов хранения, которые
используют только блокировку на уровне таблицы (MyISAM
,
MEMORY
и MERGE
), это дает всем запросам обновления
(изменения) таблицы более низкий приоритет, чем
SELECT
. В этом случае второй
SELECT
в предыдущем скрипте выполнился бы перед
UPDATE
и не ждал бы
завершения первого SELECT
.low_priority_updates
в 1.INSERT
, UPDATE
или
DELETE
более низкий приоритет, используйте параметр LOW_PRIORITY
.SELECT
более высокий приоритет, используйте параметр HIGH_PRIORITY
, см.
раздел 14.2.9.max_write_lock_count
, чтобы вынудить MySQL временно поднять приоритет всех
SELECT
, которые ждут таблицы после
определенного числа вставок к таблице. Это разрешает блокировки
READ
после определенного числа блокировок WRITE
.
INSERT
в сочетании с
SELECT
, рассмотрите переключение на
MyISAM
, которые поддерживают параллельные
SELECT
и
INSERT
, см.
раздел 9.11.3.SELECT
в сочетании с
DELETE
, опция LIMIT
в
DELETE
может помочь, см.
раздел 14.2.2.SQL_BUFFER_RESULT
с
SELECT
. Это может помочь сделать
продолжительность табличных блокировок короче. См.
раздел 14.2.9.mysys/thr_lock.c
, чтобы
использовать единственную очередь. В этом случае у блокировок чтения и записи
будет тот же самый приоритет, который мог бы помочь некоторым приложениям.
MyISAM
поддерживает параллельные вставки, чтобы уменьшить
проблемы для данной таблицы: если таблица MyISAM
не имеет
промежутков в файле с данными (удаленные строки в середине),
INSERT
может быть выполнено, чтобы
добавить строки в конец таблицы в то же самое время, когда
SELECT
читает строки из таблицы.
Если там есть многократные INSERT
,
они стоят в очереди и выполнены в последовательности, одновременно с
SELECT
. Результаты параллельного
INSERT
, возможно, не
будут видимы немедленно.
concurrent_insert
может быть установлена, чтобы изменить обработку. По умолчанию
переменная установлена в AUTO
(1), и параллельные вставки
обработаны, как только что описано. Если
concurrent_insert
установлена в NEVER
(0), параллельные вставки отключены.
Если переменная установлена в ALWAYS
(2), параллельные вставки в
конце таблицы разрешены даже для таблиц, которые удалили строки. См. также
описание
concurrent_insert
.
Если Вы используете двоичный журнал, параллельные вставки преобразованы в
нормальные вставки для CREATE ... SELECT
или
INSERT ... SELECT
.
Это сделано, чтобы гарантировать, что Вы можете обновить точную копию своих
таблиц, применяя журнал во время резервной работы. См.
раздел 6.4.4.
Кроме того, для тех запросов блокировка чтения добавлена, таким образом,
вставки в эту таблицу заблокированы. Эффект состоит в том, что параллельные
вставки для той таблицы должны ждать также.
С LOAD DATA INFILE
,
если Вы определяете CONCURRENT
с MyISAM
, которая
удовлетворяет условие для параллельных вставок (то есть, это не содержит
свободных блоков в середине), другие сеансы может получить данные от таблицы
в то время, как работает LOAD DATA
. Использование опции CONCURRENT
затрагивает исполнение
LOAD DATA
, даже если никакой
другой сеанс не использует таблицу в то же самое время.
Если Вы определяете HIGH_PRIORITY
, это переопределяет эффект
--low-priority-updates
, если сервер был запущен с этой опцией.
Это также заставляет параллельные вставки не использоваться.
Для LOCK TABLE
различие между READ LOCAL
и READ
в том, что
READ LOCAL
не конфликтует с
INSERT
(параллельные вставки),
в то время, как блокировка проводится. Однако, это не может использоваться,
если Вы собираетесь управлять процессами использования базы данных, внешними
к серверу в то время, как Вы держите блокировку.
MySQL применяет блокировку метаданных, чтобы управлять параллельным доступом к объектам базы данных и гарантировать последовательность данных. Блокировка метаданных применяется не только к таблицам, но также и к схемам, сохраненным программам (процедуры, функции, триггеры и намеченные события) и табличным пространствам.
Блокировка метаданных действительно вовлекает некоторые издержки, которые растут, когда объем запроса увеличивается. Издержки метаданных увеличиваются больше, когда многократные запросы пытаются получить доступ к тем же самым объектам.
Блокировка метаданных не замена для табличного кэша, и его mutexes и
блокировки отличаются от LOCK_open
mutex.
Следующее обсуждение предоставляет некоторую информацию о том, как
блокировка метаданных работает.
Чтобы гарантировать последовательность транзакции, сервер не должен разрешить одному сеансу выполнять запрос языка определения данных (DDL) о таблице, которая используется в незаконченной явно или неявно запущенной транзакции в другом сеансе. Сервер достигает, это, приобретая блокировки метаданных на таблицы, используемые в пределах транзакции и снимая их, когда транзакция заканчивается. Блокировка метаданных предотвращает изменения структуры таблицы. У этого подхода блокировки есть значение, что таблица, которая используется транзакцией в пределах одного сеанса, не может использоваться в запросах DDL другими сеансами, пока транзакция не заканчивается.
Этот принцип применяется не только к транзакционным таблицам, но также и
к нетранзакционным таблицам. Предположите, что сеанс начинает транзакцию,
которая использует транзакционную таблицу t
и нетранзакционную таблицу nt
следующим образом:
START TRANSACTION; SELECT * FROM t; SELECT * FROM nt;
Сервер держит блокировки метаданные обоих t
и
nt
, пока транзакция не заканчивается. Если другой сеанс делает
попытку DDL или блокировки записи на любой таблице, это блокируется до
выпуска блокировки метаданных. Например, второй сеанс блокируется, если он
делает попытку какой-либо из этих операций:
DROP TABLE t; ALTER TABLE t ...; DROP TABLE nt; ALTER TABLE nt ...; LOCK TABLE t ... WRITE;
То же самое поведение применимо для
LOCK TABLES ... READ
.
Таким образом, явно или неявно запуск транзакции, которая обновляет
любую таблицу (транзакционную или нетранзакционную) заблокирует и
LOCK TABLES ... READ
для той таблицы.
Если сервер приобретает блокировки метаданных на запрос, который синтаксически допустимо, но терпит неудачу во время выполнения, он не выпускает блокировки рано. Выпуск блокировки все еще задержан до конца транзакции, потому что неудавшийся запрос записан в двоичный журнал, а блокировки защищают последовательность журнала.
В режиме autocommit каждый запрос в действительности полная транзакция, таким образом, блокировки метаданных, приобретенные на запрос, проводятся только до конца запроса.
Блокировки метаданных, приобретенные во время
PREPARE
, выпущены
как только запрос был подготовлен, даже если подготовка происходит в пределах
транзакции многократного запроса.
Внешняя блокировка это использование блокировки файловой системы, чтобы
управлять сдержимым базы данных
MyISAM
многократными процессами. Внешняя блокировка
используется в ситуациях, где единственный процесс, такой как сервер MySQL,
как может предполагаться, не является единственным процессом, который требует
доступа к таблицам. Вот некоторые примеры:
Если Вы выполняете много серверов, которые используют тот же самый каталог базы данных (не рекомендуется так делать!), каждому серверу нужно было включить внешнюю блокировку.
MyISAM
, Вы должны гарантировать, что сервер не работает, или что сервер
включил внешнюю блокировку так, чтобы это заблокировало табличные файлы по
мере необходимости, чтобы скоординировать с
myisamchk
для доступа к таблицам. То же самое верно для использования
myisampack
.
Если сервер выполнен с внешней включенной блокировкой, Вы можете использовать myisamchk в любое время для операций проверки таблицы. В этом случае, если сервер попытается обновить таблицу, которую использует myisamchk, сервер будет ждать, пока myisamchk закончит работу.
Если Вы используете myisamchk для записи, например, для ремонта таблиц, Вы должны всегда гарантировать, что mysqld не использует таблицу. Если Вы не останавливаете mysqld, по крайней мере, сделайте mysqladmin flush-tables перед myisamchk. Если сервер и myisamchk будут писать таблицы вместе, ничем хорошим это не кончится.
С внешней блокировкой каждый процесс, который требует доступа к таблице, приобретает блокировку файловой системы на табличные файлы прежде, чем продолжить получать доступ к таблице. Если все необходимые блокировки не могут быть приобретены, процесс заблокирован на доступ к таблице, пока блокировки не могут быть получены (после того, как процесс, который в настоящее время держит блокировки, освобождает их).
Внешняя блокировка затрагивает работу сервера, потому что сервер должен иногда ждать других процессов прежде, чем это сможет получить доступ к таблицам.
Внешняя блокировка является ненужной, если Вы выполняете единственный сервер, чтобы получить доступ к каталогу определенных данных (что является обычным случаем), и если никакие другие программы, такие как myisamchk не должны изменить таблицы, в то время как сервер работает. Если Вы только читаете таблицы другими программами, внешняя блокировка не требуется, хотя myisamchk мог бы сообщить о предупреждениях, если сервер изменяет таблицы, в то время как myisamchk читает их.
С внешней отключенной блокировкой, чтобы использовать
myisamchk,
Вы должны или остановить сервер, в то время как
myisamchk
выполняется или иначе блокировать и сбросить таблицы прежде, чем выполнить
myisamchk.
См. System Factors. Чтобы избежать этого требования,
используйте CHECK TABLE
и
REPAIR TABLE
для
MyISAM
.
Для mysqld
внешней блокировкой управляет значение
skip_external_locking
. Когда эта переменная включена, внешняя блокировка отключена и
наоборот. Внешняя блокировка отключена по умолчанию.
Использованием внешней блокировки можно управлять при запуске сервера при
использовании опции
--external-locking
или
--skip-external-locking
.
Если Вы действительно используете внешнюю опцию блокировки, чтобы включить
обновления MyISAM
от многих процессов MySQL, Вы должны гарантировать, что
следующие условия удовлетворены:
Не используйте кэш запроса для запросов, которые используют таблицы, которые обновлены другим процессом.
--delay-key-write=ALL
или используйте опцию таблицы
DELAY_KEY_WRITE=1
для любых совместно используемых таблиц.
Иначе может произойти повреждение индекса.Самый легкий способ удовлетворить эти условия состоит в том, чтобы
всегда использовать
--external-locking
с
--delay-key-write=OFF
и
--query-cache-size=0
. Это не сделано по умолчанию, потому что во многих установках
полезно иметь смесь предыдущих опций.
Этот раздел обсуждает методы оптимизации для сервера базы данных, прежде всего имея дело с системной конфигурацией вместо того, чтобы настроить запросы SQL. Информация в этом разделе является подходящей для DBA, кто хочет гарантировать работу и масштабируемость через серверы, которыми они управляют, для разработчиков, создающих скрипт установки, которые включают базы данных, и выполнение MySQL непосредственно для развития, тестирования и так далее для тех, кто хочет максимизировать их собственную производительность.
Этот раздел описывает способы сконфигурировать устройства хранения данных,
когда Вы можете посвятить больше и более быстрые аппаратные средства хранения
серверу базы данных. Для информации об оптимизации InnoDB
, чтобы
улучшить работу ввода/вывода см.
раздел 9.5.8.
Дисковые поиски огромное исполнительное узкое место. Эта проблема становится более очевидной, когда объем данных начинает становиться настолько большим, что эффективное кэширование становится невозможным. Для больших баз данных, где Вы получаете доступ к данным более или менее беспорядочно, Вы можете убедиться, что нуждаетесь по крайней мере в одном диске для чтения и нескольких для записи. Чтобы минимизировать эту проблему, применяйте диски с низким временем поиска.
Символические ссылки
Это означает для MyISAM
, что Вы создаете
символьную ссылку на индексный файл и файлы с данными от их обычного
местоположения в каталоге данных к другому диску. Это делает времена
поиска и чтения лучше, предполагая, что диск не используется в других целях
также. См. раздел 9.12.2.
Символические ссылки не поддержаны для использования с
InnoDB
. Однако, Вы можете создать табличное пространство
file-per-table
в местоположении за пределами каталога данных MySQL, используя параметр
DATA DIRECTORY =
в absolute_path_to_directory
CREATE TABLE
.
См. раздел 16.7.5.
Общие табличные пространства
могут также быть созданы в местоположении за пределами каталога данных
MySQL. Для получения дополнительной информации см.
раздел 16.7.9.
Striping подразумевает, что Вы имеете много дисков и помещаете первый блок
на первый диск, второй блок на второй диск и N
-й блок
на диск (
). Это означает, что если Ваш нормальный размер данных меньше,
чем размер полосы (или отлично выровнен), Вы получаете намного лучшую работу.
Striping очень зависит от операционной системы и размера полосы, так
определите эффективность своего приложения с различными размерами полосы. См.
раздел 9.13.2.N
MOD number_of_disks
Скорость для striping очень зависит от параметров. В зависимости от того, как Вы устанавливаете параметры и число дисков, Вы можете измерить различия на порядки величин. Вы должны оптимизировать для случайного или последовательного доступа.
N
дисках
для хранения N
блоков данных. Это, вероятно, наилучший
вариант, если у Вас есть деньги для него. Однако, Вам, вероятно, также
придется вложить капитал в некоторое программное обеспечение управления,
чтобы обработать это эффективно.N
.
RAID N
может быть проблемой, если Вы имеете, много
записей из-за времени, требуемого, чтобы обновить биты четности.Если Вы не должны знать, когда файлы были в последний раз использованы,
Вы можете установить свои файловые системы с опцией -o noatime
.
Это пропускает обновления последнего времени доступа в inodes
на файловой системе.
На многих операционных системах Вы можете установить файловую систему,
которая будет обновлена асинхронно, устанавливая это с опцией -o
async
. Если Ваш компьютер разумно устойчив, это должно дать Вам лучшую
работу, не жертвуя слишком большой надежностью.
Этот флаг идет по умолчанию в Linux.
Вы можете переместить базы данных или таблицы от каталога базы данных в другое место и заменить их символическими ссылками к новым местоположениям. Вы могли бы хотеть сделать это, например, чтобы переместить базу данных в файловую систему с большим свободным пространством или увеличить скорость Вашей системы, распространяя Ваши таблицы к различным дискам.
Для InnoDB
используйте DATA DIRECTORY
в
CREATE TABLE
вместо символических ссылок, как объяснено в
разделе 16.7.5.
Чтобы определить местоположение Вашего каталога данных, используйте этот запрос:
SHOW VARIABLES LIKE 'datadir';
В Unix надо создать каталог на некотором диске, где у Вас есть свободное место и затем создать мягкую ссылку к этому из каталога данных MySQL.
shell> mkdir /dr1/databases/test
shell> ln -s /dr1/databases/test /path/to/datadir
MySQL не поддерживает соединение одного каталога
к многократным базам данных. Замена каталога базы данных с символической
ссылкой работает, пока Вы не делаете символическую ссылку между базами
данных. Предположите, что у Вас есть база данных db1
в соответствии с каталогом данных MySQL, и затем сделайте символьную ссылку
db2
, которая указывает на db1
:
shell> cd /path/to/datadir
shell> ln -s db1 db2
Результат состоит в том, что любая таблица tbl_a
в
db1
, также является таблицей
tbl_a
в db2
. Если один клиент обновляет
client updates db1.tbl_a
, а другой клиент обновляет
updates db2.tbl_a
, проблемы, вероятно, произойдут.
Символьные ссылки полностью поддержаны только для MyISAM
.
Для файлов, используемых таблицами для других механизмов хранения, Вы можете
получить странные проблемы, если Вы пытаетесь использовать символические
ссылки. Для InnoDB
используйте альтернативный метод,
объясненный в разделе 16.7.5.
Не делайте таблицы символьной ссылки на системах, у которых нет полностью
рабочего вызова realpath()
. Linux и Solaris поддерживают
realpath()
. Чтобы определить, поддерживает ли Ваша система
символические ссылки, проверьте значение переменной
have_symlink
:
SHOW VARIABLES LIKE 'have_symlink';
Обработка символических ссылок для MyISAM
работает следующим образом:
В каталоге данных у Вас всегда есть файлы данных
(.MYD
) и индекса (.MYI
).
Они могут быть перемещены в другое место и заменены в каталоге
данных символьными ссылками.
DATA DIRECTORY
и
INDEX DIRECTORY
в CREATE
TABLE
. См. раздел 14.1.15.
Альтернативно, если mysqld
не работает, эффект может быть достигнут вручную,
используя ln -s в командной строке.
Путь, используемый с DATA
DIRECTORY
и INDEX DIRECTORY
, возможно, не включает
каталог MySQL data
(Bug #32167).
ALTER TABLE
,
OPTIMIZE TABLE
и
REPAIR TABLE
.root
или системный пользователь с доступом на запись к
каталогам базы данных MySQL.
ALTER TABLE ... RENAME
или
RENAME TABLE
и Вы не перемещаете таблицу в другую базу данных,
символьные ссылки в каталоге базы данных переименованы к новым именам,
файл с данными и индексный файл переименованы соответственно.ALTER TABLE
... RENAME
или RENAME TABLE
, чтобы переместить таблицу в другую базу данных, таблица
перемещена в другой каталог базы данных. Если имя таблицы изменилось,
символьные ссылки в новом каталоге базы данных переименованы к новым именам,
файл с данными и индексный файл переименованы соответственно.
--skip-symbolic-links
, чтобы гарантировать, что никто не может
использовать mysqld
, чтобы удалить или переименовать файл за
пределами каталога данных.Эти табличные операции для символьной ссылки не поддержаны:
ALTER TABLE
игнорирует DATA DIRECTORY
и INDEX DIRECTORY
.
В Windows символические ссылки могут использоваться для каталогов базы данных. Это позволяет Вам поместить каталог базы данных в иное место (например, на ином диске), настраивая символическую ссылку. Использование символьных ссылок базы данных в Windows подобно их использованию в Unix, хотя процедура для того, чтобы настроить ссылку отличается.
Предположите, что Вы хотите поместить каталог для названной базы данных
mydb
в D:\data\mydb
.
Чтобы сделать это, создайте символическую ссылку в каталоге данных MySQL,
которая указывает на D:\data\mydb
.
Однако, прежде, чем создать символическую ссылку, удостоверьтесь, что
D:\data\mydb
существует, создавая это в случае необходимости.
Если Вы уже назвали каталог базы данных mydb
в каталоге данных,
переместите это в D:\data
. Иначе символическая ссылка будет
неэффективна. Чтобы избежать проблем, удостоверьтесь, что сервер не работает,
когда Вы перемещаете каталог базы данных.
Windows Vista, Windows Server 2008 и новее имеют родную поддержку символической ссылки, таким образом, Вы можете создать символьную ссылку, используя mklink. Эта команда требует административных привилегий.
Перейдите в каталог данных:
C:\> cd \path\to\datadir
В каталоге данных создайте ссылку mydb
,
которая указывает на местоположение каталога базы данных:
C:\> mklink /d mydb D:\data\mydb
После этого все таблицы в базе данных mydb
создаются в D:\data\mydb
.
MySQL выделяет буферы и кэши, чтобы улучшить исполнение операций базы данных. Конфигурация значения по умолчанию разработана, чтобы позволить MySQL Server запускаться на виртуальной машине, у которой есть приблизительно 512 МБ RAM. Вы можете улучшить работу MySQL, увеличивая значения кэша и связанных с буфером системных переменных. Вы можете также изменить конфигурацию по умолчанию, чтобы выполнить MySQL на системах с ограниченной памятью.
Следующий список описывает некоторые из способов, которыми MySQL использует память. Где применимо, на соответствующие системные переменные ссылаются. Некоторые элементы определены для механизмов хранения.
Буферный пул InnoDB
область памяти, которая
кэширует данные для таблиц, индексов и другие вспомогательные буферы.
Для эффективности большого объема операций буферный пул разделен на
страницы, которые могут потенциально
держать много строк. Для эффективности управления кэшем буферный пул
осуществлен как связанный список страниц, данные, которые редко используются,
удаляются из кэша, используя измененный алгоритм
LRU. См.
раздел 16.6.3.1.
Размер буферного пула важен для системной работы.
Как правило, рекомендуют сконфигурировать
innodb_buffer_pool_size
к 50-75 процентам системной памяти.
InnoDB
выделяет память для всего буферного пула при запуске
сервера. Распределение памяти выполнено malloc()
.
Буферный размер пула определен опцией
innodb_buffer_pool_size
.
innodb_buffer_pool_size
может быть сконфигурирован динамически, в
то время как сервер работает. Для получения дополнительной информации см.
раздел 16.6.3.2.
innodb_buffer_pool_instances
.MyISAM
,
его размер определен
key_buffer_size
. Другие буферы, используемые сервером, выделены,
когда необходимо. См. раздел 6.1.1
.
Для каждой открытой таблицы MyISAM
индексный файл открыт однажды, файл с данными открыт однажды для каждого
рабочего потока. Для каждого параллельного потока выделены: структуры
таблицы, структуры столбца для каждого столбца и буфер размера 3 *
(здесь N
N
это
максимальная длина строки, не учитывая BLOB
). Столбец BLOB
требует пяти-восьми байтов плюс длина данных
BLOB
. MyISAM
поддерживает один дополнительный буфер строки для внутреннего пользования.
Стек (
thread_stack
).
net_buffer_length
).
net_buffer_length
).Буфер соединения и результата начинают с размера, равного
net_buffer_length
, но динамически увеличены до
max_allowed_packet
по мере надобности. Буфер результата сжимается к
net_buffer_length
после каждого запроса SQL. В то время как запрос работает, копия текущей
строки запроса также выделена.
Каждый поток соединения использует память для вычислительных
обзоров запроса (см.
раздел 23.7):
max_digest_length
байт на сеанс.
myisam_use_mmap
может быть установлена в 1, чтобы включить отображение памяти для
всех таблиц MyISAM
.read_buffer_size
).read_rnd_buffer_size
) может быть выделен, чтобы избежать дисковых поисков.InnoDB
, когда оптимизатор читает много строк,
буфер записей может быть выделен, чтобы уменьшить издержки
навигации B-дерева и блокировок.BLOB
,
сохранены на диске.
Если внутренняя временная таблица в памяти становится слишком большой,
MySQL обрабатывает это автоматически, изменяя таблицу в памяти до формата на
диске, обработанного механизмом хранения, определенным
internal_tmp_disk_storage_engine
.
Вы можете увеличить допустимый временный табличный размер как описано в
разделе 9.4.4.
Для таблиц MEMORY
явно составленных с CREATE TABLE
, только
max_heap_table_size
определяет, на сколько таблице разрешают
вырасти, нет никакого преобразования в формат на диске.
BLOB
буфер увеличен динамически, чтобы читать значение
BLOB
.
Если Вы просматриваете таблицу, выделен буфер, столь же большой, как самое
большое значение BLOB
.table_open_cache
, см. раздел 9.4.3.1.
MySQL также требует памяти для табличного кэша определений. Переменная
table_definition_cache
определяет число табличных определений,
которые могут быть сохранены в табличном кэше определений.
Если Вы используете большое количество таблиц, Вы можете создать большой
табличный кэш определений, чтобы ускорить открытие таблиц. Табличный кэш
определений занимает меньше места и не использует описатели файла, в отличие
от табличного кэша.
FLUSH TABLES
или
mysqladmin
flush-tables
закрывают все таблицы, которые не находятся в использовании,
и отмечает все таблицы в использовании, которые будут закрыты, когда в
настоящее время выполняющийся поток заканчивается.
Это наиболее эффективно освобождает память.
FLUSH TABLES
не возвращается, пока все таблицы не закрыты.GRANT
,
CREATE USER
,
CREATE SERVER
и
INSTALL PLUGIN
.
Эта память не освобождена соответствующими
REVOKE
,
DROP USER
,
DROP SERVER
и
UNINSTALL PLUGIN
, таким
образом, для сервера, который выполняет много таких запросов
будет увеличение использования памяти. Эта память может быть освобождена
через FLUSH PRIVILEGES
.
ps и другие системные программы состояния
могут сообщить, что mysqld
использует большую память. Это может быть вызвано
стеками потока на различных адресах памяти. Например, версия Solaris
ps считает неиспользованную память между
стеками как используемую. Чтобы проверить это, проверьте доступную память с
swap -s
. Мы проверяем
mysqld
с несколькими датчиками утечки памяти, таким образом не должно быть
никаких утечек памяти.
Следующий пример демонстрирует, как использовать Performance Schema и sys schema, чтобы отследить использование памяти MySQL.
Большинство инструментовки памяти Performance Schema
отключено по умолчанию. Инструменты могут быть включены, обновляя
столбец ENABLED
в таблице Performance Schema
setup_instruments
. У инструментов памяти есть имена в форме
memory/
, где code_area
/instrument_name
code_area
значение вроде
sql
или innodb
, а
instrument_name
это конкретный инструмент.
Чтобы рассмотреть доступные инструменты памяти MySQL,
запросите таблицу Performance Schema
setup_instruments
. Следующий запрос возвращает сотни инструментов памяти для
всех областей кода.
mysql> SELECT * FROM performance_schema.setup_instruments -> WHERE NAME LIKE '%memory%';
Вы можете сузить результаты, определяя область кода.
Например, Вы можете ограничить результаты InnoDB
, определяя
innodb
как область кода.
mysql> SELECT * FROM performance_schema.setup_instruments -> WHERE NAME LIKE '%memory/innodb%'; +-------------------------------------------+---------+-------+ | NAME | ENABLED | TIMED | +-------------------------------------------+---------+-------+ | memory/innodb/adaptive hash index | NO | NO | | memory/innodb/buf_buf_pool | NO | NO | | memory/innodb/dict_stats_bg_recalc_pool_t | NO | NO | | memory/innodb/dict_stats_index_map_t | NO | NO | | memory/innodb/dict_stats_n_diff_on_level | NO | NO | | memory/innodb/other | NO | NO | | memory/innodb/row_log_buf | NO | NO | | memory/innodb/row_merge_sort | NO | NO | | memory/innodb/std | NO | NO | | memory/innodb/trx_sys_t::rw_trx_ids | NO | NO | ...
В зависимости от Вашей установки MySQL, области кода могут включать
performance_schema
,
sql
, client
,
innodb
, myisam
,
csv
, memory
, blackhole
,
archive
, partition
и другие.
performance-schema-instrument
к своему конфигурационному файлу
MySQL. Например, чтобы включить все инструменты памяти, добавьте это правило
к своему конфигурационному файлу и перезапустите сервер:
performance-schema-instrument='memory/%=COUNTED'
Включение инструментов памяти при запуске гарантирует, что распределения памяти, которые происходят при запуске, посчитаны.
После перезапуска сервера столбец
ENABLED
таблицы Performance Schema
setup_instruments
должен сообщить YES
для инструментов памяти, которые Вы
включали. Столбец TIMED
в таблице
setup_instruments
проигнорирован для инструментов памяти, потому что операции
памяти не рассчитаны.
mysql> SELECT * FROM performance_schema.setup_instruments -> WHERE NAME LIKE '%memory/innodb%'; +-------------------------------------------+---------+-------+ | NAME | ENABLED | TIMED | +-------------------------------------------+---------+-------+ | memory/innodb/adaptive hash index | NO | NO | | memory/innodb/buf_buf_pool | NO | NO | | memory/innodb/dict_stats_bg_recalc_pool_t | NO | NO | | memory/innodb/dict_stats_index_map_t | NO | NO | | memory/innodb/dict_stats_n_diff_on_level | NO | NO | | memory/innodb/other | NO | NO | | memory/innodb/row_log_buf | NO | NO | | memory/innodb/row_merge_sort | NO | NO | | memory/innodb/std | NO | NO | | memory/innodb/trx_sys_t::rw_trx_ids | NO | NO | ...
В этом примере инструментальные данные о памяти запрошены в
таблице Performance Schema
memory_summary_global_by_event_name
, которая суммирует
данные EVENT_NAME
. EVENT_NAME
это имя инструмента.
Следующий запрос возвращает данные о памяти для буферного пула
InnoDB
. См.
раздел 23.9.15.9.
mysql> SELECT * FROM performance_schema.memory_summary_global_by_event_name -> WHERE EVENT_NAME LIKE 'memory/innodb/buf_buf_pool'\G EVENT_NAME: memory/innodb/buf_buf_pool COUNT_ALLOC: 1 COUNT_FREE: 0 SUM_NUMBER_OF_BYTES_ALLOC: 137428992 SUM_NUMBER_OF_BYTES_FREE: 0 LOW_COUNT_USED: 0 CURRENT_COUNT_USED: 1 HIGH_COUNT_USED: 1 LOW_NUMBER_OF_BYTES_USED: 0 CURRENT_NUMBER_OF_BYTES_USED: 137428992 HIGH_NUMBER_OF_BYTES_USED: 137428992
Те же самые основные данные могут быть запрошены, используя схему
sys
, таблицу
memory_global_by_current_bytes
,
которая показывает текущее использование памяти в пределах сервера глобально.
mysql> SELECT * FROM sys.memory_global_by_current_bytes -> WHERE event_name LIKE 'memory/innodb/buf_buf_pool'\G *************************** 1. row *************************** event_name: memory/innodb/buf_buf_pool current_count: 1 current_alloc: 131.06 MiB current_avg_alloc: 131.06 MiB high_count: 1 high_alloc: 131.06 MiB high_avg_alloc: 131.06 MiB
Этот запрос схемы sys
агрегирует в настоящее время выделенную память
(current_alloc
) областью кода:
mysql> SELECT SUBSTRING_INDEX(event_name,'/',2) AS -> code_area, sys.format_bytes(SUM(current_alloc)) -> AS current_alloc FROM sys.x$memory_global_by_current_bytes -> GROUP BY SUBSTRING_INDEX(event_name,'/',2) -> ORDER BY SUM(current_alloc) DESC; +---------------------------+---------------+ | code_area | current_alloc | +---------------------------+---------------+ | memory/innodb | 843.24 MiB | | memory/performance_schema | 81.29 MiB | | memory/mysys | 8.20 MiB | | memory/sql | 2.47 MiB | | memory/memory | 174.01 KiB | | memory/myisam | 46.53 KiB | | memory/blackhole | 512 bytes | | memory/federated | 512 bytes | | memory/csv | 512 bytes | | memory/vio | 496 bytes | +---------------------------+---------------+
См. главу 24.
Немногие архитектуры аппаратных средств/операционной системы поддерживают страницы памяти, больше чем значение по умолчанию (обычно 4 КБ). Фактическое выполнение этой поддержки зависит от используемого оборудования и операционной системы. Приложения, которые выполняют много доступов к памяти, могут получить исполнительные улучшения при использовании больших страниц из-за уменьшенного Translation Lookaside Buffer (TLB).
В MySQL большие страницы могут использоваться InnoDB, чтобы выделить память для буферного пула и дополнительного пула памяти.
Стандартное использование больших страниц в MySQL пытается использовать
самый большой поддержанный размер, до 4 МБ. В соответствии с
Solaris super large pages
включает использование страниц до 256 МБ. Эта особенность доступна для
недавних платформ SPARC. Это может быть включено или отключено при
использовании
--super-large-pages
или
--skip-super-large-pages
.
MySQL также поддерживает выполнение Linux большой поддержки страницы (который называют HugeTLB в Linux).
Прежде, чем большие страницы могут использоваться в Linux,
ядру нужно позволить поддержать их, и необходимо сконфигурировать пул памяти
HugeTLB. См. файл Documentation/vm/hugetlbpage.txt
в
исходных текстах Linux.
Ядро для некоторых недавних систем, таких как Red Hat Enterprise Linux, имеет поддержку этой функции, включенную по умолчанию. Чтобы проверить, является ли это истиной для Вашего ядра, используйте следующую команду и ищите выходные строки, содержащие huge:
shell> cat /proc/meminfo | grep -i huge HugePages_Total: 0 HugePages_Free:0 HugePages_Rsvd:0 HugePages_Surp:0 Hugepagesize: 4096 kB
Непустой вывод команды указывает, что поддержка большой страницы присутствует, но нулевые значения указывают, что никакие страницы не сконфигурированы для использования.
Если Ваше ядро должно быть реконфигурировано, чтобы поддержать большие
страницы, консультируйтесь с файлом hugetlbpage.txt
.
Предполагая, что Вашему ядру Linux включили поддержку большой страницы,
сконфигурируйте это для использования MySQL, используя следующие команды.
Обычно Вы вставляете их в файл rc
или эквивалентный файл
запуска, который выполнен во время системной последовательности загрузки,
чтобы команды выполнялись каждый раз, когда система запускается.
Команды должны выполнить в последовательности загрузки прежде, чем сервер
MySQL запустится. Убедитесь, что изменили числа распределения и групповое
число, как надо для Вашей системы.
# Set the number of pages to be used. # Each page is normally 2MB, so a value of 20 = 40MB. # This command actually allocates memory, so this much # memory must be available. echo 20 > /proc/sys/vm/nr_hugepages # Set the group number that is permitted to access this # memory (102 in this case). The mysql user must be a # member of this group. echo 102 > /proc/sys/vm/hugetlb_shm_group # Increase the amount of shmem permitted per segment # (12G in this case). echo 1560281088 > /proc/sys/kernel/shmmax # Increase total amount of shared memory. The value # is the number of pages. At 4KB/page, 4194304 = 16GB. echo 4194304 > /proc/sys/kernel/shmall
Для MySQL Вы обычно хотите значение
shmmax
близко к значению shmall
.
Чтобы проверить конфигурацию страницы, надо проверить
/proc/meminfo
, как описано ранее. Теперь Вы должны видеть
некоторые ненулевые значения:
shell> cat /proc/meminfo | grep -i huge HugePages_Total:20 HugePages_Free: 20 HugePages_Rsvd:0 HugePages_Surp:0 Hugepagesize: 4096 kB
Заключительный шаг, чтобы использовать
hugetlb_shm_group
должен дать пользователю
mysql
значение unlimited
для предела memlock. Это может быть сделано, редактируя
/etc/security/limits.conf
или добавляя следующую команду к
Вашему mysqld_safe
:
ulimit -l unlimited
Добавление ulimit в
mysqld_safe
вызывает пользователя root
, чтобы установить предел memlock
в unlimited
прежде, чем переключиться на пользователя
mysql
. Это предполагает, что
mysqld_safe
запущен как root
.
Поддержка большой страницы в MySQL отключена по умолчанию. Чтобы включить,
запустите сервер с опцией
--large-pages
. Например, Вы можете использовать следующие
строки в своем my.cnf
:
[mysqld] large-pages
С этой опцией InnoDB
использует большие страницы
автоматически для его буферного пула и дополнительного пула памяти. Если
InnoDB
не может сделать этого, это отступает к использованию
традиционной памяти и пишет предупреждение в журнал ошибок:
Warning: Using conventional memory pool.
Чтобы проверить, что большие страницы используются, надо проверить
файл /proc/meminfo
:
shell> cat /proc/meminfo | grep -i huge HugePages_Total:20 HugePages_Free: 20 HugePages_Rsvd:2 HugePages_Surp:0 Hugepagesize: 4096 kB
Менеджер соединений распараллеливает запросы соединения клиента на сетевых интерфейсах, которые сервер слушает. На всех платформах один поток менеджера обрабатывает запросы соединения TCP/IP. В Unix этот поток менеджера также обрабатывает запросы соединения файла сокета Unix. В Windows поток менеджера обрабатывает запросы соединения совместно используемой памяти и именованного канала. Сервер не создает потоки, чтобы обработать интерфейсы, которые он не слушает. Например, в Windows сервер, у которого нет поддержки соединений именованного канала, не создает поток, чтобы обработать их.
Менеджер соединений распараллеливает каждое соединение клиента с потоком, который ведет обработку аутентификации и запросов для того соединения. Потоки менеджера создают новую ветвь когда необходимо, но пытаются избежать этого, работая с кэшем потока сначала, чтобы видеть, содержит ли он поток, который может использоваться для соединения. Когда соединение заканчивается, его поток возвращен к кэшу потока, если кэш не полон.
В этой модели потока соединения есть так много потоков, сколько клиентов в настоящее время работают. У этого есть некоторые недостатки, когда рабочая нагрузка сервера должна масштабироваться, чтобы обработать большие количества соединений. Например, создание потока становятся дорогими. Кроме того, каждый поток требует серверных и ядерных ресурсов, таких как пространство стека. Чтобы приспособить большое количество одновременных соединений, размер стека на поток должен быть сохранен маленьким, приводя к ситуации, где это является слишком маленьким или сервер потребляет большие объемы памяти. Истощение других ресурсов может произойти также, и планирование может стать существенным.
Чтобы управлять и контролировать, как сервер управляет потоками, которые обрабатывают соединения клиента, несколько систем и переменных состояния релевантны. См. разделы 6.1.5 и and 6.1.7.
Кэшу потока определили размер
thread_cache_size
. Значение по умолчанию 0 (никакого кэширования) заставляет поток быть
настроенным для каждого нового соединения. Установка
thread_cache_size
в N
включает N
кжширование бездействующих соединений.
thread_cache_size
может быть установлен при запуске сервера или изменен в то время, как
сервер работает. Поток соединения становится бездействующим, когда соединение
клиента, с которым он был связан, заканчивается.
Чтобы контролировать число потоков в кэше и сколько потоков было создано,
потому что поток не мог быть взят от кэша, следите за переменными
Threads_cached
и
Threads_created
.
Вы можете установить
max_connections
при запуске сервера или во время выполнения, чтобы
управлять максимальным количеством клиентов, которые
могут соединиться одновременно.
Когда стек потока является слишком маленьким, это ограничивает сложность
запросов SQL, которые сервер может обработать, глубину рекурсии хранимых
процедур и другие потребляющие память действия.
Чтобы установить размер стека в N
байт для каждого
потока, запустите сервер с
--thread_stack=
.N
MySQL поддерживает кэш узла в памяти, который содержит информацию о
клиентах: IP-адрес, имя хоста и информацию об ошибке. Сервер использует этот
кэш для нелокальных соединений TCP. Это не использует кэш для соединений TCP,
установленных, используя кольцевой интерфейс
(127.0.0.1
или ::1
) или
для соединений, установленных, используя файл сокета Unix,
именованный канал или совместно используемую память.
Для каждого нового соединения клиента сервер использует IP-адрес клиента, чтобы проверить, есть ли имя хоста клиента в кэше узла. В противном случае сервер пытается решить имя хоста. Сначала это решает IP-адрес к имени хоста и решает имя хоста назад к IP-адресу. Тогда это сравнивает результат с оригинальным IP-адресом, чтобы гарантировать, что это то же самое. Сервер хранит информацию о результате этой работы в кэше узла. Если кэш полон, от последней использованной записи отказываются.
Таблица
host_cache
Performance Schema
выставляет содержание кэша узла так, чтобы это могло быть исследовано,
используя SELECT
.
Это может помочь Вам диагностировать причины проблем соединения. См.
раздел 23.9.16.1.
Сервер обрабатывает записи в кэше узла так:
Когда первое соединение клиента TCP достигает сервера от
данного IP-адреса, новый вход создается, чтобы сделать запись IP клиента,
имени хоста и флага проверки допустимости поиска клиента. Первоначально имя
хоста установлено в NULL
и флаг ложен. Этот вход также
используется для последующих соединений клиента от того же самого IP.
NULL
, а флаг проверки допустимости установлен в истину. Для
переходных отказов имя хоста и флаг проверки допустимости остаются
неизменными. Другая попытка разрешения DNS происходит в следующий раз, когда
клиент соединяется от этого IP.Сервер выполняет разрешение имени хоста, используя безопасные для
потока вызовы gethostbyaddr_r()
и
gethostbyname_r()
, если операционная система поддерживает их.
Иначе поток, выполняющий поиск, блокирует mutex и вызывает
gethostbyaddr()
и gethostbyname()
.
В этом случае никакой другой поток не может решить имена хоста, которые не
находятся в кэше узла, пока поток, держащий блокировку mutex,
не выпускает ее.
Сервер использует кэш узла в нескольких целях:
Кэшируя результаты поисков IP к имени хоста, сервер избегает делать поиск DNS для каждого соединения клиента. Вместо этого для данного узла это должно выполнить поиск только для первого соединения от того узла.
max_connect_errors
определяет число разрешенных ошибок прежде, чем блокировка
произойдет. См. раздел B.5.2.5.
Чтобы открыть заблокированные узлы, сбросьте кэш узла командой
FLUSH HOSTS
или
mysqladmin
flush-hosts.
Для заблокированного узла возможно стать открытым даже без
FLUSH HOSTS
, если деятельность от
других узлов произошла, начиная с последней попытки соединения от
заблокированного узла. Это может произойти, потому что сервер отказывается от
последнего использованного входа кэша, чтобы создать место для нового входа,
если кэш полон, когда соединение прибывает от IP клиента не в кэше.
Если вход, от которого отказываются, был для заблокированного узла, тот
узел становится открытым.
Кэш узла включен по умолчанию. Чтобы отключить это, установите
host_cache_size
в 0 при запуске сервера или во время выполнения.
Чтобы отключить поиски имени хоста DNS, запустите сервер с
--skip-name-resolve
. В этом случае сервер использует только
IP-адреса, но не имена хоста, чтобы соответствовать строкам в таблицах
привилегий. Только учетные записи, определенные в тех таблицах, используя
IP-адреса, могут использоваться. Убедитесь, что учетная запись, которая
определяет IP-адрес существует, или Вы можете быть
не в состоянии соединиться.
Если у Вас есть очень медленный DNS и много узлов, Вы могли бы быть в
состоянии улучшить работу, отключая поиски DNS с
--skip-name-resolve
или увеличивая значение
host_cache_size
,
чтобы сделать кэш узла больше.
Чтобы отвергнуть соединения TCP/IP полностью, запустите сервер с
опцией
--skip-networking
.
Некоторые ошибки соединения не связаны с соединениями TCP,
происходят очень рано в процессе соединения (даже прежде, чем IP-адрес будет
известен), или не являются определенными для любого особого IP-адреса (такие,
как условия памяти). Для информации об этих ошибках проверьте
Connection_errors_
(см.
раздел 6.1.7).xxx
Чтобы определить эксплуатационные качества, рассмотрите следующие факторы:
Измеряете ли Вы скорость единственной работы на тихой системе или как ряд операций в течение времени. С простыми тестами Вы обычно проверяете, как изменение одного аспекта (установка конфигурации, набор индексов на таблице, пункты SQL в запросе) влияет на работу. Точки отсчета это типично продолжительные и тщательно продуманные тесты производительности, где результаты могли продиктовать высокоуровневый выбор, такой как аппаратные средства и конфигурация хранения, или как скоро обновиться до новой версии MySQL.
InnoDB
.Этот раздел прогрессирует от методов простого и прямого измерения, которые единственный разработчик может сделать, к более сложным, которые требуют дополнительной экспертизы и интерпретации результатов.
Чтобы измерить скорость определенного выражения MySQL или функции,
вызовите BENCHMARK()
,
используя mysql
. Ее синтаксис:
BENCHMARK(
. Возвращаемое значение всегда ноль, но
mysql
печатает строку, выводящую на экран приблизительно, сколько времени
запрос взял. Например:
loop_count
,expression
)
mysql> SELECT BENCHMARK(1000000,1+1); +------------------------+ | BENCHMARK(1000000,1+1) | +------------------------+ | 0 | +------------------------+ 1 row in set (0.32 sec)
Этот результат был получен на системе Pentium II 400MHz. Это показывает, что MySQL может выполнить 1000000 простых сложений за 0.32 секунды на этой системе.
Встроенные функции MySQL, как правило, чрезвычайно оптимизируются,
но могут быть некоторые исключения.
BENCHMARK()
превосходный инструмент для того, чтобы узнать, является ли некоторая функция
проблемой для Ваших запросов.
Определите эффективность своего приложения и базы данных, чтобы узнать, где узкие места. После установки одного узкого места (или заменяя это with a пустым модулем), Вы можете продолжить идентифицировать следующее узкое место. Даже если эффективность работы для Вашего приложения в настоящее время является приемлемой, Вы должны, по крайней мере, сделать план относительно каждого узкого места и решить, как решать это, если когда-нибудь Вы действительно будете нуждаться в дополнительной производительности.
Свободный эталонный набор Open Source Database Benchmark, можно скачать с http://osdb.sourceforge.net/.
Это очень характерно для проблемы проявляться только, когда система очень в большой степени загружена. У нас было много клиентов, которые связываются с нами, когда они имеют (проверенную) систему в производстве и столкнулись с проблемами загрузки. В большинстве случаев проблемы, оказывается, происходят из-за проблем основного проектирования баз данных (например, сканирование таблицы не хорошо при высокой загрузке) или проблемы с операционной системой или библиотеками. Большую часть времени эти проблемы было бы намного легче решить, если бы системы еще не работали.
Чтобы избежать проблем, определите эффективность своего целого приложения при худшей загрузке:
mysqlslap может быть полезной для того, чтобы моделировать высокую загрузку, произведенную многими клиентами, выпускающими запросы одновременно. См. раздел 5.5.8.
Эти программы или пакеты могут положить систему, так что надо убедиться, что использовали их только на Ваших системах развития.
Вы можете запросить таблицы в performance_schema
, чтобы
видеть информацию в реальном времени о технических характеристиках Вашего
сервера и приложений, см. главу 23.
Когда Вы пытаетесь установить то, что делает Ваш сервер MySQL, может быть полезно исследовать список процессов, который является набором потоков, в настоящее время выполняющихся в пределах сервера. Информация о списке процесса доступна из этих источников:
SHOW [FULL] PROCESSLIST
:
раздел 14.7.5.29
SHOW PROFILE
:
раздел 14.7.5.31INFORMATION_SCHEMA
PROCESSLIST
:
раздел 22.16потоков
,
таблицы этапа и таблицы блокировки в Performance Schema:
раздел 23.9.16
, раздел 23.9.5
и раздел 23.9.12.
Доступ к threads
не требует mutex и оказывает минимальное влияние на работу сервера.
INFORMATION_SCHEMA.PROCESSLIST
и SHOW PROCESSLIST
имеют отрицательные исполнительные последствия, потому что они
требуют mutex. threads
также показывает информацию о фоновых потоках, которую не дают
INFORMATION_SCHEMA.PROCESSLIST
и SHOW PROCESSLIST
. Это означает, что
threads
может использоваться, чтобы контролировать деятельность,
которую другие источники информации дать не могут.
Вы можете всегда рассматривать информацию о своих собственных потоках.
Чтобы смотреть информацию о потоках, выполняемых для других учетных записей,
Вы должны иметь привилегию PROCESS
.
Каждый вход списка процесса содержит несколько сведений:
Id
идентификатор соединения для
клиента, связанного с потоком.
User
и Host
указывают на учетную
запись, связанную с потоком.db
база данных по умолчанию для потока или
NULL
, если ни одна не выбрана.Command
и State
указывают на то, что делает поток.
Большинство статусов соответствует очень быстрым операциям. Если поток остается в данном статусе в течение многих секунд, может быть проблема, которая должна быть исследована.
Time
указывает, как долго поток был в его текущем состоянии.
Понятие потока текущего времени может быть изменено в некоторых случаях:
поток может изменить время с SET
TIMESTAMP = value
.
Для потока, работающего на ведомом устройстве, которое обрабатывает события
от ведущего устройства, время потока установлено во время, найденное в
событиях, и таким образом отражает текущее время на ведущем устройстве,
а не на ведомом.Info
содержит текст запроса, выполняемого потоком, или
NULL
, если это не выполняет ничего. По умолчанию это значение
содержит только первые 100 символов запроса. Чтобы видеть полные запросы,
надо использовать
SHOW FULL PROCESSLIST
.Следующие разделы перечисляют возможные значения
Command
и State
по категориям. Значение для
некоторых из этих значений самоочевидно. Для других
обеспечено дополнительное описание.
У потока может быть любое из следующих значений Command
:
Binlog Dump
Это поток на главном сервере для того, чтобы послать содержание журнала в ведомый сервер.
Change user
Поток выполняет работу изменения пользователя.
Close stmt
Поток закрывает готовый запрос.
Connect
Ведомое устройство соединено с его ведущим устройством.
Connect Out
Ведомое устройство соединяется с его ведущим устройством.
Create DB
Поток выполняет работу создать-базу-данных.
Daemon
Этот поток является внутренним к серверу, а не потоку, который обслуживает соединение клиента.
Debug
Поток производит информацию об отладке.
Delayed insert
Поток обрабатывает отложенную вставку.
Drop DB
Поток выполняет работу удаления базы данных.
Error
Execute
Поток выполняет готовый запрос.
Fetch
Поток приносит следствия выполнения готового запроса.
Field List
Поток получает информацию для столбцов таблицы.
Init DB
Поток выбирает базу данных по умолчанию.
Kill
Поток уничтожает другой поток.
Long Data
Поток получает длинные данные в результате выполнения готового запроса.
Ping
Поток обрабатывает запрос ping сервера.
Prepare
Поток готовит готовый запрос.
Processlist
Поток производит информацию о потоках сервера.
Query
Поток выполняет запрос.
Quit
Поток заканчивается.
Refresh
Поток сбрасывает таблицу, журналы или кэши или сбрасывает переменную состояния или информацию о сервере репликации.
Register Slave
Поток регистрирует ведомый сервер.
Reset stmt
Поток сбрасывает готовый запрос.
Set option
Поток устанавливает или сбрасывает опцию выполнения запроса клиента.
Shutdown
Поток закрывает сервер.
Sleep
Поток ждет клиента, чтобы послать новый запрос.
Statistics
Поток производит информацию о статусе сервера.
Table Dump
Поток посылает табличное содержание в ведомый сервер.
Time
Не использован.
Следующий список описывает значения State
потока, которые
связаны с общей обработкой запроса, но не более специализированными
действиями, такими как репликация. Многие из них полезны только для
отлова ошибок в сервере.
After create
Это происходит, когда поток составляет таблицу (включая внутренние временные таблицы), в конце функции, которая составляет таблицу. Это состояние используется, даже если таблица не могла быть составлена из-за некоторой ошибки.
Analyzing
Поток вычисляет табличные ключевые распределения MyISAM
(например, для ANALYZE TABLE
).
checking permissions
Поток проверяет, есть ли у сервера необходимые привилегии, чтобы выполнить этот запрос.
Checking table
Поток выполняет табличную проверку.
cleaning up
Поток обработал одну команду и готовится освобождать память и сбрасывать определенные параметры состояния.
closing tables
Поток сбрасывает измененные табличные данные на диск и закрывает используемые таблицы. Это должно быть быстрой работой. В противном случае проверьте, что у Вас нет полного диска и что диск не находится в очень интенсивном использовании.
converting HEAP to MyISAM
Поток преобразовывает внутреннюю временную таблицу из
MEMORY
в MyISAM
на диске.
copy to tmp table
Поток обрабатывает ALTER
TABLE
. Это состояние происходит после того, как таблица с новой
структурой была составлена, но прежде, чем строки будут скопированы в нее.
Для потока в этом статусе Performance Schema может использоваться, чтобы получить информацию о продвижении работы копии. См. раздел 23.9.5.
Copying to group table
Если запрос имеет отличающиеся ORDER BY
и
GROUP BY
, строки сортированы группой и скопированы
во временную таблицу.
Copying to tmp table
Сервер копирует к временной таблице в памяти.
altering table
Сервер находится в процессе выполнения оперативного
ALTER TABLE
.
Copying to tmp table on disk
Сервер копирует к временной таблице на диске. Временный набор результатов стал слишком большим (см. раздел 9.4.4). Следовательно, поток изменяет временную таблицу в памяти до основанного на диске формата, чтобы сохранить память.
Creating index
Поток обрабатывает ALTER TABLE ... ENABLE KEYS
для MyISAM
.
Creating sort index
Поток обрабатывает SELECT
,
используя внутреннюю временную таблицу.
creating table
Поток составляет таблицу. Это включает создание временных таблиц.
Creating tmp table
Поток составляет временную таблицу в памяти или на диске.
Если таблица будет составлена в памяти, но позже будет преобразована в
таблицу на диске, то состояние во время той работы будет
Copying to tmp table on disk
.
committing alter table to storage engine
Сервер закончил оперативное ALTER
TABLE
и передает результат.
deleting from main table
Сервер выполняет первую часть многотабличного удаления. Это удаляет только из первой таблицы и сохраняет столбцы и смещения, которые будут использоваться для того, чтобы удалить из другой таблицы.
deleting from reference tables
Сервер выполняет вторую часть многотабличного удаления: удаление соответствующих строк из других таблиц.
discard_or_import_tablespace
Поток обрабатывает ALTER TABLE ... DISCARD TABLESPACE
или
ALTER TABLE ... IMPORT TABLESPACE
.
end
Это происходит в конце, но перед уборкой
ALTER TABLE
,
CREATE VIEW
,
DELETE
,
INSERT
,
SELECT
или
UPDATE
.
executing
Поток начал выполнять запрос.
Execution of init_command
Поток выполняет запросы в значении системной
переменной init_command
.
freeing items
Поток выполнил команду. Некоторое освобождение от элементов, сделанных во
время этого состояния, вовлекает кэш запроса. Это состояние обычно
сопровождается cleaning up
.
FULLTEXT initialization
Сервер готовится выполнять полнотекстовый поиск на естественном языке.
init
Это происходит перед инициализацией
ALTER TABLE
,
DELETE
,
INSERT
,
SELECT
или
UPDATE
.
Меры, предпринятые сервером в этом статусе, включают сброс двоичного журнала,
журнала InnoDB
и некоторые операции уборки кэша запроса.
Для статуса end
следующие операции могли происходить:
Удаление записи кэша запроса после того, как данные в таблице изменены.
Killed
Кто-то послал потоку запрос KILL
и это должно прерваться в следующий раз, когда это проверяет
флаг уничтожения. Флаг проверен в каждом главном цикле
в MySQL, но в некоторых случаях могло бы все еще потребоваться короткое время
для потока, чтобы завершиться. Если поток заблокирован некоторым другим
потоком, уничтожение вступает в силу, как только другой поток
выпускает свою блокировку.
Locking system tables
Поток пытается заблокировать системную таблицу (например, часовой пояс или таблицу журнала).
logging slow query
Поток пишет запрос в журнал медленного запроса.
login
Начальное состояние для потока соединения, пока клиент не был заверен успешно.
manage keys
Сервер включает или отключает индекс таблицы.
NULL
Это состояние используется для
SHOW PROCESSLIST
.
Opening system tables
Поток пытается открыть системную таблицу (например, часовой пояс или таблицу журнала).
Opening tables
Поток пытается открыть таблицу. Это должна быть очень быстрая процедура,
если что-то не предотвращает открытие. Например,
ALTER TABLE
или
LOCK TABLE
может предотвратить открытие таблицы, пока запрос не закончено. Также стоит
проверить что Ваше значение
table_open_cache
является достаточно большим.
Для системных таблиц вместо этого используется
Opening system tables
.
optimizing
Сервер выполняет начальную оптимизацию для запроса.
preparing
Это состояние происходит во время оптимизации запроса.
Purging old relay logs
Поток удаляет ненужные файлы системного журнала реле.
query end
Это состояние происходит после обработки запроса, но
перед freeing items
.
Receiving from client
Сервер читает пакет от клиента.
Removing duplicates
Запрос использовал SELECT DISTINCT
таким способом, которым MySQL не мог оптимизировать работу на ранней
стадии. Из-за этого MySQL требует, чтобы дополнительный этап удалил все
дублированные строки прежде, чем послать результат клиенту.
removing tmp table
Поток удаляет внутреннюю временную таблицу после обработки
SELECT
.
Это состояние не используется, если никакая временная таблица
не была составлена.
rename
Поток переименовывает таблицу.
rename result table
Поток обрабатывает ALTER TABLE
, который составил новую таблицу и переименовывает ее, чтобы
заменить оригинальную таблицу.
Reopen tables
Поток получил блокировку для таблицы, но после получения блокировки, которая изменила основную структуру таблицы. Это освободило блокировку, закрыло таблицу и пытается вновь открыть.
Repair by sorting
Код ремонта использует сортировку, чтобы создать индексы.
preparing for alter table
Сервер готовится выполнять оперативное
ALTER TABLE
.
Repair done
Поток завершил мультипоточный ремонт для MyISAM
.
Repair with keycache
Код ремонта использует создание ключей один за другим через ключевой кэш.
Это намного медленнее, чем Repair by sorting
.
Rolling back
Поток удаляет транзакцию.
Saving state
Для операций MyISAM
, таких как ремонт или анализ, поток
сохраняет новое табличное состояние в заголовок файла .MYI
.
Состояние включает такую информацию, как число строк, счетчик
AUTO_INCREMENT
и ключевые распределения.
Searching rows for update
Поток делает первую фазу, чтобы найти все строки соответствия прежде, чем
обновить их. Это должно быть сделано, если
UPDATE
изменяет индексирование,
которое используется, чтобы найти вовлеченные строки.
Sending data
Поток читает и обрабатывает строки для
SELECT
и посылает данные клиенту. Поскольку операции, происходящие во время этого
состояния, имеют тенденцию выполнять большое количество дискового доступа
(чтения), это часто самое продолжительное состояние данного запроса.
Sending to client
Сервер пишет пакет клиенту.
setup
Поток начинает ALTER TABLE
.
Sorting for group
Поток делает сортировку, чтобы удовлетворить GROUP BY
.
Sorting for order
Поток делает сортировку, чтобы удовлетворить ORDER BY
.
Sorting index
Поток сортирует индексные страницы для более эффективного доступа во время
оптимизации таблицы MyISAM
.
Sorting result
Для SELECT
это подобно
Creating sort index
, но не для временных таблиц.
statistics
Сервер вычисляет статистику, чтобы развить план выполнения запроса. Если поток находится в этом статусе в течение долгого времени, сервер, вероятно, занят выполнением другой работы.
System lock
Поток вызвал mysql_lock_tables()
,
и состояние потока не было обновлено с тех пор. Это очень общее состояние,
которое может произойти по многим причинам.
Например, поток собирается просить или ждет внутренней или внешней
системной блокировки для таблицы. Это может произойти, когда
InnoDB
ждет блокировки на уровне таблицы во время выполнения
LOCK TABLES
.
Если это состояние вызывается запросами о внешних блокировках, и Вы не
используете многократные mysqld
, которые получают доступ к тем же самым таблицам
MyISAM
,
Вы можете отключить внешние системные блокировки с помощью
--skip-external-locking
. Однако, внешняя блокировка отключена по
умолчанию, таким образом, вероятно, что эта опция не будет иметь никакого
эффекта. Для SHOW PROFILE
это состояние означает, что поток просит блокировку (не ждет ее).
Для системных таблиц вместо этого используется
Locking system tables
.
update
Поток готовится начинать обновлять таблицу.
Updating
Поток ищет строки, чтобы обновить и обновляет их.
updating main table
Сервер выполняет первую часть многотабличного обновления. Это обновляет только первую таблицу и сохраняет столбцы и смещения, которые будут использоваться для того, чтобы обновить другие таблицы.
updating reference tables
Сервер выполняет вторую часть многотабличного обновления и обновляет соответствующие строки других таблиц.
User lock
Поток собирается просить или ждет консультативной блокировки, которую
требуют через GET_LOCK()
. Для SHOW PROFILE
это состояние означает, что поток просит блокировку (не ждет ее).
User sleep
Поток вызвал SLEEP()
.
Waiting for commit lock
FLUSH TABLES WITH READ LOCK
ждет передачи блокировки.
Waiting for global read lock
FLUSH TABLES WITH READ LOCK
ждет глобальной блокировки чтения или установки глобальной переменной
read_only
.
Waiting for tables
Поток получил уведомление, что глубинная структура для таблицы изменилась и это должно вновь открыть таблицу, чтобы получить новую структуру. Однако, чтобы вновь открыть таблицу, это должно ждать, пока все другие потоки не закрыли рассматриваемую таблицу.
Это уведомление имеет место, если другой поток использовал
FLUSH TABLES
или один из следующих запросов о рассматриваемой таблице:
FLUSH TABLES
,
tbl_name
ALTER TABLE
,
RENAME TABLE
,
REPAIR TABLE
,
ANALYZE TABLE
или
OPTIMIZE TABLE
.
Waiting for table flush
Поток выполняет FLUSH TABLES
и ждет всех потоков, чтобы закрыть их таблицы, или поток получил уведомление,
что глубинная структура для таблицы изменилась, и это должно вновь
открыть таблицу, чтобы получить новую структуру. Однако, чтобы вновь
открыть таблицу, это должно ждать, пока все другие потоки
не закрыли рассматриваемую таблицу.
Это уведомление имеет место, если другой поток использовал
FLUSH TABLES
или один из следующих запросов о рассматриваемой таблице:
FLUSH TABLES
,
tbl_name
ALTER TABLE
,
RENAME TABLE
,
REPAIR TABLE
,
ANALYZE TABLE
или
OPTIMIZE TABLE
.
Waiting for lock_type
lock
Сервер ждет, чтобы приобрести блокировку THR_LOCK
или
блокировку от подсистемы блокировки метаданных, где
lock_type
указывает на тип блокировки.
Это состояние указывает на ожидаемую THR_LOCK
:
Waiting for table level lock
Эти состояния указывают на ожидание блокировки метаданных:
Waiting for event metadata lock
Waiting for global read lock
Waiting for schema metadata lock
Waiting for stored function metadatalock
Waiting for stored procedure metadata lock
Waiting for table metadata lock
Waiting for trigger metadata lock
Для информации о табличных индикаторах блокировки см. раздел 9.11.1. Для информации о блокировке метаданных см. раздел 9.11.4. Чтобы видеть, какие блокировки блокируют запросы блокировки, используйте таблицы блокировки Performance Schema, описанные в разделе 23.9.12.
Waiting on cond
Состояние, в котором поток ждет условия. Никакая определенная информация не доступна.
Writing to net
Сервер пишет пакет в сеть.
Эти состояния потока связаны с кэшем запроса (см. раздел 9.10.3).
checking privileges on cached query
Сервер проверяет, есть ли у пользователя привилегии, чтобы получить доступ к кэшируемому результату запроса.
checking query cache for query
Сервер проверяет, присутствует ли текущий запрос в кэше запроса.
invalidating query cache entries
Записи кэша запроса отмечаются как недопустимые, потому что основные таблицы изменились.
sending cached result to client
Сервер берет результат запроса из кэша запроса и посылает это клиенту.
storing result in query cache
Сервер хранит результат запроса в кэше запроса.
Waiting for query cache lock
Это состояние происходит в то время, как сеанс ждет, чтобы взять
блокировку кэша запроса. Это может произойти для любого запроса, который
должен выполнить некоторую работу кэша запроса, например,
INSERT
или
DELETE
лишает законной силы кэш
запроса, SELECT
, который ищет
кэшируемые данные, RESET QUERY CACHE
и т.д.
Следующий список показывает наиболее распространенные состояния, которые
Вы можете видеть в столбце State
для потока Binlog Dump
ведущего устройства. Если Вы не видите потоки Binlog Dump
на главном сервере, это означает, что репликация не запущена,
то есть, что никакие ведомые устройства в настоящее время не соединяются.
Finished reading one binlog; switching to next binlog
Поток закончил читать двоичный файл системного журнала и открывает следующий, чтобы послать в ведомое устройство.
Master has sent all binlog to slave; waiting for more updates
Поток считал все остающиеся обновления из двоичных журналов и послал их в ведомое устройство. Поток теперь неактивен, ожидая новых событий из новых обновлений, происходящих на ведущем устройстве.
Sending binlog event to slave
Двоичные журналы состоят из событий, где событие обычно обновление плюс некоторая другая информация. Поток считал событие из двоичного журнала и теперь посылает его в ведомое устройство.
Waiting to finalize termination
Очень краткое состояние, которое происходит как поток, останавливается.
Следующий список показывает наиболее распространенные состояния, которые
Вы видите в столбце State
для ведомого потока ввода/вывода
сервера. Это состояние также появляется в столбце Slave_IO_State
, выведенный на экран SHOW
SLAVE STATUS
, таким образом, Вы можете получить хорошее
представление того, что происходит при использовании запроса.
Checking master version
Установлено состояние, которое происходит очень кратко, после соединения с ведущим устройством.
Connecting to master
Поток пытается соединиться с ведущим устройством.
Queueing master event to the relay log
Поток считал событие и копирует это к журналу реле так, чтобы поток SQL мог обработать это.
Reconnecting after a failed binlog dump request
Поток пытается повторно соединиться с ведущим устройством.
Reconnecting after a failed master event read
Поток пытается повторно соединиться с ведущим устройством. Когда
соединение установлено снова, состояние становится
Waiting for master to send event
.
Registering slave on master
Установлено состояние, которое происходит очень кратко после соединения с ведущим устройством.
Requesting binlog dump
Установлено состояние, которое происходит очень кратко, после соединения с ведущим устройством. Поток посылает ведущему устройству запрос о содержании его двоичных журналов, запускающихся с требуемого имени файла системного журнала и позиции.
Waiting for its turn to commit
Состояние, которое происходит, когда ведомый поток ждет больше
старых потоков, чтобы передать, если включена
slave_preserve_commit_order
.
Waiting for master to send event
Поток соединился с ведущим устройством и ждет событий журнала.
Это может длиться в течение долгого времени, если ведущее устройство
неактивно. Если ожидание длится
slave_net_timeout
секунд, тайм-аут происходит. В том пункте поток полагает, что
соединение сломано и предпринимает попытку повторно соединиться.
Waiting for master update
Начальное состояние прежде Connecting to master
.
Waiting for slave mutex on exit
Состояние, которое происходит кратко, когда поток останавливается.
Waiting for the slave SQL thread to free enough
relay log space
Вы используете отличное от нуля значение
relay_log_space_limit
, и журналы реле стали достаточно большими,
что их объединенный размер превышает это значение. Поток ввода/вывода ждет,
пока поток SQL не освобождает достаточно пространства,
обрабатывая содержание журнала реле так, чтобы это могло удалить некоторые
файлы системного журнала реле.
Waiting to reconnect after a failed binlog dump request
Если запрос дампа журнала прерван (из-за разъединения), поток входит в это
состояние и спит в то время, как идут попытки периодически повторно
соединяться. Интервал между повторениями может быть определен, используя
CHANGE MASTER TO
.
Waiting to reconnect after a failed master event read
Ошибка произошла, читая (из-за разъединения). Поток спит число секунд,
установленных CHANGE MASTER TO
(по умолчанию 60) прежде, чем попытаться повторно соединиться.
Следующий список показывает наиболее распространенные состояния, которые
Вы можете видеть в столбце State
для потока
SQL ведомого сервера:
Killing slave
Поток обрабатывает STOP SLAVE
.
Making temporary file (append) before replaying
LOAD DATA INFILE
Поток выполняет LOAD DATA INFILE
и прилагает данные к временному файлу, содержащему данные, из которых в
едомое устройство считает строки.
Making temporary file (create) before replaying
LOAD DATA INFILE
Поток выполняет LOAD DATA INFILE
и создает временный файл, содержащий данные, из которого ведомое
устройство читает строки. С этим состоянием можно только столкнуться, если
оригинал LOAD DATA INFILE
был зарегистрирован ведущим устройством, выполняющим версию MySQL
ниже, чем версия 5.0.3.
Reading event from the relay log
Поток считал событие из журнала реле так, чтобы оно было обработано.
Slave has read all relay log; waiting for more updates
Поток обработал все события в файлах системного журнала реле и теперь ждет потока ввода/вывода, чтобы написать новые события журналу реле.
Waiting for an event from Coordinator
Используя мультипоточное ведомое устройство
(
slave_parallel_workers
больше 1), один из ведомых потоков
ждет события от потока координатора.
Waiting for slave mutex on exit
Очень краткое состояние, которое происходит, когда поток останавливается.
Waiting for Slave Workers to free pending events
Это действие ожидания происходит, когда полный размер обрабатываемых
событий превышает размер
slave_pending_jobs_size_max
.
Координатор продолжит планировать, когда размер будет ниже этого предела. Это
состояние происходит только, когда
slave_parallel_workers
больше 0.
Waiting for the next event in relay log
Начальное состояние прежде Reading event from the relay log
.
Waiting until MASTER_DELAY seconds after master executed event
Поток SQL считал событие, но ждет ведомой задержки. Эта задержка
установлена с помощтю опции MASTER_DELAY
в
CHANGE MASTER TO
.
Столбец Info
для потока SQL может также показать текст
запроса. Это указывает, что поток считал событие
из журнала реле, извлек запрос из этого и может его выполнять.
Эти состояния потока происходят на ведомом устройстве, но связаны с потоками соединения, а не с потоками SQL или вводом/выводом.
Changing master
Поток обрабатывает
CHANGE MASTER TO
.
Killing slave
Поток обрабатывает STOP SLAVE
.
Opening master dump table
Это состояние происходит после
Creating table from master dump
.
Reading master dump table data
Это состояние происходит после Opening master dump table
.
Rebuilding the index on master dump table
Это состояние происходит после Reading master dump table data
.
Эти состояния происходят для потока Event Scheduler, потоков, которые создаются, чтобы запустить намеченные события, или потоков, которые заканчивают планировщик.
Clearing
Поток планировщика или поток, который запускал событие, заканчиваются или собираются закончиться.
Initialized
Поток планировщика или поток, который запустит событие, были инициализированы.
Waiting for next activation
У планировщика есть непустая очередь событий, но следующая активация находится в будущем.
Waiting for scheduler to stop
Поток запустил SET GLOBAL event_scheduler=OFF
и ждет завершения планировщика.
Waiting on empty queue
Очередь планировщика событий пуста, и он спит.