Глава 9. Оптимизация
Эта глава объясняет, как оптимизировать работу MySQL и обеспечивает
примеры. Оптимизация вовлекает конфигурирование, настройку и определение
эксплуатационных качеств на нескольких уровнях. В зависимости от Вашей роли
(разработчик, DBA или комбинация обоих), Вы могли бы оптимизировать на уровне
отдельных запросов SQL, всех приложений, единственного сервера базы данных
или многих сетевых серверов базы данных. Иногда Вы можете запланировать
заранее работу в то время, как в других случаях Вы могли бы расследовать
конфигурацию или кодировать проблему после того, как проблема происходит.
Оптимизация центрального процессора и использования памяти может также
улучшить масштабируемость, позволяя базе данных обработать большие
нагрузки без замедления.
9.1. Краткий обзор оптимизации
Работа базы данных зависит от нескольких факторов на уровне базы данных,
таких как таблицы, запросы и настройки конфигурации. Это результат
конструкций программного обеспечения в центральном процессоре и операциях
ввода/вывода на уровне аппаратных средств, который Вы должны минимизировать и
сделать настолько эффективным, насколько возможно. Поскольку Вы работаете над
работой базы данных, Вы начинаете, изучая высокоуровневые правила и
направляющие линии для стороны программного обеспечения, и измеряя работу,
используя время. По мере того, как Вы становитесь экспертом, Вы узнаете
больше о том, что происходит внутренне, и начинаете измерять вещи, такие как
циклы центрального процессора и операции ввода/вывода.
Типичные пользователи стремятся вытаскивать лучшую работу базы данных из
своих существующих конфигураций программного и аппаратного обеспечения.
Усовершенствованные пользователи ищут возможности улучшить программное
обеспечение MySQL непосредственно или развить их собственные механизмы
хранения и аппаратные средства, чтобы расширить экосистему MySQL.
Оптимизация на уровне базы данных
Наиболее важным фактором в создании приложения базы данных
является базовая конструкция:
Таблицы структурированы должным образом? В частности, у столбцов
есть правильные типы данных, и действительно каждая таблица имеет
соответствующие столбцы для типа работы? Например, у приложений, которые
выполняют частые обновления, есть много таблиц с малым числом столбцов в то
время, как у приложений, которые анализируют большие объемы данных, есть
немного таблиц со многими столбцами.
- Правильные индексы сделают запросы эффективными.
- Вы используете соответствующий механизм хранения для каждой таблицы,
и используете в своих интересах силы и особенности каждого механизма
хранения, который Вы используете? В частности, выбор такого механизма
хранения, как
InnoDB
или MyISAM
может быть очень важным для работы и масштабируемости.
InnoDB механизм хранения по умолчанию для новых таблиц.
Практически, усовершенствованные технические характеристики означают, что
таблицы InnoDB часто выигрывают у более простого
MyISAM , специально для занятой базы данных.
- Каждая таблица использует соответствующий формат строки? Этот выбор также
зависит от механизма хранения, используемого для таблицы. В частности, сжатые
таблицы используют меньше дискового пространства и требуют меньше дискового
ввода/вывода. Сжатие доступно для всех видов рабочих нагрузок с
InnoDB и MyISAM только для чтения.
- Приложение использует соответствующую
стратегию блокировки?
Например, позволяя совместный доступ, когда возможно, чтобы операции базы
данных могли работать одновременно, и требуя эксклюзивного доступа, когда
надо, чтобы критические операции получили высший приоритет. Снова выбор
механизма хранения является существенным.
InnoDB обрабатывает
большинство проблем блокировки без Вашего участия, ведет учет лучшего
параллелизма в базе данных и сокращает количество экспериментирования и
настройки для Вашего кода.
- Все области памяти для кэшей
измерены правильно? Они должны быть достаточно большими, чтобы содержать
нужные данные, но не настолько большими, чтобы перегружали физическую память.
Основные области памяти, чтобы сконфигурировать буферный пул
InnoDB , кэш ключей MyISAM и кэш запросов MySQL.
Оптимизация на уровне аппаратных средств
Любое приложение базы данных в конечном счете упирается в пределы
аппаратных средств, поскольку база данных становится более занятой. DBA
должен оценить, возможно ли настроить приложение или реконфигурировать
сервер, чтобы избежать этих проблем или требуется больше ресурсов аппаратных
средств. Системные узкие места, как правило, являются
результатом этих источников:
Диск поиски. Это занимает время, чтобы найти часть данных. С
современными дисками среднее время для этого обычно ниже 10 миллисекунд,
таким образом, мы можем в теории делать, приблизительно 100 поисков в
секунду. Это время медленно улучшается с новыми дисками и очень трудно
оптимизировать для единственной таблицы. Способ оптимизировать время поиска,
это распределить данные больше, чем на один диск.
- Дисковое чтение. Когда диск в правильной позиции, мы должны читать или
писать данные. С современными дисками один диск поставляет, по крайней мере,
10-20 MB/сек. Это легче оптимизировать, чем поиск, потому что Вы можете
читать параллельно с многих дисков.
- Циклы центрального процессора. Когда данные находятся в основной памяти,
мы должны обработать это, чтобы получить наш результат. Наличие больших
таблиц по сравнению с объемом памяти является наиболее распространенным
ограничивающим фактором. Но с маленькими таблицами, скорость
обычно не проблема.
- Пропускная способность памяти. Когда центральный процессор нуждается в
большем количестве данных, чем может поместиться в кэш центрального
процессора, основная пропускная способность памяти становится узким местом.
Это необычное узкое место для большинства систем, но оно есть.
Балансирование мобильности и работы
Чтобы использовать ориентируемый на работу на расширения SQL в портируемой
программе MySQL, Вы можете обернуть MySQL-определенные ключевые слова в
запросе в пределах комментария /*! */ .
Другие SQL-серверы игнорируют прокомментированные ключевые слова.
Для информации об использовании комментариев см.
раздел 10.6.
9.2.
Оптимизация запросов SQL
Основная логика приложения базы данных выполнена через запросы SQL.
9.2.1. Оптимизация SELECT
Запросы в форме SELECT
выполняют все операции поиска в базе данных. Настройка этих запросов
является высшим приоритетом.
Кроме того SELECT также относятся к таким конструкциям, как
CREATE TABLE...AS SELECT , INSERT INTO...SELECT и
WHERE в DELETE .
У этих запросов есть дополнительные исполнительные соображения, потому что
они объединяют операции записи с ориентируемыми на чтение операциями запроса.
9.2.1.1. Скорость SELECT
Основные соображения для того, чтобы оптимизировать запросы:
Чтобы ускорить 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
может быть Вашим первым шагом для каждого запроса.
- Скорректируйте размер и свойства областей памяти, которые MySQL
использует для того, чтобы кэшировать. С эффективным использованием
буферного пула
InnoDB
, кэша ключей MyISAM и кэша запроса MySQL, повторенные
запросы работают быстрее, потому что результаты получены из
памяти в последующие разы.
- Даже для запроса, который выполняет быстрое использование областей
кэш-памяти, Вы могли бы все еще оптимизировать далее так, чтобы он требовал
меньшего количества кэш-памяти. Масштабируемость означает, что Ваше
приложение может обработать больше одновременных пользователей, больше
запросов и так далее, не испытывая большое замедление работы.
- Разберитесь с проблемой блокировок, где скорость Вашего запроса могла бы
быть затронута другими сеансами, получающими доступ к таблицам в то
же самое время.
9.2.1.2.
Как MySQL оптимизирует WHERE
Этот раздел обсуждает оптимизацию, которая может быть сделана для того,
чтобы обработать 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
, когда используется только с одной таблицей.
- Раннее обнаружение недопустимых постоянных выражений. MySQL быстро
обнаруживает что некоторые
SELECT
невозможны и не возвращает строки.
HAVING слит с WHERE ,
если Вы не используете GROUP BY или совокупные функции
(COUNT() ,
MIN() и т.д.).
- Для каждой таблицы в соединении более простой
WHERE
создан, чтобы получить быструю оценку WHERE
для таблицы и пропускать строки как можно скорее.
- Все постоянные таблицы считаны сначала перед любыми другими таблицами в
запросе. Постоянная таблица это любое из следующего:
Все следующие таблицы используются в качестве постоянных таблиц:
SELECT * FROM t WHERE primary_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 использует временную таблицу в памяти.
- Каждый индекс таблицы запрошен, и лучший индекс используется,
если оптимизатор не полагает, что более эффективно использовать сканирование
таблицы. Когда-то просмотр использовался, основываясь на том, индексирует ли
лучший индекс больше 30% таблицы, но неподвижный процент больше не определяет
выбор между использованием индексирования или просмотра. Оптимизатор теперь
более сложен и базирует свою оценку на дополнительных факторах, таких как
табличный размер, число строк и размер блока ввода/вывода.
- В некоторых случаях MySQL может считать строки из индекса, даже не
консультируясь с файлом с данными. Если все столбцы, используемые в
индексе, являются числовыми, только индексное дерево используется,
чтобы решить запрос.
- Прежде, чем каждая строка выведена, те, которые не
соответствуют
HAVING пропущены.
Некоторые примеры запросов, которые очень быстры:
SELECT COUNT(*) FROM tbl_name ;
SELECT MIN(key_part1 ),
MAX(key_part1 )
FROM tbl_name ;
SELECT MAX(key_part2 )
FROM tbl_name
WHERE key_part1 =constant ;
SELECT ... FROM tbl_name
ORDER BY key_part1 ,
key_part2 ,... LIMIT 10;
SELECT ... FROM tbl_name
ORDER BY key_part1 DESC,
key_part2 DESC, ... LIMIT 10;
MySQL решает следующие запросы, используя только индексное
дерево, предполагая, что индексированные столбцы являются числовыми:
SELECT key_part1 ,key_part2
FROM tbl_name
WHERE key_part1 =val ;
SELECT COUNT(*) FROM tbl_name
WHERE key_part1 =val1 AND
key_part2 =val2 ;
SELECT key_part2 FROM tbl_name
GROUP BY key_part1 ;
Следующая индексация использования запросов, чтобы получить строки в
сортированном порядке без отдельного прохода сортировки:
SELECT ... FROM tbl_name
ORDER BY key_part1 ,
key_part2 ,... ;
SELECT ... FROM tbl_name
ORDER BY key_part1 DESC,
key_part2 DESC, ... ;
9.2.1.3. Оптимизация диапазона
Метод доступа диапазона
использует один индекс, чтобы получить подмножество строк
таблицы, которые содержатся в пределах одного или нескольких индексных
интервалов. Это может использоваться для единственной или нескольких частей
индекса. Следующие разделы дают описания условий, при которых оптимизатор
использует доступ диапазона.
9.2.1.3.1.
Метод доступа диапазона для единственной части индекса
Интервалы значения могут быть удобно представлены соответствующими
условиями в WHERE , обозначенный как условия диапазона
вместо интервалов.
Определение условия диапазона для единственной части индекса:
Для индексов BTREE и HASH сравнение
части ключа с постоянной величиной условия диапазона сделано, используя
операторы = ,
<=> ,
IN() ,
IS NULL или
IS NOT NULL .
- Дополнительно для индексов
BTREE сравнение
части ключа с постоянной величиной условия диапазона сделано, используя
операторы > ,
< ,
>= ,
<= ,
BETWEEN ,
!= ,
<> или
LIKE ,
если параметр LIKE
постоянная строка, которая не начинается с подстановочного символа.
- Для всех индексных типов многократные условия диапазона, объединены с
OR или
AND .
Постоянная величина
в предыдущих описаниях означает одно из следующего:
Вот некоторые примеры запросов с условиями диапазона в WHERE :
SELECT * FROM t1 WHERE key_col > 1 AND
key_col < 10;
SELECT * FROM t1 WHERE key_col = 1
OR key_col IN (15,18,20);
SELECT * FROM t1 WHERE key_col LIKE 'ab%' OR
key_col BETWEEN 'bar' AND 'foo';
Некоторые непостоянные величины могут быть преобразованы в
константы во время постоянной фазы оптимизации.
MySQL пытается извлечь условия диапазона из WHERE
для каждого из возможных индексов. Во время процесса извлечения удалены
условия, которые не могут использоваться для того, чтобы создать условие
диапазона, условия, которые производят накладывающиеся диапазоны, объединены,
а условия, которые производят пустые диапазоны, удалены.
Рассмотрите следующий запрос, где key1
индексированный столбец и nonkey не индексирован:
SELECT * FROM t1 WHERE (key1 < 'abc' AND (key1 LIKE 'abcde%' OR
key1 LIKE '%b')) OR (key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z');
Процесс извлечения для ключа key1 :
Начнем с оригинального WHERE :
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z')
Удалим nonkey = 4 и key1 LIKE '%b'
потому что они не могут использоваться для просмотра диапазона. Правильный
способ удалить их состоит в том, чтобы заменить их TRUE ,
так, чтобы мы не пропустили строк соответствия, делая просмотр диапазона.
Заменив их TRUE , получим:
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
(key1 < 'bar' AND TRUE) OR
(key1 < 'uux' AND key1 > 'z')
Уберем условия, которые всегда являются истиной или ложью:
Заменяя эти условия константами, получим:
(key1 < '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 .
9.2.1.3.2.
Метод доступа диапазона для нескольких частей индексов
Условия диапазона для нескольких частей индексов это расширение условий
диапазона для единственной части индекса. Условие диапазона для нескольких
частей индексов ограничивает индексируемые строки, чтобы они были в пределах
одного или нескольких ключевых интервалов кортежа. Ключевые интервалы кортежа
определены по ряду ключевых кортежей, используя упорядочивание из индекса.
Например, полагайте, что многократная часть индекса определена как
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
AND key_part2 cmp const2
AND ...
AND key_partN cmp constN ;
Здесь const1 , const2
являются константами, cmp один из операторов сравнения
= ,
<=> или
IS NULL , а
условия покрывают все индексные части. Таким образом, есть
N условий, одно для каждой части индекса из
N частей. Например, следующее условие диапазона для
трехчастного индекса HASH :
key_part1 = 1 AND key_part2
IS NULL AND key_part3 = 'foo'
Для определения константы см.
раздел 9.2.1.3.1.
- Для индекса
BTREE интервал мог бы быть применимым для
условий, объединенных с AND
, где каждое условие сравнивает ключевую часть с постоянной величиной
с использованием
= ,
<=> ,
IS NULL ,
> ,
< ,
>= ,
<= ,
!= ,
<> ,
BETWEEN или
LIKE 'pattern
' (здесь 'pattern '
не начинается с подстановочного знака). Интервал может использоваться, пока
возможно определить единственный ключевой кортеж, содержащий все строки,
которые соответствуют условию (или два интервала, если используется
<> или
!= ).
Оптимизатор пытается использовать дополнительные части ключей, чтобы
определить интервал, пока оператор сравнения
= ,
<=> или
IS NULL . Если оператор
> ,
< ,
>= ,
<= ,
!= ,
<> ,
BETWEEN или
LIKE , оптимизатор
использует это, но не рассматривает больше частей ключа. Для следующего
выражения оптимизатор использует
= из первого сравнения.
Это также использует
>= из второго сравнения, но не рассматривает дальнейших
ключевых частей и не использует третье сравнение для конструкции интервала:
key_part1 = 'foo' AND key_part2
>= 10 AND key_part3 > 10
Единственный интервал:
('foo',10,-inf) < (key_part1 ,
key_part2 ,key_part3 ) <
('foo',+inf,+inf)
Возможно, что создаваемый интервал содержит больше строк,
чем начальное условие. Например, предыдущий интервал включает значение
('foo', 11, 0) , которое не удовлетворяет оригинальное условие.
- Если условия, которые покрывают наборы строк, содержавшихся в пределах
интервалов, объединены с
OR , они формируют условие,
которое покрывает ряд строк, содержавшихся в пределах союза их интервалов.
Если условия объединены с
AND , они формируют условие,
которое покрывает ряд строк, содержавшихся в пересечении их интервалов.
Например, для этого условия с двумя частями индекса:
(key_part1 = 1 AND key_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 AND key_part2 < 2
Но, фактически, условие преобразовано в это:
key_part1 >= 1 AND key_part2 IS NOT NULL
Раздел 9.2.1.3.1
описывает, как оптимизация выполнена, чтобы объединить или устранить
интервалы для условий диапазона на единственной части индекса. Аналогичные
шаги выполнены для условий диапазона на многих частях индекса.
9.2.1.3.3.
Оптимизация диапазона равенства сравнений
Рассмотрите эти выражения, где
col_name индексированный столбец:
col_name IN(val1 , ...,
valN )
col_name = val1 OR
... OR col_name = valN
Каждое выражение истина, если 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 .
9.2.1.3.4.
Ограничение использования памяти для оптимизации диапазона
Чтобы управлять памятью, доступной оптимизатору, используйте
range_optimizer_max_mem_size :
Для отдельных запросов, которые превышают доступную память оптимизации
диапазона и для которого оптимизатор отступает к менее оптимальным планам,
увеличение
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 .
9.2.1.3.5.
Оптимизация диапазона выражений конструктора строки
Оптимизатор в состоянии применить метод доступа просмотра диапазона
для запросов этой формы:
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');
Для оптимизатора, чтобы использовать просмотр диапазона, запросы должны
удовлетворить этим условиям:
См. раздел 9.2.1.20
.
9.2.1.4.
Оптимизация слияния индекса
Метод слияния индекса
используется, чтобы получить строки с несколькими
диапазонами
и слить их результаты в один. Слияние может произвести союзы, пересечения или
союзы пересечений его основных просмотров. Этот метод доступа слияния
индексирует просмотры от единственной таблицы,
это не сливает просмотры через многократные таблицы.
В выводе EXPLAIN метод
слияния появляется как
index_merge в столбце type . В этом случае столбец
key содержит список используемых индексов, а
key_len содержит список самых длинных ключевых частей
для тех индексов.
Примеры:
SELECT * FROM tbl_name
WHERE key1 = 10 OR
key2 = 20;
SELECT * FROM tbl_name
WHERE (key1 = 10 OR
key2 = 20) AND
non_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 AND y ) OR
z = (x OR
z ) AND (y OR
z )
(x OR y ) AND
z = (x AND
z ) OR (y AND
z )
- Слияние неприменимо к полнотекстовому индексу. Мы планируем расширить
это, чтобы покрыть их в будущем выпуске MySQL.
Выбор между различными возможными разновидностями метода доступа
слияния и других методов доступа основан на сметах
различных доступных параметров.
9.2.1.4.1.
Перекрестный алгоритм доступа слияния
Этот алгоритм доступа может использоваться, когда WHERE
был преобразован в несколько условий диапазона на различных ключах,
объединенных с AND ,
и каждое условие одно из следующего:
В этой форме, где индексирование имеет точно
N частей (то есть, все индексные части покрыты):
key_part1 =const1 AND
key_part2 =const2 ... AND
key_partN =constN
- Любой диапазон условия на первичном ключе
InnoDB .
Примеры:
SELECT * FROM innodb_table WHERE
primary_key < 10 AND
key_col1 =20;
SELECT * FROM tbl_name
WHERE (key1_part1 =1 AND
key1_part2 =2) AND
key2 =2;
Перекрестный алгоритм слияния использует одновременные просмотры на всех
используемых индексах и производит пересечение последовательностей строки,
которые получает от слияния просмотров индекса.
Если все столбцы, используемые в запросе, покрыты используемым индексом,
полные строки таблицы не получены
(вывод EXPLAIN включает
Using index в поле Extra ).
Вот пример такого запроса:
SELECT COUNT(*) FROM t1 WHERE key1=1 AND key2=1;
Если используемый индекс не покрывают все столбцы, используемые в запросе,
все строки получены только, когда условия диапазона для всех
используемых ключей удовлетворены.
Если одно из слитых условий это условие по первичному ключу
InnoDB , это не используется для извлечения строки, но
используется, чтобы отфильтровать полученные с использованием
других условий строки.
9.2.1.4.2.
Алгоритм доступа союза слияния
Критерии применимости для этого алгоритма подобны критериям для
перекрестного алгоритма. Алгоритм может использоваться когда
WHERE был преобразован в несколько условий диапазона на
различных ключах, объединенных с OR
, и каждое условие одно из следующего:
В этой форме, где индексирование имеет точно
N частей (то есть, все индексные части покрыты):
key_part1 =const1 AND
key_part2 =const2 ... AND
key_partN =constN
- Любой диапазон условия на первичном ключе
InnoDB .
- Условие, для которого перекрестный алгоритм метода слияния применим.
Примеры:
SELECT * FROM t1 WHERE key1 =1 OR
key2 =2 OR key3 =3;
SELECT * FROM innodb_table WHERE
(key1 =1 AND key2 =2) OR
(key3 ='foo' AND
key4 ='bar') AND key5 =5;
9.2.1.4.3.
Алгоритм доступа сортировки слиянием
Этот алгоритм доступа используется, когда WHERE
был преобразован в несколько условий диапазона, объединенных
OR ,
но для которого алгоритм союза неприменим.
Примеры:
SELECT * FROM tbl_name
WHERE key_col1 < 10 OR
key_col2 < 20;
SELECT * FROM tbl_name
WHERE (key_col1 > 10 OR
key_col2 = 20) AND
nonkey_col =30;
Различие между алгоритмом сортировки союза и алгоритмом союза в том, что
алгоритм сортировки союза должен сначала принести ID строки для всех строк и
сортировать их прежде, чем возвратить любые строки.
9.2.1.5.
Механизм оптимизации выражении
Эта оптимизация улучшает эффективность прямых сравнений между
неиндексированным столбцом и константой. В таких случаях условие
передано механизму хранения для оценки. Эта
оптимизация может использоваться только
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 , чтобы определить, какие
условия фактически применены.
9.2.1.6.
Оптимизация Index Condition Pushdown
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.
9.2.1.7.
Использование индексного расширения
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 байт).
9.2.1.8. Оптимизация IS NULL
MySQL может выполнить ту же самую оптимизацию на
col_name IS
NULL , которую может использовать для
col_name =
constant_value . Например, MySQL
может использовать индекс и диапазоны, чтобы искать NULL с
IS NULL .
Примеры:
SELECT * FROM tbl_name WHERE
key_col IS NULL;
SELECT * FROM tbl_name WHERE
key_col <=> NULL;
SELECT * FROM tbl_name
WHERE key_col =const1 OR
key_col =const2 OR
key_col IS NULL;
Если WHERE включает col_name
IS NULL
для столбца, который объявлен как NOT NULL ,
то выражение оптимизировано. Эта оптимизация не происходит в случаях, когда
столбец мог бы произвести NULL так или иначе, например, если это
прибывает из таблицы на правой стороне LEFT JOIN .
MySQL может также оптимизировать комбинацию
col_name = expr OR
col_name IS NULL ,
форма, которая распространена в решенных подзапросах.
EXPLAIN показывает
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);
9.2.1.9.
Оптимизация LEFT JOIN и RIGHT JOIN
MySQL осуществляет A LEFT JOIN
B join_condition следующим образом:
Выполнение 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.
9.2.1.10.
Алгоритмы соединения вложенной петли
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 использует буферизацию соединения при этих условиях:
Для соединения в качестве примера, описанного ранее для алгоритма 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 является достаточно большим, чтобы содержать все
предыдущие комбинации строки. В этом пункте нет никакой скорости, которая
будет получена, делая это больше.
9.2.1.11. Оптимизация Nested Join
Синтаксис для выражений разрешает вложенные соединения. Следующее
обсуждение обращается к синтаксису соединения, описанному в
разделе 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
имеют следующее состояние:
В этом случае первое выражение возвращает набор результатов, включая
строки (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.
9.2.1.12.
Упрощение Outer Join
Табличные выражения в 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 просты. Условие отклонено нулем в следующих случаях:
Условие может быть отклонено нулем для одной внешней работы соединения в
запросе и не отклонено нулем для другой. В запросе:
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
9.2.1.13.
Оптимизация мультидиапазонного чтения
Чтение строк, используя просмотр диапазона на вторичном индексе, может
привести ко многим случайным дисковым доступам к базовой таблице, когда
таблица является большой и не сохранена в кэше механизма хранения. С
оптимизацией Disk-Sweep Multi-Range Read (MRR) MySQL
пытается сократить количество случайного дискового доступа для просмотров
диапазона первым только просмотром индекса и сбором ключей для
соответствующих строк. Тогда ключи сортированы, и строки получены из базовой
таблицы, используя порядок первичного ключа. Побуждение для Disk-sweep MRR
должно сократить количество случайных дисковых доступов и вместо этого
достигнуть более последовательного просмотра данных базовой таблицы.
Оптимизация Multi-Range Read обеспечивает эту выгоду:
MRR позволяет строкам данных быть полученными доступ
последовательно, а не в случайном порядке, основанном на индексных кортежах.
Сервер получает ряд индексных кортежей, которые удовлетворяют условиям
запроса, сортирует их согласно порядку ID строк данных и использует
сортированные кортежи, чтобы получить строки данных в этом порядке. Это
делает доступ к данным более эффективным и менее дорогим.
- MRR включает пакетную обработку данных запросов о ключевом доступе
для операций, которые требуют доступ к строкам данных через индексные
кортежи, например, просмотры индексного диапазона и equi-соединения, которые
используют индексирование для признака соединения. MRR повторяет по
последовательности диапазоны, чтобы получить индексные кортежи. Поскольку эти
результаты накапливаются, они используются, чтобы получить доступ к
соответствующим строкам данных. Не надо собирать все индексные кортежи
прежде, чем начать читать строки данных.
Оптимизация MRR не поддержана со вторичным индексом
на произведенных виртуальных столбцах.
Следующие примеры иллюстрируют, когда оптимизация MRR может быть выгодной:
Сценарий A: MRR может использоваться для таблиц
InnoDB и MyISAM
для просмотра индексного диапазона и операции equi-соединения.
Часть индексных кортежей накоплена в буфере.
- Кортежи в буфере сортированы по их ID строки данных.
- К строкам данных получают доступ согласно сортированной
последовательности индексных кортежей.
Сценарий B: MRR может использоваться для
NDB для мультидиапазонного индексного
просмотра или выполняя equi-соединение.
Часть диапазонов, возможно, одноключевые диапазоны,
накоплена в буфере на центральном узле, где запрос представлен.
- Диапазоны посылают в узлы выполнения, которые обращаются к строке.
- Строки, к которым получают доступ, упакованы в пакеты и отосланы
назад к центральному узлу.
- Полученные пакеты со строками данных помещены в буфер.
- Строки данных считаны из буфера.
Когда MRR используется, столбец Extra в выводе
EXPLAIN показывает Using MRR
.
InnoDB и MyISAM не используют
MRR, если к полным строкам таблицы нельзя получить доступ, чтобы привести к
результату запроса. Дело обстоит так, если к результатам можно привести
полностью на основе информации в индексных кортежах (посредством
покрытия индекса), MRR
не обеспечивает выгоды.
Запрос в качестве примера, для которого MRR может использоваться,
предполагая, что есть индексирование на (key_part1
, key_part2 ) :
SELECT * FROM t WHERE key_part1 >= 1000 AND
key_part1 < 2000 AND
key_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
байт и определяет число диапазонов, чтобы
обработать в единственном проходе.
9.2.1.14.
Вложенная петля блока и пакетные ключевые соединения доступа
В MySQL алгоритм Batched Key Access (BKA) Join
доступен, который использует индексный доступ к таблице, к которой
присоединяются, и буфер соединения. Алогритм BKA поддерживает внутреннее
соединение, внешнее соединение, и операции полусоединения, включая вложенные
внешние соединения. Выгода BKA включает улучшенную работу соединения из-за
более эффективного табличного просмотра. Кроме того, алгоритм Block
Nested-Loop (BNL) Join, ранее используемый только для внутренних соединений,
расширен и может использоваться для внешнего соединения и операций
полусоединения, включая вложенные внешние соединения.
Следующие разделы обсуждают буферное управление соединением, которое лежит
в основе расширения оригинального алгоритма BNL, расширенного алгоритма BNL,
и алгоритма BKA. См. раздел 9.2.1.18.1.
9.2.1.14.1.
Буферное управление соединением для алгоритмов
Block Nested-Loop и Batched Key Access
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.
9.2.1.14.2.
Алгоритм Block Nested-Loop для Outer Join и Semi-Join
Оригинальное выполнение 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.
9.2.1.14.3. Batched Key Access
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 .
9.2.1.15. Оптимизация ORDER BY
В некоторых случаях MySQL может использовать индексирование, чтобы
удовлетворить ORDER BY , не делая дополнительную сортировку.
Индексирование может также использоваться даже если ORDER BY
не соответствует индексу точно, пока все неиспользованные части индекса
и всех дополнительных столбцов ORDER BY это константы в
WHERE . Следующие запросы используют индексирование, чтобы решить
часть ORDER BY :
SELECT * FROM t1 ORDER BY key_part1 ,
key_part2 ,... ;
SELECT * FROM t1
WHERE key_part1 = constant
ORDER BY key_part2 ;
SELECT * FROM t1 ORDER BY key_part1 DESC,
key_part2 DESC;
SELECT * FROM t1 WHERE key_part1 = 1
ORDER BY key_part1 DESC,
key_part2 DESC;
SELECT * FROM t1 WHERE key_part1 > constant
ORDER BY key_part1 ASC;
SELECT * FROM t1 WHERE key_part1 < constant
ORDER BY key_part1 DESC;
SELECT * FROM t1
WHERE key_part1 = constant1
AND key_part2 > constant2
ORDER BY key_part2 ;
В некоторых случаях MySQL не может использовать
индекс, чтобы решить ORDER BY , хотя это все еще использует
индекс, чтобы найти строки, которые соответствуют WHERE .
Эти случаи включают следующее:
Использование запроса ORDER BY на различных индексах:
SELECT * FROM t1 ORDER BY key1 ,
key2 ;
- Использование запроса
ORDER BY на
непоследовательных частях индекса:
SELECT * FROM t1 WHERE key2 =constant
ORDER BY key_part2 ;
- Смесь запросов
ASC и DESC :
SELECT * FROM t1 ORDER BY key_part1 DESC,
key_part2 ASC;
- Индексирование, которое используется, чтобы принести строки,
отличается от используемого в
ORDER BY :
SELECT * FROM t1 WHERE key2 =constant
ORDER BY key1 ;
- Использование запроса
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 .
- Для каждой строки в буфере сортировки хранится кортеж, состоящий из пары
значений (значение ключа сортировки и ID строки).
- Если все пары вписываются в буфер, никакой временный файл не создается.
Иначе, когда буфер становится полным, выполняется qsort (quicksort)
в памяти и пишется результат во временный файл. Сохраняется указатель
на отсортированный блок.
- Повторите предыдущие шаги, пока все строки не считаны.
- Сделайте мультислияние до
MERGEBUFF (7) к одному блоку в
другом временном файле. Повторите, пока все блоки от первого файла не будут
во втором файле.
- Повторите пока нет меньше, чем
MERGEBUFF2 (15) блоков.
- На последнем мультислиянии только ID строки (последняя часть пары
значения) написано в файл результата.
- Считайте строки в сортированном порядке, используя ID
строки в файле результата. Чтобы оптимизировать это, читайте в большом блоке
ID строки, сортируйте их, и используйте их, чтобы считать строки в
сортированном порядке в буфер строки. Размер буфера строки
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 .
- Используйте меньше RAM на строку, объявляя столбцы строго столь большими,
как они должны хранить значения. Например,
CHAR(16) лучше, чем
CHAR(200) , если значения никогда не превышают 16 символов.
- Измените
tmpdir ,
чтобы указать на специализированную файловую систему с большим количеством
свободного пространства. Переменное значение может перечислить несколько
путей, которые используются круговым способом, Вы можете использовать эту
функцию, чтобы распространить загрузку на несколько каталогов. Пути должны
быть отделены символами двоеточия (: ) в Unix и символами
точки с запятой (; ) в Windows. Пути должны назвать каталоги в
файловых системах, расположенных на
различных физических дисках.
Если индексирование не используется для ORDER BY , но
LIMIT также присутствует, оптимизатор может быть в состоянии
избегать использования файла слияния и сортировать строки в памяти. См.
раздел 9.2.1.19.
9.2.1.16. Оптимизация GROUP BY
Самый общий способ удовлетворить GROUP BY
должен просмотреть целую таблицу и составить новую временную таблицу, где
все строки от каждой группы последовательны, а затем использовать эту
временную таблицу, чтобы обнаружить группы и применить совокупные функции
(если есть). В некоторых случаях MySQL в состоянии сделать намного лучше
и избежать создания временных таблиц при использовании индекса.
Самые важные предварительные условия для того, чтобы использовать индекс
для GROUP BY это все ссылочные признаки столбцов
GROUP BY от того же самого индекса, и что ключи индекса
хранятся в порядке (например, это индекс BTREE , но не
HASH ). Может ли использование временных таблиц быть заменено
индексным доступом, также зависит от того, на которой части индексирования
используются в запросе условия, определенные для этих частей
и выбранных совокупных функций.
Есть два способа выполнить запрос GROUP BY
через индексный доступ, как детализировано в следующих разделах. В первом
методе группировка применена вместе со всеми предикатами диапазона (если
есть). Второй метод сначала выполняет просмотр диапазона, затем
группирует получающиеся кортежи.
В MySQL GROUP BY используется для того, чтобы сортировать,
таким образом, сервер может также применить ORDER BY
к группировке. Однако, доверие неявному GROUP BY устарело, см.
раздел 9.2.1.15.
9.2.1.16.1. Свободный индексный просмотр
Самый эффективный способ обработать GROUP BY , когда
индексирование используется, чтобы непосредственно получить группирующиеся
столбцы. С этим методом доступа MySQL использует свойство некоторых типов
индекса, что ключи упорядочены (например, BTREE ).
Это свойство включает использование групп поиска в индексировании, не имея
необходимость рассматривать все ключи в индексировании, которые удовлетворяют
все выражения WHERE . Этот метод доступа рассматривает только
фракцию ключей в индексировании, таким образом, это называют
свободный просмотр индекса. Когда нет WHERE ,
свободный просмотр читает столько ключей, сколько имеется групп, что
может быть намного меньшим числом, чем все ключи. Если WHERE
содержит предикаты диапазона (см. обсуждение
range в
разделе 9.8.1), свободный просмотр
ищет первый ключ каждой группы, которая удовлетворяет условиям диапазона, и
снова читает наименее возможное число ключей.
Это возможно при следующих условиях:
Если свободный просмотр, применим к запросу,
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() :
Предположите, что есть индекс 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;
9.2.1.16.2. Трудный индексный просмотр
Трудный индексный просмотр может быть полным просмотром
или просмотром диапазона, в зависимости от условий запроса.
Когда условия для свободного индексного просмотра не встречены,
все еще может быть возможно избежать создания временных таблиц для
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;
9.2.1.17. Оптимизация DISTINCT
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;
9.2.1.18. Оптимизация подзапроса
Оптимизатор запроса MySQL имеет различные стратегии, чтобы
оценить подзапросы. Для IN (или =ANY )
у оптимизатора есть этот выбор:
Semi-join
- Материализация
EXISTS
Для NOT IN (или <>ALL )
у оптимизатора есть этот выбор:
Для полученных таблиц (подзапросы в FROM )
и ссылок представления у оптимизатора есть этот выбор:
Следующее обсуждение предоставляет больше информации об
этих стратегиях оптимизации.
Ограничение UPDATE и
DELETE , которые используют
подзапрос, чтобы изменить единственную таблицу, это когда оптимизатор не
использует полусоединение или оптимизацию подзапроса материализации.
Как обходное решение, попытайтесь переписать их как многотабличный
UPDATE и
DELETE , которые используют
соединение, а не подзапрос.
9.2.1.18.1.
Оптимизация подзапросов с преобразованиями полусоединения
Оптимизатор использует стратегии полусоединения, чтобы улучшить выполнение
подзапроса, как описано в этом разделе.
Для внутреннего соединения между двумя таблицами соединение возвращает
строку из одной таблицы так много раз, сколько есть соответствий в другой
таблице. Но для некоторых запросов, единственная информация, это есть ли
соответствие, а не число соответствий. Предположите, что есть таблицы
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 .
- Временное табличное использование для Duplicate Weedout обозначено
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 > .
9.2.1.18.2.
Оптимизация подзапросов с материализацией подзапроса
Оптимизатор использует материализацию подзапроса в качестве стратегии,
которая включает более эффективную обработку подзапроса. Материализация
ускоряет выполнение запроса, производя результат подзапроса как временную
таблицу, обычно в памяти. В первый раз, когда 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 (SELECT ie_1 ,
i_2 , ..., ie_N ...)
- У предиката есть эта форма, когда есть единственное внешнее выражение
oe и внутреннее выражение ie .
Выражения могут быть null.
oe [NOT] IN (SELECT ie ...)
- Предикат
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 .
9.2.1.18.3.
Оптимизация полученных таблиц и ссылок представления
Оптимизатор может обработать полученные таблицы (подзапросы в
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
привел бы к более высокой стоимости, чем некоторый другой метод доступа,
оптимизатор не создает индекс и ничего не теряет.
9.2.1.18.4.
Оптимизация подзапросов со стратегией EXISTS
Определенная оптимизация применима к сравнениям, которые используют
оператор IN , чтобы проверить результаты подзапроса (или
использующие =ANY ). Этот раздел обсуждает эту оптимизацию,
особенно относительно проблем со значениями NULL .
Последняя часть обсуждения включает предложения о том, что Вы можете сделать,
чтобы помочь оптимизатору.
Рассмотрите следующее сравнение подзапроса:
outer_expr IN (SELECT inner_expr
FROM ... WHERE subquery_where )
MySQL оценивает запросы снаружи внутрь.
Таким образом, это сначала получает значение внешнего выражения
outer_expr , затем выполняет подзапрос и получает
строки, которые он производит.
Очень полезная оптимизация информировать
подзапрос, что единственные интересные строки это те, где внутреннее
выражение inner_expr равно outer_expr
. Это сделано, отталкивая соответствующее равенство в подзапрос
WHERE . Таким образом, сравнение преобразовано в это:
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
outer_expr =inner_expr )
После преобразования MySQL может использовать продвинутое вниз равенство,
чтобы ограничить число строк, которые это должно
исследовать, оценивая подзапрос.
Более широко, сравнение N значений с
подзапросом, который возвращает N строк,
подвергается тому же самому преобразованию. Если oe_i и
ie_i представляют соответствующие внешние и внутренние
значения выражения, это сравнение подзапроса:
(oe_1 , ..., oe_N ) IN
(SELECT ie_1 , ..., ie_N
FROM ... WHERE subquery_where )
становится:
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
oe_1 = ie_1 AND ... AND
oe_N = ie_N )
Для простоты следующее обсуждение принимает единственную пару внешних и
внутренних значений выражения.
У только что описанного преобразования есть свои ограничения.
Это допустимо, только если мы игнорируем возможный NULL .
Таким образом, стратегия pushdown работает,
пока оба эти условия являются истиной:
outer_expr и
inner_expr не NULL .
- Вы не должны различать
NULL и FALSE в
результатах подзапроса. Если подзапрос часть
OR или AND
в WHERE , MySQL предполагает, что Вы их не различаете.
Другой случай, где оптимизатор замечает, что результаты подзапроса
NULL и FALSE нельзя отличить, это конструкция:
... WHERE outer_expr IN (subquery )
В этом случае WHERE отклоняет строку
IN (subquery ) независимо от того, вернет
она NULL или FALSE .
Когда одно или оба из этих условий не выполнены, оптимизация более сложна.
Предположите, что outer_expr значение не
NULL , но подзапрос не производит строку таким образом, что
outer_expr = inner_expr .
outer_expr IN (SELECT ...)
оценивается следующим образом:
В этой ситуации поиск строк с
outer_expr =
inner_expr больше не действителен. Необходимо
искать такие строки, но если ни одна не найдена, также искать строки, где
inner_expr = NULL .
Примерно говоря, подзапрос может быть преобразован во что-то вроде этого:
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
(outer_expr =inner_expr OR
inner_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 (SELECT inner_expr FROM ...
WHERE subquery_where )
Необходимо выполнить оригинал
SELECT
без любых продвинутых вниз равенств, упомянутых ранее.
С другой стороны, когда
outer_expr не NULL ,
абсолютно важно, что это сравнение:
outer_expr IN (SELECT inner_expr
FROM ... WHERE subquery_where )
будет преобразовано в это выражение, которое использует
продвинутое вниз условие:
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
outer_expr =inner_expr )
Без этого преобразования подзапросы будут медленными. Чтобы решить дилемму
того, оттолкнуть или не оттолкнуть условия в подзапрос, условия обернуты в
функции триггера.
Таким образом, выражение следующей формы:
outer_expr IN (SELECT inner_expr
FROM ... WHERE subquery_where )
преобразовано в:
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
trigcond(outer_expr =inner_expr ))
Более широко, если сравнение подзапроса основано на нескольких парах
внешних и внутренних выражений, преобразование берет это сравнение:
(oe_1 , ..., oe_N ) IN
(SELECT ie_1 , ..., ie_N
FROM ... WHERE subquery_where )
и приводит к этому выражению:
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
trigcond(oe_1 =ie_1 ) AND
... AND trigcond(oe_N =ie_N ))
trigcond(X ) это специальная функция,
которая оценивается к следующим значениям:
Триггерные функции это не триггеры в смысле
запроса CREATE TRIGGER .
Равенства, которые обернуты в trigcond() , это
не предикаты первого класса для оптимизатора. Большинство оптимизации не
может иметь дело с предикатами, которые могут быть включены во время
выполнения запроса, таким образом, они принимают любую
trigcond(X ) как
неизвестную функцию и проигнорируют это. В настоящее время вызванные
равенства могут использоваться этой оптимизацией:
Когда оптимизатор использует вызванное условие, чтобы создать некоторый
основанный на индексном поиске доступ (что касается первых двух элементов
предыдущего списка), у этого должна быть стратегия отступления для случая,
когда условие выключено. Эта стратегия отступления всегда сделать полное
сканирование таблицы. В выводе 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 (SELECT inner_expr FROM ...)
вот на такое:
(outer_expr IS NOT NULL) AND (outer_expr
IN (SELECT inner_expr FROM ...))
Тогда NULL IN (SELECT ...)
никогда не будет оцениваться, потому что MySQL прекращает оценивать часть
AND
как только результат выражения ясен.
Другая возможная перезапись:
EXISTS (SELECT inner_expr FROM ...
WHERE inner_expr =outer_expr )
Это применилось бы, когда Вы не должны различить NULL и
FALSE , когда Вы можете фактически хотеть EXISTS .
Флаг subquery_materialization_cost_based
включает управление выбором между материализацией подзапроса и
преобразованием подзапроса IN -to-EXISTS . См.
раздел 9.9.2.
9.2.1.19. Оптимизация запроса LIMIT
Если Вы нуждаетесь только в конкретном количестве строк от набора
результатов, используйте 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 .
- Как только MySQL послал необходимое число строк клиенту, это прерывает
запрос, если Вы не используете
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 ... FROM single_table ...
ORDER BY non_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 и размера строки.
9.2.1.20.
Оптимизация выражения конструктора строки
Конструкторы строки разрешают одновременные сравнения многократных
значений. Например, эти два запроса семантически эквивалентны:
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
.
9.2.1.21.
Как избежать полного сканирования таблицы
Вывод EXPLAIN показывает
ALL в столбце
type , когда MySQL использует
полный просмотр таблицы,
чтобы решить запрос. Это обычно происходит при следующих условиях:
Для маленьких таблиц сканирование таблицы часто является соответствующим,
и исполнительное воздействие незначительно. Для больших таблиц
попробуйте следующие методы, чтобы избежать того, что оптимизатор
неправильно выбирает сканирование таблицы:
Используйте ANALYZE TABLE tbl_name
, чтобы обновить ключевые распределения для просмотренной таблицы. См.
раздел 14.7.2.1.
- Используйте
FORCE INDEX для просмотренной таблицы, чтобы
сказать MySQL, что сканирование таблицы очень дорого по сравнению с
использованием данного индекса:
SELECT * FROM t1, t2 FORCE INDEX (index_for_column )
WHERE t1.col_name =t2.col_name ;
См. раздел 9.9.4.
- Запустите mysqld
с опцией
--max-seeks-for-key=1000 или примените
SET max_seeks_for_key=1000 , чтобы сказать оптимизатору
предполагать, что никакой ключевой просмотр не вызывает больше, чем 1000
ключевых поисков, см. раздел
6.1.5.
9.2.2.
Оптимизация запросов изменения данных
Этот раздел объясняет, как ускорить запросы изменения данных:
INSERT ,
UPDATE и
DELETE .
Традиционные приложения OLTP и современные веб-приложения, как правило,
делают много маленьких операций изменения данных, где параллелизм жизненно
важен. Анализ данных, как правило, выполняет операции изменения данных,
которые затрагивают много строк сразу, где основные соображения это
ввод/вывод, чтобы написать большие объемы данных и сохранить индекс
современным. Для вставки и обновления больших объемов данных (известный в
промышленности как ETL, от extract-transform-load),
иногда Вы используете другие запросы SQL или внешние команды, которые
имитируют эффекты INSERT ,
UPDATE и
DELETE .
9.2.2.1. Скорость INSERT
Чтобы оптимизировать скорость вставок, комбинируют много маленьких
операций в единственную большую работу. Идеально, Вы делаете единственное
соединение, посылаете данные многих новых строк сразу и задерживаете все
индексные обновления и проверку последовательности до самого конца.
Время, требуемое для того, чтобы вставить строку, определено следующими
факторами, где числа указывают на приблизительные пропорции:
Соединение: (3)
- Посылка запроса к серверу: (2)
- Парсинг запроса: (2)
- Вставка строки: (размер строки)
- Вставка индексов: (число индексов)
- Закрытие: (1)
Это не учитывает начальные издержки, чтобы открыть таблицы, которые
сделаны однажды для каждого одновременно работающего запроса.
Размер таблицы замедляет вставку индексов на
log N для индексов B-tree.
Вы можете использовать следующие методы, чтобы убыстрить вставку:
Если Вы вставляете много строк от того же самого клиента в то же
самое время, стоит использовать INSERT
со списком VALUES , чтобы вставить несколько строк за один
раз. Это значительно быстрее (во много раз быстрее в некоторых случаях), чем
использование отдельного однострочного запроса
INSERT .
Если Вы добавляете данные к непустой таблице, Вы можете настроить переменную
bulk_insert_buffer_size , чтобы сделать вставку данных еще быстрее.
См. раздел 6.1.5.
- Загружая таблицу из текстового файла, лучше использовать
LOAD DATA INFILE .
Это обычно в 20 раз быстрее, чем использование
INSERT в любом виде.
См. раздел 14.2.6.
- Используйте в своих интересах факт, что у столбцов есть значения по
умолчанию. Вставьте значения явно только, когда значение, которое будет
вставлено, отличается от значения по умолчанию. Это уменьшает парсинг,
который MySQL должен сделать и улучшает скорость вставки.
- См. раздел
9.5.5 для подсказок, определенных для таблиц
InnoDB .
- См. раздел
9.6.2 для подсказок, определенных для таблиц
MyISAM .
9.2.2.2. Скорость UPDATE
Запрос обновления оптимизирован как
SELECT . Скорость записи зависит от
обновляемого объема данных и числа индексов, которые обновлены. Индексы,
которые не изменены, не становятся обновленными.
Другой способ получить быстрые обновления состоит в том, чтобы задержать
обновления и затем сделать много обновлений подряд позже. Выполнение
многократных обновлений вместе намного более быстро, чем выполнение
по одному, если Вы блокируете таблицу.
Для таблицы MyISAM , которая использует динамический формат
строки, обновление строку к большей полной длине может разделить строку. Если
Вы часто делаете это, очень важно использовать
OPTIMIZE TABLE .
См. раздел 14.7.2.4.
9.2.2.3. Скорость DELETE
Время, требуемое, чтобы удалить отдельные строки в
MyISAM , точно пропорционально числу индексов. Чтобы удалить
строки более быстро, Вы можете увеличить размер ключевого кэша, увеличивая
key_buffer_size .
См. раздел 6.1.1.
Чтобы удалить все строки из MyISAM ,
TRUNCATE TABLE tbl_name быстрей, чем
DELETE FROM tbl_name .
Усечение не безопасно для транзакции; ошибка происходит в ходе активной
транзакции или активной табличной блокировки. См.
раздел 14.1.30.
9.2.3.
Оптимизация привилегий базы данных
Чем более сложна Ваша установка привилегии, тем больше издержек
относится ко всем запросам SQL. Упрощение привилегий, установленных
GRANT ,
позволяет MySQL уменьшить издержки проверки разрешения, когда клиенты
выполняют запросы. Например, если Вы не предоставляете привилегий на уровне
столбца или на уровне таблицы, сервер никогда не должен проверять содержание
таблиц tables_priv и columns_priv .
Точно так же, если Вы не устанавливаете границ ресурса для каких-либо учетных
записей, сервер не должен выполнять подсчет ресурса. Если Вы имеете очень
высокую обрабатывающую запрос загрузку, рассмотрите использование упрощенной
структуры полномочий, чтобы уменьшить проверку разрешений.
9.2.4.
Оптимизация запросов 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 .
9.2.5.
Оптимизация запросов Performance Schema
Приложения, которые контролируют базы данных, могут сделать частое
использование таблиц 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 подобны хеш-индексам, например:
См. раздел 9.3.8.
9.2.6.
Другие подсказки по оптимизации
Этот раздел перечисляет много разных подсказок для того, чтобы улучшить
скорость обработки запроса:
Если Ваше приложение обращается с несколькими запросами, чтобы
выполнить связанные обновления, комбинирование запросов в сохраненную
подпрограмму может помочь работе. Точно так же, если Ваше приложение
вычисляет единственный результат, основанный на нескольких значениях
столбцов, или большие объемы данных, комбинирование вычислений в UDF
(определяемая пользователем функция) может помочь работе. Получающиеся
быстрые операции базы данных тогда доступны, чтобы быть снова использованными
другими запросами, приложениями и даже программам, написанным на различных
языках программирования. См. разделы
21.2 и
26.4.
- Установить любые проблемы сжатия, которые происходят с таблицами
ARCHIVE , используйте
OPTIMIZE TABLE . См.
раздел 17.5.
- Если возможно, классифицируйте отчеты как live
или как statistical, где данные, необходимые для
статистических отчетов, создаются только из сводных таблиц, которые
периодически производятся от живых данных.
- Если у Вас есть данные, которые не соответствуют хорошо структуре
таблицы строк-и-столбцов, Вы можете упаковать и хранить данные в столбце
BLOB . В этом случае Вы должны
обеспечить код в своем приложении, чтобы упаковать и распаковать информацию,
но это могло бы сохранить операции ввода/вывода, чтобы считать и написать
наборы связанных значений.
- С веб-серверами, архивами изображений и другими двоичными активами
храните их как файлы с путем, сохраненным в базе данных. Большинство
веб-серверов лучше в кэшируемых файлах, чем в содержании базы данных, так как
использование файлов вообще быстрее. Хотя Вы должны обработать резервные
копии и проблемы хранения самостоятельно в этом случае.
- Если Вы нуждаетесь в действительно высокой скорости, смотрите на низкий
уровень интерфейсов MySQL. Например, получая доступ к механизмам хранения
InnoDB или MyISAM напрямую, Вы можете получить
существенное увеличение скорости по сравнению с
использованием интерфейса SQL.
- Репликация может обеспечить исполнительную выгоду для некоторых операций.
Вы можете распределить извлечения клиента среди серверов, чтобы разделить
загрузку. Чтобы избежать замедлять ведущее устройство, делая резервные копии,
Вы можете сделать резервные копии, используя ведомый сервер. См.
главу 19.
9.3. Оптимизация и индексы
Лучший способ улучшить исполнение
SELECT это
создать индексы на одном или больше столбцов, которые проверены в запросе.
Индексированные записи действуют как указатели на строки таблицы, позволяя
запросу быстро определить, какие строки соответствуют условию в
WHERE и получить другие значения столбцов для тех строк. Все
типы данных MySQL могут быть индексированы.
Хотя может быть заманчиво создать индексирование для каждого возможного
столбца, используемого в запросе, не нужный индекс тратит ненужное
пространство и напрасно тратит время для MySQL, чтобы определить, который
индекс использовать. Индексирование также добавляет свой вклад к стоимости
вставок, обновлений и удалений, потому что каждый индекс должен быть
обновлен. Вы должны найти правильный баланс, чтобы достигнуть быстрых
запросов, используя оптимальный набор индексов.
9.3.1. Как MySQL использует индексы
Индекс используются, чтобы найти строки с определенными значениями
столбцов быстро. Без индексирования MySQL должен начать с первой строки и
затем прочитать всю таблицу, чтобы найти соответствующие строки. Чем больше
таблица, тем больше это стоит. Если у таблицы есть индексирование для
рассматриваемых столбцов, MySQL может быстро определить позицию, чтобы искать
в середине файла с данными, не имея необходимости смотреть на все данные.
Это намного быстрее, чем чтение каждой строки последовательно.
Большинство индексов MySQL (PRIMARY KEY ,
UNIQUE , INDEX и FULLTEXT )
сохранены в B-tree.
Исключения: индексы на пространственных типах данных применяют R-tree,
таблицы MEMORY также поддерживают
hash-индексы, InnoDB
использует инвертированные списки для индексов FULLTEXT .
Вообще, индексы используются как описано в следующем обсуждении.
Характеристики, определенные для хеш-индекса (как используется в таблицах
MEMORY ), описаны в
разделе 9.3.8.
MySQL применяет индексы для этих операций:
Найти строки, соответствующие WHERE .
- Устранить строки из соображения. Если есть выбор между многими индексами,
MySQL обычно использует индексирование, которое находит самое маленькое число
строк (самый отборный индекс).
- Если у таблицы есть многостолбцовый индекс, любой левый префикс
индексирования может использоваться оптимизатором, чтобы искать строки.
Например, если у Вас есть индекс на трех столбцах
(col1, col2, col3) , Вы можете искать по индексу на
(col1) , (col1, col2) и (col1, col2, col3)
. См. раздел 9.3.5.
- Получать строки от других таблиц, выполняя соединения. MySQL может
использовать индекс на столбцах более эффективно, если они объявлены как тот
же самый тип и размер. В этом контексте
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 )
FROM tbl_name WHERE key_part1 =10;
Чтобы сортировать или сгруппировать таблицу, если сортировка или
группировка сделаны на левом префиксе применимого индексирования (например,
ORDER BY key_part1 ,
key_part2 ). Если все ключевые части
сопровождаются DESC , ключ считан в обратном порядке. См. разделы
9.2.1.15 и
9.2.1.16.
- В некоторых случаях запрос может быть оптимизирован, чтобы получить
значения, не консультируясь со строками данных. Индексирование, которое
обеспечивает все необходимые результаты для запроса, называют
покрывающим.
Если запрос использует от таблицы только столбцы, которые включены в
некоторые индексы, выбранные значения могут быть получены от индексного
дерева для большей скорости:
SELECT key_part3 FROM tbl_name
WHERE key_part1 =1
Индексы менее важны для запросов на маленьких таблицах или больших
таблицах, где запросы обрабатывают больше всего или все строки. Когда запрос
должен получить доступ к большинству строк, читать последовательно быстрее,
чем работа посредством индексирования. Последовательные чтения минимизируют
дисковый поиск, даже если не все строки необходимы для запроса. См.
раздел 9.2.1.21.
9.3.2. Используя первичные ключи
Первичный ключ для таблицы представляет столбец или набор столбцов,
которые Вы используете в своих самых жизненных запросах. У этого есть
связанный индекс для быстрой работы запроса. Работа запроса извлекает выгоду
из оптимизации NOT NULL , поскольку это не может включать
значения NULL . С InnoDB табличные данные физически
организованы, чтобы сделать ультрабыстрые поиски и сортировки, основанные на
столбце или столбцах первичного ключа.
Если Ваша таблица является большой и важной, но не имеет очевидного
столбца или набора столбцов, чтобы использовать в качестве первичного ключа,
Вы могли бы создать отдельный столбец со значениями auto-increment, чтобы
использовать в качестве первичного ключа. Эти уникальные ID могут служить
указателями на соответствующие строки в других таблицах, когда Вы
присоединяетесь к таблицам, используя внешние ключи.
9.3.3.
Используя внешние ключи
Если у таблицы есть много столбцов, и Вы запрашиваете много различных
комбинаций столбцов, могло бы быть эффективно разделить менее часто
используемые данные на отдельные таблицы с несколькими столбцами
и связать их с основной таблицей, дублируя числовой столбец ID
от основной таблицы. Таким путем у каждой маленькой таблицы может быть
первичный ключ для быстрых поисков данных, и Вы можете запросить только набор
столбцов, в котором Вы нуждаетесь при использовании работы соединения.
В зависимости от того, как распределены данные, запросы могли бы выполнить
меньше ввода/вывода и занять меньше кэш-памяти, потому что соответствующие
столбцы упакованы вместе на диске. Чтобы максимизировать работу, запросы
пытаются читать так мало блоков данных, насколько возможно с диска, таблицы
только с несколькими столбцами могут приспособить больше строк в
каждом блоке данных.
9.3.4. Столбец индекса
Наиболее распространенный тип индекса вовлекает единственный столбец,
храня копии значений от того столбца в структуре данных, позволяя быстрые
поиски для строк с соответствующими значениями столбцов. Структура данных
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
Индексы FULLTEXT используются для полнотекстовых поисков.
Только InnoDB и
MyISAM
поддерживают индексы FULLTEXT и только для столбцов
CHAR ,
VARCHAR и
TEXT .
Индексация всегда имеет место по всему столбцу, префикс не используется.
См. раздел 13.9.
Оптимизация применена к определенным видам запросов FULLTEXT .
Запросы с этими характеристиками особенно эффективны:
Пространственный индекс
Вы можете создать индексы на пространственных типах данных.
MyISAM и InnoDB понимают индексы R-tree
на пространственных типах. Другие механизмы хранения используют B-деревья для
того, чтобы индексировать пространственные типы (за исключением
ARCHIVE , который не поддерживает пространственную индексацию).
Индексы в механизме хранения MEMORY
MEMORY применяет по умолчанию индексы
HASH , но также и BTREE .
9.3.5. Многостолбцовые индексы
MySQL может создать сводные индексы (то есть, индекс на многих столбцах).
Индексирование может состоять из 16 столбцов. Для определенных типов данных
Вы можете индексировать префикс столбца (см.
раздел 9.3.4).
MySQL может использовать многостолбцовый индекс для запросов, которые
проверяют все столбцы в индексировании или запросы, которые проверяют только
первый столбец, первые два столбца, первые три столбца и так далее. Если Вы
определяете столбцы в правильном порядке в определении индекса, единственный
сводный индекс может ускорить несколько видов запросов на той
же самой таблице.
Многостолбцовый индекс может считаться сортированным массивом, строки
которого содержат значения, которые созданы, связывая
значения индексированных столбцов.
Как альтернатива сводному индексу, Вы можете ввести столбец, который
является хэшем, основанный на информации от других
столбцов. Если этот столбец короток, разумно уникален и индексирован, это
могло бы быть быстрее, чем широкий индекс
на многих столбцах. В MySQL очень удобен этот дополнительный столбец:
SELECT * FROM tbl_name
WHERE hash_col =MD5(CONCAT(val1 ,
val2 )) AND
col1 =val1 AND
col2 =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 * FROM tbl_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 * FROM tbl_name WHERE col1=val1 ;
SELECT * FROM tbl_name WHERE col1=val1 AND
col2=val2 ;
SELECT * FROM tbl_name WHERE col2=val2 ;
SELECT * FROM tbl_name WHERE col2=val2 AND
col3=val3 ;
Если индексирование существует на (col1, col2, col3) ,
только первые два запроса используют индексирование. Третий и четвертый
запросы действительно вовлекают индексированные столбцы, но
(col2) и (col2, col3) не префиксы для
(col1, col2, col3) .
9.3.6.
Подтверждение использования индекса
Всегда проверяйте, используют ли все Ваши запросы действительно
индексирование, которое Вы создали в таблицах. Используйте
EXPLAIN , как описано в
разделе 9.8.1.
9.3.7. Набор индексной статистики
InnoDB и MyISAM
Механизмы хранения собирают статистические данные о таблицах для
использования оптимизатором. Табличные статистические данные основаны на
группах значения, где группа значения это ряд строк с тем же самым ключевым
значением префикса. В целях оптимизатора важная статистическая величина это
групповой размер среднего значения.
MySQL использует групповой размер среднего значения следующими способами:
Чтобы оценить, как строки должны быть считаны для каждого
доступа ref .
- Оценить, сколько произведет строка частичных соединений,
то есть, число строк, которые произведет работа этой формы:
(...) JOIN tbl_name ON
tbl_name .key = expr
Как групповой размер среднего значения для увеличения индекса,
индексирование менее полезно в этих двух целях, потому что среднее число
строк за поиск увеличивается: для хорошего индексирования в целях
оптимизации лучше, чтобы каждый индекс обрабатывал небольшое количество строк
в таблице. Когда данный индекс приводит к большому количеству строк,
индексирование менее полезно, и MySQL, менее вероятно,
будет использовать это.
Групповой размер среднего значения связан с табличным количеством
элементов, которое является числом групп значения.
SHOW INDEX
выводит на экран значение количества элементов, основанное на
N/S , где N число строк в таблице
и S групповой размер среднего значения. Это отношение
приводит к приблизительному количеству групп значения в таблице.
Для соединения, основанного на операторе сравнения
<=> , NULL
не обработан по-другому ни от какого другого значения:
NULL <=> NULL так же, как
N <=> N
для любого другого N .
Однако, для соединения, основанного на операторе = ,
NULL отличается от не-NULL :
expr1 = expr2
не истина, когда expr1 или
expr2 (или оба) NULL . Это затрагивает
доступы ref
для сравнений формы tbl_name.key =
expr : MySQL не будет получать доступ к таблице,
если текущее значение 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 можно любым
из следующих методов:
Некоторые протесты относительно использования
innodb_stats_method
и
myisam_stats_method :
Вы можете вынудить табличную статистику быть собранной явно, как
только что описано. Однако, MySQL может также собрать статистические данные
автоматически. Например, если в течение выполнения запросов для таблицы,
некоторые из тех запросов изменяют таблицу, MySQL может собрать
статистические данные. Это может произойти для большей части вставок или
удалений или некоторых ALTER TABLE
. Если это происходит, статистические данные собраны, используя любое
значение
innodb_stats_method или
myisam_stats_method
. Таким образом, если Вы соберете статистические данные, используя
один метод, но системная переменная установлена в другой метод, когда
статистические данные таблицы будут собраны автоматически позже, то другой
метод будет использоваться.
- Нет никакого способа сказать, какой метод использовался, чтобы произвести
статистику для данной таблицы.
- Эти переменные применяются только к
InnoDB и
MyISAM . У других механизмов хранения есть только один метод для
того, чтобы собрать табличные статистические данные. Обычно это ближе к
методу nulls_equal .
9.3.8.
Сравнение B-дерева и хеш-индекса
Понимание B-дерева и структур данных хеша может помочь предсказать, как
различные запросы выступают на различных механизмах хранения, которые
используют эти структуры данных, особенно для механизма хранения
MEMORY , который позволяет Вам выбирать B-дерево или хеш-индекс.
Характеристики B-Tree
B-tree может использоваться для сравнений столбца в выражениях, которые
используют операторы = ,
> ,
>= ,
< ,
<= или
BETWEEN .
Индексирование также может использоваться для
LIKE , если параметр
LIKE постоянная строка,
которая не начинается с подстановочного символа. Например, следующий
SELECT использует индекс:
SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';
В первом запросе только строки с 'Patrick' <=
key_col < 'Patricl'
рассмотрены. Во втором запросе только строки с 'Pat' <=
key_col < 'Pau' рассмотрены.
Следующие SELECT
не используют индексы:
SELECT * FROM tbl_name WHERE key_col
LIKE '%Patrick%';
SELECT * FROM tbl_name WHERE key_col
LIKE other_col ;
В первом запросе LIKE
начинается с с подстановочного символа. Во втором запросе значение
LIKE не константа.
Если Вы используете ... LIKE '%string %'
и string более длинно чем три символа, MySQL использует
алгоритм Turbo Boyer-Moore, чтобы инициализировать образец для
строки и затем использует этот образец, чтобы выполнить поиск более быстро.
Использование поиска col_name IS NULL
использует индекс, если col_name индексирован.
Любой индекс, который не охватывает все уровни
AND в WHERE ,
не используется, чтобы оптимизировать запрос. Другими словами, чтобы быть в
состоянии использовать индексирование, префикс должен использоваться в каждой
группе AND .
Следующий WHERE использует индекс:
... WHERE index_part1 =1 AND
index_part2 =2 AND other_column =3
/* index = 1 OR index = 2 */
... WHERE index =1 OR A=10 AND index =2
/* optimized like "index_part1 ='hello'" */
... WHERE index_part1 ='hello' AND
index_part3 =5
/* Can use index on index1 but not on
index2 or index3 */
... WHERE index1 =1 AND index2 =2 OR
index1 =3 AND index3 =3;
Эти WHERE не используют индекс:
/* index_part1 is not used */
... WHERE index_part2 =1 AND index_part3 =2
/* Index is not used in both parts of the WHERE clause */
... WHERE index =1 OR A=10
/* No index spans all rows */
... WHERE index_part1 =1 OR
index_part2 =10
Иногда MySQL не использует индексирование, даже если можно.
Одно обстоятельство, при котором это происходит, когда оптимизатор оценивает,
что использование индексирования потребовало бы, чтобы MySQL получил доступ к
очень большому проценту строк в таблице. В этом случае сканирование таблицы,
вероятно, будет намного быстрее, потому что оно требует меньше поисков.
Однако, если такой запрос использует LIMIT , чтобы получить
только некоторые из строк, MySQL использует индексирование так или иначе,
потому что он может намного более быстро найти немного строк.
Характеристики Hash-индекса
Hash-индекс имеет несколько иные характеристики:
Они используются только для сравнений равенства, которые
используют операторы = или <=>
(зато очень быстро).
Они не используются для таких операторов сравнения, как < ,
для диапазона значений. Системы, которые полагаются на этот тип поиска
единственного значения, известны как значение ключа
.
- Оптимизатор не может использовать хеш-индекс, чтобы ускорить операции
ORDER BY . Этот тип индекса не может использоваться, чтобы искать
следующую запись в порядке.
- MySQL не может определить приблизительно, сколько строк между двумя
значениями (это используется оптимизатором диапазона, чтобы решить, который
индекс использовать). Это может затронуть некоторые запросы, если Вы
изменяете
MyISAM или InnoDB на MEMORY .
- Только целые ключи могут использоваться, чтобы искать строку. С
B-tree любой префикс ключа может использоваться, чтобы найти строки.
9.3.9.
Использование оптимизатором произведенного столбца индекса
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, которая возвращает заключенную в
кавычки строку,
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()
в определении столбца оптимизатор обнаруживает соответствие только для
первого из этих сравнений.
- Если оптимизатор выбрал неверный индекс, индексные подсказки
могут использоваться, чтобы отключить его и вынудить оптимизатор
сделать иной выбор.
9.3.10. Невидимый индекс
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 |
+------------+------------+
Невидимый индекс позволяют проверить эффект удаления индексирования на
работе запроса, не производя разрушительное изменение. Передобавление
индексирования может быть дорогим для большой таблицы, тогда как переключение
невидимости индекса является быстрыми оперативными операциями.
Если невидимый индекс фактически необходим или используется оптимизатором,
есть несколько способов заметить эффект его отсутствия на
запросах для таблицы:
Невидимость не затрагивает обслуживание. Например, индексирование
продолжает обновляться при изменении строк таблицы, и уникальный индекс
предотвращает вставку дубликатов в столбец, независимо от того, видимо ли
индексирование или невидимо.
У таблицы без явного первичного ключа может все еще быть
эффективный неявный первичный ключ, если у этого есть индекс
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)
9.4.
Оптимизация структуры базы данных
В Вашей роли проектировщика базы данных, ищите самый эффективный способ
организовать Ваши схемы, таблицы и столбцы. Настраивая код программы, Вы
минимизируете ввод/вывод, держите связанные элементы вместе и планируете
заранее, чтобы работа осталась высокой, когда объем данных увеличивается.
9.4.1. Оптимизация размера данных
Разработайте свои таблицы, чтобы минимизировать их место на диске. Это
может привести к огромным усовершенствованиям, уменьшая объем данных,
написанный и прочитанный с диска. Меньшие таблицы обычно требуют менее
основной памяти, в то время как их содержание активно обрабатывается во время
выполнения запроса. Любое сокращение пространства для табличных данных также
приводит к меньшему индексу, который может быть обработан быстрее.
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
дублированы в каждом вторичном индексе, таким образом, короткий первичный
ключ оставляет значительное свободное место, если Вы имеете
много вторичных индексов.
- Создайте только индексирование, которое улучшит работу запроса.
Индексы хороши для извлечения, но замедляют вставки и обновления.
Если Вы получаете доступ к таблице главным образом, ища на комбинации
столбцов, создайте единственный сводный индекс на них, а не отдельный индекс
для каждого столбца. Первая часть индекса должна быть наиболее используемым
столбцом. Если Вы всегда используете много столбцов,
выбирая из таблицы, первый столбец в индексировании должен быть с
большинством дубликатов, чтобы получить лучшее сжатие индексирования.
- Если вероятно, что у длинного строкового столбца есть уникальный префикс
на первом числе символов, лучше индексировать только этот префикс, используя
поддержку MySQL создания индексирования на префиксе столбца (см.
раздел 14.1.12).
Короткий индекс быстрее не только потому, что он требует меньшего количества
дискового пространства, но и потому что он также дает Вам больше хитов в
индексном кэше, и таким образом меньше дисковых поисков. См.
раздел 6.1.1.
Joins
При некоторых обстоятельствах может быть выгодно
разделить на две таблицу, которая просматривается очень часто.
Это особенно верно, если это таблица динамического формата, и возможно
использовать меньшую статическую таблицу, которая может использоваться, чтобы
найти соответствующие строки, просматривая таблицу.
- Объявите столбцы с идентичной информацией в различных таблицах с
идентичными типами данных, чтобы ускорить соединения, основанные
на соответствующих столбцах.
- Сохраните имена столбцов простыми, так, чтобы Вы могли использовать то же
самое имя через различные таблицы и упростить запросы соединения. Например, в
таблице
customer используйте столбец
name вместо customer_name .
Чтобы сделать Ваши имена переносимыми к другим SQL-серверам, рассмотрите
их сохранение короче 18 символов.
Нормализация
Обычно попытайтесь сохранить все данные безызбыточными
(то, что упомянуто в теории базы данных как third normal form).
Вместо того, чтобы повторить длинные значения, такие как имена и адреса,
назначьте им уникальные ID, повторите эти ID где надо через меньшие таблицы
и присоединитесь к таблицам в запросах, ссылаясь на ID в join.
- Если скорость более важна, чем дисковое пространство и затраты на
обслуживание хранения многократных копий данных, например в скрипте
бизнес-анализа, откуда Вы анализируете все данные больших таблиц, Вы можете
ослабить правила нормализации, дублируя информацию или создавая сводные
таблицы, чтобы получить больше скорости.
9.4.2.
Оптимизация типов данных MySQL
9.4.2.1.
Оптимизация для числовых данных
Для уникальных ID или других значений, которые могут быть
представлены как строки или как числа, предпочтите, чтобы числовые столбцы
представили столбцы в виде строки. Так как большие числовые значения могут
быть сохранены в меньшем количестве байтов чем соответствующие строки, это
быстрее и берет меньше памяти, чтобы передать и сравнить их.
- Если Вы используете числовые данные, во многих случаях быстрее запросить
базу данных, чем получить доступ к текстовому файлу. Информация в базе
данных, вероятно, будет храниться в более компактном формате, чем в текстовом
файле, так чир доступ вовлекает меньше дисковых поисков. Вы также сохраняете
код в своем приложении, потому что Вы можете избежать разбирать текстовый
файл, чтобы найти границы столбца и строки.
9.4.2.2.
Оптимизация для типов символа и строки
Используйте двоичный порядок сопоставления для быстрого сравнения
и сортировки, когда Вы не нуждаетесь в определенных для языка особенностях
сопоставления. Вы можете использовать оператор
BINARY , чтобы
использовать двоичное сопоставление в пределах особого запроса.
- Сравнивая значения от различных столбцов, объявите те столбцы с тем же
самым набором символов и сопоставлением везде, где возможно, чтобы избежать
строковых преобразований, выполняя запрос.
- Для значений столбцов меньше 8 КБ в размере, используйте двоичный
VARCHAR вместо BLOB . GROUP BY и
ORDER BY могут произвести временные таблицы, и эти временные
таблицы могут использовать MEMORY ,
если оригинальная таблица не содержит BLOB .
- Если таблица содержит строковые столбцы, такие как имя и адрес, но много
запросов не получают те столбцы, рассмотрите выделение строковых столбцов в
отдельную таблицу и использование запросов соединения с внешним ключом, когда
необходимо. Когда MySQL получает любое значение от строки, он читает блок
данных, содержащий все столбцы той строки (и возможно других смежных строк).
Хранение каждой небольшой строки, с только наиболее часто используемыми
столбцами, позволяет большему количеству строк помещаться в каждый блок
данных. Такие компактные таблицы уменьшают дисковый ввод/вывод и
использование памяти для общих запросов.
- Когда Вы используете беспорядочно произведенное значение в качестве
первичного ключа в
InnoDB , примените префикс
со значением возрастания, таким как текущая дата и время, если возможно.
Когда последовательные основные значения физически сохранены друг около
друга, InnoDB может вставить и получить их быстрее.
- См. раздел 9.4.2.1.
9.4.2.3. Оптимизация для BLOB
Храня большой blob, содержащий текстовые данные, рассмотрите
сжатие этого сначала. Не используйте этот метод, когда вся таблица сжата
InnoDB или MyISAM .
- Для таблицы с несколькими столбцами, чтобы уменьшить требования к памяти
для запросов, которые не используют столбец BLOB, рассмотрите выделение
столбца BLOB в отдельную таблицу и ссылку на него с
соединением при необходимости.
- Так как эксплуатационные требования, чтобы получить и вывести на экран
значение BLOB могли бы очень отличаться от других типов данных, Вы могли
поместить спецтаблицу на иное устройство хранения данных или даже отдельный
экземпляр базы данных. Например, получение BLOB могло бы потребовать большого
последовательного чтения с диска, что лучше подходит для традиционного
жесткого диска, чем для SSD device.
- См. раздел 9.4.2.2.
- Вместо того, чтобы проверять на равенство против очень длинной текстовой
строки, Вы можете сохранить хеш значения столбца в отдельном столбце,
индексировать тот столбец и проверить хешированное значение в запросах.
Используйте функции
MD5() или CRC32() , чтобы
произвести значение хеша. Так как функции хеша могут привести к двойным
результатам для различных вводов, Вы все еще включаете пункт
AND blob_column =
long_string_value в запрос, чтобы принять меры
против ложных соответствий, исполнительная выгода прибывает из меньшего,
легко просматриваемого индекса для хешированных значений.
9.4.2.4. Применение PROCEDURE ANALYSE
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()
должен выделить столбцу, пытаясь найти все отличные значения.
9.4.3.
Оптимизация для многих таблиц
Некоторые методы для того, чтобы сохранить отдельные запросы быстрыми,
вовлекают данные о разделении через многие таблицы. Когда число таблиц
сталкивается с тысячами или даже миллионами, издержки контакта со всеми этими
таблицами становятся новым исполнительным соображением.
9.4.3.1.
Как MySQL открывает и закрывает таблицы
Когда Вы выполняете
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 закрывает неиспользованную таблицу и удаляет это из табличного кэша
при следующих обстоятельствах:
Когда табличный кэш заполняется, сервер использует следующую процедуру,
чтобы определить местонахождение использумой записи кэша:
Таблицы, которые не используются в настоящее время, выпущены,
начиная с последней использованной таблицы.
- Если новая таблица должна быть открыта, но кэш полон, и никакие таблицы
не могут быть выпущены, кэш временно расширен по мере необходимости. Когда
кэш находится во временно расширенном статусе, и таблица переходит от
используемого состояния в неиспользуемое, таблица закрыта и выпущена из кэша.
MyISAM открыта для каждого параллельного доступа.
Это означает потребность открыть таблицу дважды, если два потока получают
доступ к той же самой таблице, или если поток получает доступ к таблице
дважды в том же самом запросе (например, соединяя таблицу с собой). Каждое
параллельное открытие требует записи в табличном кэше. Первое открытие любой
MyISAM берет два описателя файла: один для файла с данными и
один для индексного файла. Каждое дополнительное использование таблицы берет
только один описатель файла для файла с данными. Описатель индексного файла
совместно использован среди всех потоков.
Если Вы открываете таблицу с HANDLER
tbl_name OPEN , специализированный табличный
объект выделен для потока. Этот табличный объект не использован совместно
другими потоками и не закрыт до требований потока HANDLER
tbl_name CLOSE или завершения потока.
Когда это происходит, таблица отложена в табличном кэше (если кэш не полон).
См. раздел 14.2.4.
Вы можете определить, является ли Ваш табличный кэш слишком маленьким,
проверяя переменную
Opened_tables , которая указывает на число открывающих таблицу
операций с момента запуска сервера:
mysql> SHOW GLOBAL STATUS LIKE 'Opened_tables';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Opened_tables | 2741 |
+---------------+-------+
Если значение является очень большим или увеличивается быстро, даже когда
Вы не выполняли много FLUSH TABLES ,
увеличьте размер табличного кэша. См. разделы
6.1.5 и
6.1.7.
9.4.3.2.
Недостатки составления многих таблиц в той же самой базе данных
Если у Вас есть много таблиц MyISAM
в том же самом каталоге базы данных, операции открытия, создания и закрытия
являются медленными. Если Вы выполняете SELECT
на многих различных таблицах, добавляется небольшая издержка,
когда табличный кэш полон, потому что для каждой таблицы, которая должна быть
открыта, другая должна быть закрыта. Вы можете уменьшить эти издержки,
увеличивая число записей, разрешенных в табличном кэше.
9.4.4.
Внутренние временные таблицы в MySQL
В некоторых случаях сервер составляет внутренние временные таблицы,
обрабатывая запросы. У пользователей нет никакого прямого управления,
когда это происходит.
Сервер составляет временные таблицы при таких условиях:
Чтобы определить, требует ли запрос временной таблицы, надо использовать
EXPLAIN и проверьте столбец
Extra , чтобы видеть, говорит ли это
Using temporary (см. раздел
9.8.1). EXPLAIN не обязательно скажет Using temporary
для полученных или осуществленных временных таблиц.
Когда сервер составляет внутреннюю временную таблицу (в памяти или на
диске), это постепенно увеличивает переменную
Created_tmp_tables
. Если сервер составляет таблицу на диске (первоначально или
преобразовывая таблицу в памяти), это постепенно увеличивает переменную
Created_tmp_disk_tables .
Некоторые условия запроса предотвращают использование временной таблицы в
памяти, когда сервер использует таблицу на диске вместо этого:
Сервер не использует временную таблицу для
UNION , которые встречают
определенные квалификации. Вместо этого это сохраняет из временной таблицы
только структуры данных, необходимые, чтобы выполнить подбор типов.
Таблица не полностью инстанцирует, и никакие строки не написаны или считаны
из нее, строки посылают непосредственно клиенту. Результат уменьшение
требований к памяти и диску и меньшая задержка прежде, чем первую строку
пошлют клиенту, потому что сервер не должен ждать, пока последний блок
запроса будет выполнен. EXPLAIN и
трассировка оптимизатора отражают эту стратегию выполнения: блок запроса
UNION RESULT не присутствует, потому что тот блок соответствует
части, которая читает из временной таблицы.
Эти условия квалифицируют UNION
для оценки без временной таблицы:
Механизмы хранения, используемые для временных таблиц
Внутренняя временная таблица может быть проведена в памяти и обработана
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 может
использоваться, чтобы вызвать дисковое хранение внутренних временных таблиц.
9.5. Оптимизация таблиц InnoDB
InnoDB это механизм хранения,
который клиенты MySQL, как правило, используют в производственных базах
данных, где надежность и параллелизм важны. Это механизм хранения по
умолчанию в MySQL. Этот раздел объясняет, как оптимизировать операции базы
данных для InnoDB .
9.5.1.
Оптимизация расположения хранения для таблиц 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 .
9.5.2.
Оптимизирующее операционное управление InnoDB
Чтобы оптимизировать обработку транзакций, найдите идеальный баланс между
издержками транзакционных особенностей и рабочей нагрузкой Вашего сервера.
Например, приложение могло бы столкнуться с исполнительными проблемами, если
оно передает тысячи раз в секунду и совсем другими проблемами, если оно
передает только каждые 2-3 часа.
Установка MySQL значения по умолчанию AUTOCOMMIT=1
может наложить исполнительные ограничения на занятый сервер базы данных.
Где практично, оберните несколько связанных операций изменения данных в
единственную транзакцию через SET AUTOCOMMIT=0 или START
TRANSACTION , сопровождаемый COMMIT .
InnoDB должен сбросить журнал на диск в каждой передаче
транзакции, если та транзакция сделала модификации базы данных. Когда каждое
изменение сопровождается передачей, пропускная способность ввода/вывода
устройства хранения данных имеет решающее значение.
- Альтернативно, для транзакций, которые состоят только из одного
SELECT , включение
AUTOCOMMIT помогает InnoDB
признать транзакции только для чтения и оптимизировать их. См.
раздел 9.5.3.
- Избегайте выполнять обратные перемотки после вставки, обновления или
удаления огромных количеств строк. Если большая транзакция замедляет работу
сервера, ее откат может сделать проблему хуже, потенциально беря в несколько
раз больше времени, чем оригинальные операции изменения данных. Уничтожение
процесса базы данных не помогает, потому что обратная перемотка запускается
снова при запуске сервера.
Минимизировать шанс этого проявления проблемы:
Чтобы избавиться от безудержной обратной перемотки, как только это
происходит, увеличьте буферный пул так, чтобы обратная перемотка стала
ограничиваться центральным процессором и работала быстро, или уничтожьте
сервер и перезапустите его с
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 ,
возможно, должен искать записи, используя кластеризируемый индекс.
9.5.3.
Оптимизация транзакций только для чтения
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 .
9.5.4.
Оптимизация журнала InnoDB Redo
Сделайте файлы журнала redo размером как
буферный пул. Когда
InnoDB пишет в полные файлы системного журнала, это должно
записать измененное содержание буферного пула на диск в
контрольную точку.
Маленький файл приводит к большому числу лишних дисковых записей.
Хотя исторически большой файл вызывал долгое время восстановления файлов
системного журнала, восстановление теперь намного быстрее, и Вы можете
уверенно использовать большой файл системного журнала.
Размер и число файлов системного журнала сконфигурированы, используя
опции
innodb_log_file_size и
innodb_log_files_in_group , см.
раздел 16.7.2.
- Рассмотрите увеличение размера
буфера журнала.
Большой буфер журнала позволяет большим
транзакциям
работать без потребности записать журнал на диск прежде
commit.
Таким образом, если у Вас есть транзакции, которые обновляют, вставляют или
удаляют много строк, большой буфер журнала сохраняет дисковый ввод/вывод.
Размер буфера журнала сконфигурирован, используя опцию
innodb_log_buffer_size .
9.5.5.
Оптовые данные для таблиц InnoDB
Эти исполнительные подсказки добавляют общие руководящие принципы для
быстрых вставок в разделе 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 .
- Когда выполняется большая вставка в таблицы со столбцами auto-increment,
установите
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 . Этот
подход, однако, требует пересоздания таблицы, что будет
воздействовать на работу.
9.5.6.
Оптимизация запросов InnoDB
Чтобы настроить запросы для InnoDB ,
создайте соответствующий набор индексов на каждой таблице. См.
раздел 9.3.1.
Поскольку у каждой таблицы InnoDB есть
первичный ключ (запрашиваете его
или нет), определите ряд столбцов первичного ключа для каждой таблицы,
столбцы которой используются в самых важных и срочных запросах.
- Не определяйте слишком много или слишком длинные столбцы в первичном
ключе, потому что эти значения столбцов дублированы в каждом вторичном
индексе. Когда индексирование содержит ненужные данные, ввод/вывод, чтобы
считать эти данные и память, чтобы их кэшировать, уменьшает
работу и масштабируемость сервера.
- Не создавайте отдельный
вторинчый индекс,
для каждого столбца, потому что каждый запрос может использовать только один
индекс. Индексы на редко проверяемых столбцах или столбцы только с
несколькими различными значениями не могли бы быть полезными для любых
запросов. Если у Вас есть много запросов для той же самой таблицы, проверяя
различные комбинации столбцов, попытайтесь создать небольшое количество
связанных индексов, а не
большое количество индексов из одного столбца. Если индекс содержит
все столбцы, необходимые для набора результатов (известный как
покрывающий),
запрос может быть в состоянии избежать читать табличные данные вообще: он все
берет из индекса.
- Если индексированный столбец не может содержать
NULL ,
определите его как NOT NULL , когда Вы составляете таблицу.
Оптимизатор может лучше определить, который индекс является самым
эффективным, чтобы использовать для запроса, когда он знает, содержит ли
каждый столбец NULL .
- Вы можете оптимизировать транзакции единственного запроса для
InnoDB , используя метод в
разделе 9.5.3.
- Если у Вас часто есть возвращающиеся запросы для таблиц, которые
часто не обновляются, включите кэш запроса:
[mysqld]
query_cache_type = 1
query_cache_size = 10M
9.5.7.
Оптимизация InnoDB DDL
Для операций 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.
9.5.8.
Оптимизация дискового ввода/вывода InnoDB
Если Вы следуете за лучшими методами для проектирования баз данных и
настройки операций SQL, но Вашу базу данных все еще замедляет тяжелая
дисковая деятельность ввода/вывода, исследуйте эти низкоуровневые методы,
связанные с дисковым вводом/выводом. Если в Unix команда top или
Windows Task Manager показывают, что процент использования центрального
процессора с Вашей рабочей нагрузкой составляет меньше 70%,
Ваша рабочая нагрузка является, вероятно, связанной с диском.
Когда табличные данные кэшируются в буферном пуле
InnoDB , к этому могут неоднократно получать доступ запросы, не
требуя никакого дискового ввода/вывода. Определите размер буферного пула с
опцией
innodb_buffer_pool_size . Эта область памяти достаточно важна, что,
как правило, рекомендуют установить
innodb_buffer_pool_size от 50 до 75
процентов системной памяти. Для получения дополнительной информации см.
раздел 9.12.3.1.
- В некоторых версиях GNU/Linux и Unix сброс файлов
на диск с Unix
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 на файловой системе
прямого ввода/вывода.
- Если Вы имеете дополнительные устройства хранения данных в наличии, чтобы
настроить конфигурацию RAID или символические ссылки к различным дискам, см.
раздел 9.12.1.
- Если пропускная способность периодически проваливается из-за
контрольной точки
InnoDB , рассмотрите увеличение значения
innodb_io_capacity
. Более высокие значения вызывают более частый
сброс, избегая отставания в работе,
которое может вызвать падения в пропускной способности.
- Если система не запаздывает со
сбросами
InnoDB ,
рассмотрите понижение значения
innodb_io_capacity
. Как правило, Вы сохраняете это значение опции столь низким, как
практично, но не настолько низким, что оно вызывает периодические падения
пропускной способности, как упомянуто выше. В типичном скрипте, где Вы могли
понизить значение опции, Вы могли бы видеть это в выводе
SHOW ENGINE INNODB STATUS :
- Вы можете использовать в своих интересах doublewrite буфер, храня
системные файлы табличного пространства
(ibdata) на устройствах Fusion-io,
которые поддерживают атомную запись. В этом случае буфер doublewrite
(
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 .
9.5.9.
Оптимизация переменных конфигурации InnoDB
Различные настройки работают лучше всего на серверах
с легкими и предсказуемыми загрузками.
Поскольку InnoDB многие из своих оптимизаций автоматически,
много настраивающих работу задач вовлекают контроль, чтобы гарантировать, что
база данных работает хорошо и изменять параметры конфигурации, когда работа
нарушается. См. раздел 16.15
.
Основные шаги конфигурации, которые Вы можете выполнить, включают:
Управление типами данных изменяет операции для которых
InnoDB буферизует измененные данные, чтобы избежать
частых маленьких операций записи на диск. См.
раздел 16.6.4.
Поскольку значение по умолчанию должно буферизовать все типы операций
изменения данных, надо изменить только эти настройки, если Вы должны
уменьшить объем буферизации.
- Включение адаптивной индексации хеша и использование опции
innodb_adaptive_hash_index . См.
раздел 16.4.3.
Вы могли бы изменить эти настройки во время периодов необычной
деятельности, затем восстановить к оригинальной установке.
- Установка предела для числа параллельных потоков, которые обрабатывает
InnoDB , если переключение контекста узкое место. См.
раздел 16.6.5.
- Управление количеством предварительной выборки
InnoDB .
Когда у системы есть неиспользованная способность ввода/вывода, чтение вперед
может улучшить исполнение запросов. Слишком большая предвыборка может вызвать
периодические падения работы на сильно загруженной системе. См.
раздел 16.6.3.5.
- Увеличивание числа фоновых потоков для чтения или записи, если у Вас есть
подсистема ввода/вывода высокого уровня, которая не полностью используется
значениями по умолчанию. См.
раздел 16.6.6
.
- Управление, сколько ввода/вывода
InnoDB идет в фоне. См.
раздел 16.6.7.
Вы могли бы вычислить эту установку, если Вы наблюдаете
периодические падения работы.
- Управление алгоритмом, который определяет, когда
InnoDB
использует определенные типы фоновой записи. См.
раздел 16.6.3.6
. Алгоритм для некоторых типов рабочих нагрузок, но не других, мог бы
выключить эту установку, если Вы наблюдаете периодические падения работы.
- Использование в своих интересах мультиядерных процессоров и конфигурации
их кэш-памяти, чтобы минимизировать задержки переключения контекста. См.
раздел 16.6.8.
- Предотвращение одноразовых операций, таких как сканирование таблицы,
с данными, к которым часто получают доступ, находящимися в буфере
InnoDB . См. подробности в
разделе 16.6.3.4
.
- Корректировка файлов системного журнала к размеру, который имеет смысл
для восстановления надежности и катастрофического отказа. Файлы системного
журнала
InnoDB часто сохранялись маленькими, чтобы избежать
долгих времен запуска после катастрофического отказа. Оптимизация, введенная
в MySQL 5.5, ускоряет определенные шаги процесса
восстановления. В частности,
просмотр и применение журнала redo
происходят быстрее из-за улучшенных алгоритмов для управления памятью. Если
Вы сохранили свои файлы системного журнала искусственно маленькими, чтобы
избежать долгих времен запуска, Вы можете теперь полагать, что
увеличивающийся размер файла системного журнала уменьшает ввод/вывод.
- Конфигурирование размера и числа копий буферного пула
InnoDB , особенно важно для систем с буферными пулами в
несколько гигабайт. См.
раздел 16.6.3.3.
- Увеличение максимального количества параллельных транзакций, которое
резко улучшает масштабируемость для самых занятых баз данных. См.
раздел 16.4.7.
- Перемещение операций чистки в фоновый поток. См.
раздел 16.6.9.
Чтобы эффективно измерить результаты этой установки, настройте
связанные с потоком настройки конфигурации сначала.
- Сокращение количества переключений, которые
InnoDB
делает между параллельными потоками, так, чтобы операции SQL на занятом
сервере не стояли в очереди и сформировали поток трафика
. Установите значение для опции
innodb_thread_concurrency
до приблизительно 32 для мощной современной системы. Увеличьте значение
опции
innodb_concurrency_tickets , как правило к 5000 или около этого.
Эта комбинация опций устанавливает ограничение на число потоков, которые
InnoDB обрабатывает в любой момент и позволяет каждому потоку
сделать существенную работу прежде, чем быть переключенным, чтобы число
потоков ожидания осталось низким, и операции могут завершиться без
чрезмерного переключения контекста.
9.5.10.
Оптимизация InnoDB для систем со многими таблицами
InnoDB вычисляет индексные значения
количества элементов
для таблицы в первый раз, когда к таблице получают доступ после запуска,
вместо того, чтобы хранить такие значения в таблице. Этот шаг может занять
время на системах, которые делят данные на многие таблицы. Так как это
относится только к начальной работе открытия таблицы, к
нагретым для более позднего использования таблицам
немедленно получают доступ после запуска, делая такой запрос, как
SELECT 1 FROM tbl_name LIMIT 1 .
9.6.
Оптимизация для таблиц MyISAM
MyISAM
выступает лучше всего с данными главным образом для чтения или с операциями
низкого параллелизма, потому что табличные блокировки ограничивают
способность выполнить одновременные обновления. В MySQL
InnoDB
механизм хранения по умолчанию, а не MyISAM .
9.6.1.
Оптимизация запросов MyISAM
Некоторые общие советы для того, чтобы ускорить запросы
на таблицах MyISAM :
Чтобы помочь MySQL лучше оптимизировать запросы, стоит
использовать ANALYZE TABLE
или myisamchk --analyze
на таблице после того, как это было загружено данными.
Это обновляет значение для каждой части индекса, которая указывает на среднее
число строк, у которых есть то же самое значение, поскольку уникальный индекс
это всегда 1. MySQL использует это, чтобы решить, который индекс выбрать,
когда Вы присоединяетесь к двум таблицам, основанным на непостоянном
выражении. Вы можете проверить следствие табличного анализа при использовании
SHOW INDEX FROM tbl_name и исследуя
значение Cardinality .
myisamchk --description --verbose показывает
информацию о распределении индекса.
- Чтобы сортировать индекс и данные согласно индексированию, используйте
myisamchk --sort-index
--sort-records=1 (предполагается, что Вы хотите
сортировать на индексе 1). Это хороший способ сделать запросы быстрее, если у
Вас есть уникальный индекс, из которого Вы хотите считать все строки в
порядке согласно индексированию. В первый раз, когда Вы сортируете большую
таблицу этим путем, может потребоваться много времени.
- Попытайтесь избежать сложных
SELECT
на MyISAM , которые часто обновляются, чтобы избежать
проблем с блокировкой таблицы.
MyISAM допускает параллельные вставки: если у таблицы нет
никаких свободных блоков в середине файла с данными, Вы можете
INSERT новые строки в то же самое
время, когда другие потоки читают из таблицы. Если важно быть в состоянии
сделать это, рассмотрите использование таблицы способами, которые избегают
удалять строки. Другая возможность состоит в том, чтобы выполнить
OPTIMIZE TABLE , чтобы
дефрагментировать таблицу после того, как Вы удалили много строк.
Это поведение изменено, устанавливая
concurrent_insert
. Вы можете вынудить новые строки быть добавленными
(и поэтому разрешить параллельные вставки), даже в таблицах, которые удалили
строки. См. раздел 9.11.3.
- Для
MyISAM таблицы, которые часто изменяются, пытаются
избежать всех столбцов переменной длины
(VARCHAR ,
BLOB и
TEXT ).
Таблица использует динамический формат строки, если это включает даже
единственный столбец переменной длины. См. главу 17
.
- Обычно не стоит делить таблицу на различные таблицы только потому, что
строки становятся большими. В доступе к строке самая большая проблема это
дисковые поиски, чтобы найти первый байт строки. После обнаружения данных
самые современные диски могут считать всю строку достаточно быстро для
большинства приложений. Единственные случаи, где разделение таблицы имеет
заметное значение, это если применен динамический формат строки, который Вы
можете изменить на фиксированный размер строки, или если Вы очень часто
должны просматривать таблицу, но не нуждаетесь в большинстве столбцов. См.
columns. See главу 17.
- Используйте
ALTER TABLE ... ORDER BY expr1 ,
expr2 , ... , если Вы обычно получаете строки в
порядке expr1 ,
expr2 , ... . При использовании этой опции после
обширных изменений таблицы Вы можете быть в состоянии получить
более высокую работу.
- Если Вы часто должны вычислять результаты, такие как количество,
основанные на информации от большого количества строк, может быть
предпочтительно ввести новую таблицу и обновить счетчик в режиме реального
времени. Обновление следующей формы очень быстро:
UPDATE tbl_name SET count_col =count_col +1
WHERE key_col =constant ;
Это очень важно, когда Вы используете механизмы хранения MySQL, такие как
MyISAM , у которых есть только блокировка на уровне таблицы.
Это также дает лучшую работу с большинством систем базы данных, потому что
менеджер блокировки строк в этом случае загружен меньше.
- Используйте регулярно
OPTIMIZE
TABLE , чтобы избегать фрагментации с динамическим форматом
таблиц MyISAM , см.
раздел 17.2.3.
- Объявление
MyISAM с опцией
DELAY_KEY_WRITE=1 делает индексные обновления быстрее, потому
что они не сбрасываются на диск, пока таблица не закрыта. Проблема в
том, что если что-то уничтожает сервер в то время как, такая таблица открыта,
Вы должны гарантировать, что таблица в порядке, выполняя сервер с опцией
--myisam-recover-options или выполняя
myisamchk
прежде, чем перезапустить сервер. Однако, даже в этом случае, Вы ничего не
должны потерять при использовании DELAY_KEY_WRITE , потому что
ключевая информация может всегда производиться от строк данных.
- Вы можете увеличить производительность, кэшируя запросы или ответы в
Вашем приложении, и затем выполняя многие вставки или обновления вместе.
Блокировка таблицы во время этой работы гарантирует, что индексный кэш
сброшен только однажды после всех обновлений. Вы можете также использовать в
своих интересах кэш запроса MySQL, чтобы достигнуть подобных результатов, см.
раздел 9.10.3.
9.6.2.
Оптовые загрузки данных для MyISAM
Эти исполнительные подсказки добавляют общие руководящие принципы
для быстрых вставок в разделе 9.2.2.1.
Для MyISAM Вы можете использовать параллельные
вставки, чтобы добавить строки в то же самое время, когда работает
SELECT ,
если нет никаких удаленных строк в середине файла с данными. См.
раздел 9.11.3.
- С некоторой дополнительной работой возможно сделать
LOAD DATA INFILE еще быстрее для
MyISAM , когда у таблицы есть много индексов.
Используйте следующую процедуру:
Выполните FLUSH
TABLES или
mysqladmin flush-tables.
- Используйте myisamchk
--keys-used=0 -rq
/path/to/db/tbl_name
, чтобы удалить все индексы для таблицы.
- Вставьте данные в таблицу с
LOAD DATA INFILE .
Это не обновляет индексы и поэтому очень быстро.
- Если Вы намереваетесь только читать из таблицы в будущем, используйте
myisampack
, см. раздел 17.2.3.3.
- Обновите индексирование с
myisamchk -rq
/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 TABLE tbl_name DISABLE KEYS;
ALTER TABLE tbl_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 .
Блокировка также удаляет полное время для тестов многократного соединения,
хотя максимум ожидания для отдельных соединений могло бы повыситься,
потому что они ждут блокировок. Предположите, что пять клиентов пытаются
вставлять одновременно следующим образом:
Если Вы не используете блокировку, соединения 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.
9.6.3. Скорость REPAIR TABLE
REPAIR TABLE для
MyISAM подобен использованию
myisamchk
для операций ремонта, и часть той же самой
исполнительной оптимизации применяется:
Предположите, что myisamchk
работает, используя следующие опции, чтобы установить
переменные распределения памяти:
--key_buffer_size=128M --myisam_sort_buffer_size=256M
--read_buffer_size=64M --write_buffer_size=64M
Некоторые из тех переменных
myisamchk соответствуют переменным сервера:
Каждая из системных переменных сервера может быть установлена во время
выполнения, и некоторые из них
(
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 INDEX tbl_name IN repair_cache;
LOAD INDEX INTO CACHE tbl_name ;
REPAIR TABLE tbl_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 глобально к большому значению делает это для всех
сеансов и может заставить работу пострадать из-за чрезмерного распределения
памяти для сервера с многими одновременными сеансами.
9.7. Оптимизация таблиц MEMORY
Рассмотрите использование 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.
9.8.
Понимание плана выполнения запроса
В зависимости от деталей Ваших таблиц, столбцов, индексов и условий в
Вашем WHERE , оптимизатор MySQL имеет много методов эффективно
выполняют поиски, вовлеченные в запрос SQL. Запрос на огромной таблице может
быть выполнен, не читая все строки; соединение, вовлекающее несколько таблиц,
может быть выполнено, не сравнивая каждую комбинацию строк. Набор операций,
с помощью которых оптимизатор хочет выполнять самый эффективный запрос,
называют планом выполнения запроса, также он
известен как план EXPLAIN .
Ваши цели состоят в том, чтобы признать аспекты плана
EXPLAIN , которые указывают на то,
что запрос оптимизирован хорошо, и изучить синтаксис SQL и методы индексации,
чтобы улучшить план, если Вы видите некоторые неэффективные операции.
9.8.1. Оптимизация запросов с EXPLAIN
EXPLAIN может использоваться,
чтобы получить информацию о том, как MySQL выполняет запрос:
С помощью 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
tbl_name синоним
with DESCRIBE tbl_name и
SHOW COLUMNS FROM tbl_name . См. разделы
information, see 14.8.1 и
14.7.5.5.
9.8.2. Выходной формат EXPLAIN
EXPLAIN
предоставляет информацию о плане выполнения относительно
SELECT .
EXPLAIN
возвращает строку информации для каждой таблицы, используемой в
SELECT .
Это перечисляет таблицы в выводе в порядке, в котором
MySQL считал бы их, обрабатывая запрос. MySQL решает все соединения,
используя метод соединения вложенной петли. Это означает, что MySQL читает
строку из первой таблицы, а затем находит соответствующую строку во второй
таблице, третьей и так далее. Когда все таблицы обработаны, MySQL выводит
выбранные столбцы и отступления через табличный список, пока таблица не
найдена, для которой более соответствуют строки. Следующая строка считана из
этой таблицы, и процесс продолжается со следующей таблицей.
Нельзя использовать устаревшие параметры EXTENDED и
PARTITIONS вместе в том же самом
EXPLAIN . Кроме того, ни одно из
этих ключевых слов не может использоваться вместе с опцией
FORMAT . FORMAT=JSON предписывает
EXPLAIN вывести на экран расширенную информацию и данные о
разделении автоматически, использование FORMAT=TRADITIONAL не
влияет на вывод 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 .
id (Имя JSON:
select_id )
Идентификатор SELECT .
Это последовательное число
SELECT в запросе.
Значение может быть NULL , если строка обращается к результату
союза других строк. В этом случае столбец table показывает
значение как <unionM ,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 )
Название таблицы, к которой обращается строка вывода.
Это может также быть одним из следующих значений:
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 .
Типы соединения EXPLAIN
type EXPLAIN
описывает, как присоединяются к таблицам. В формате JSON
они найдены как значения свойства access_type . Следующий список
описывает типы соединения, упорядоченные от лучшего типа до худшего:
system
У таблицы есть только одна строка (= системная таблица). Это особый случай
типа соединения const .
-
const
У таблицы есть самое большее одна строка соответствия, которая считана
в начале запроса. Поскольку есть только одна строка, значения столбца в этой
строке могут быть расценены как константы остальной частью оптимизатора.
Таблицы const
очень быстры, потому что они читаются только один раз.
const
используется, когда Вы сравниваете все части индекса PRIMARY KEY
или UNIQUE с постоянными величинами. В следующих запросах
tbl_name может использоваться в качестве таблицы
const :
SELECT * FROM tbl_name
WHERE primary_key =1;
SELECT * FROM tbl_name
WHERE primary_key_part1 =1 AND
primary_key_part2 =2;
-
eq_ref
Одна строка считана из этой таблицы для каждой комбинации строк от
предыдущих таблиц. Кроме типов
system и
const
это самый лучший тип соединения. Это используется, когда все части
индексирования используются соединением, и индексирование является индексом
PRIMARY KEY или UNIQUE NOT NULL .
eq_ref
может использоваться для индексированных столбцов, которые сравнены,
используя оператор = . Сравнительное значение может быть
константой или выражением, которое использует столбцы от таблиц, которые
считаны перед этой таблицей. В следующих примерах MySQL может использовать
соединение eq_ref
для обработки ref_table :
SELECT * FROM ref_table ,other_table
WHERE ref_table .key_column =
other_table .column ;
SELECT * FROM ref_table ,other_table
WHERE ref_table .key_column_part1 =
other_table .column AND
ref_table .key_column_part2 =1;
-
ref
Все строки с соответствием индексу значения
считаны из этой таблицы для каждой комбинации строк от предыдущих таблиц.
ref
используется, если соединение использует только префикс
ключа или если ключ не индекс PRIMARY KEY или
UNIQUE (другими словами, если соединение не может выбрать
единственную строку, основанную на значении ключа). Если ключу, который
используется, соответствует только несколько строк, это
хороший тип соединения.
ref
может использоваться для индексированных столбцов, которые сравнены,
используя = или <=> .
В следующих примерах MySQL может использовать соединение
ref для обработки
ref_table :
SELECT * FROM ref_table
WHERE key_column =expr ;
SELECT * FROM ref_table ,other_table
WHERE ref_table .key_column =
other_table .column ;
SELECT * FROM ref_table ,other_table
WHERE ref_table .key_column_part1
=other_table .column AND
ref_table .key_column_part2 =1;
-
fulltext
Соединение выполнено, используя индекс FULLTEXT .
-
ref_or_null
Этот тип соединения похож на
ref ,
но с дополнением, что MySQL делает дополнительный поиск строк, которые
содержат NULL . Эта оптимизация типа соединения используется чаще
всего в решении подзапросов. В следующих примерах MySQL может использовать
ref_or_null
для работы с ref_table :
SELECT * FROM ref_table
WHERE key_column =expr OR
key_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 (SELECT primary_key
FROM single_table WHERE some_expr )
unique_subquery
только индексная функция поиска, которая заменяет подзапрос
полностью для лучшей эффективности.
-
index_subquery
Этот тип соединения подобен
unique_subquery
. Заменяет подзапросы IN , но это работает на групповом
индексе в подзапросах следующей формы:
value IN (SELECT key_column
FROM single_table WHERE some_expr )
-
range
Только строки, которые находятся в данном диапазоне, получены,
используя индекс, чтобы выбрать строки. key
в выходной строке указывает, которые индексы используются.
key_len содержит самую длинную ключевую часть, которая
использовалась. ref = NULL для этого типа.
range
может использоваться, когда ключевой столбец сравнивается с постоянной с
использованием любого из операторов
= ,
<> ,
> ,
>= ,
< ,
<= ,
IS NULL ,
<=> ,
BETWEEN или
IN() :
SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND
key_part2 IN (10,20,30);
-
index
Тип соединения index то же самое, как
ALL
за исключением того, что индексное дерево просмотрено.
Это происходит двумя путями:
Если индексирование является покрытием для запросов и
может использоваться, чтобы удовлетворить все данные, требуемые от таблицы,
только индексное дерево просмотрено. В этом случае столбец
Extra сообщает Using index .
Только индексированный просмотр обычно быстрее, чем
ALL , поскольку
размер индексирования обычно меньше, чем табличные данные.
- Полное сканирование таблицы выполнено, используя чтения
из индекса строк данных в индексном порядке.
Uses index
не появляется в столбце Extra .
MySQL может использовать этот тип соединения, когда запрос использует
только столбцы, которые являются частью одного индекса.
-
ALL
Полное сканирование таблицы сделано для каждой комбинации строк от
предыдущих таблиц. Это обычно не хорошо, если таблица первая таблица,
не отмеченная как const
и обычно очень плохо во всех других случаях.
Обычно Вы можете избежать ALL
добавлением индексов, которые включают извлечение строки от таблицы,
основанной на постоянных величинах или значениях столбцов от
более ранних таблиц.
Дополнительная информация EXPLAIN
Столбец 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 .
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 , невозможно выбрать строки.
Выходная интерпретация EXPLAIN
Вы можете получить хороший признак того, насколько хорошо соединение,
принимая произведение значений в столбце 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;
Для этого примера, сделайте следующие предположения:
Первоначально, прежде, чем любая оптимизация была выполнена,
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.
9.8.3. Формат вывода EXPLAIN EXTENDED
Когда 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
, который использует значение константы.
9.8.4.
Получение информации о плане выполнения для названного соединения
Чтобы получить план выполнения относительно объяснимого выполнения запроса
в названном соединении, используйте это запрос:
EXPLAIN [options ] FOR CONNECTION connection_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 .
9.8.5. Оценка работы запроса
В большинстве случаев Вы можете оценить работу запроса, считая дисковые
поиски. Для маленьких таблиц Вы можете обычно находить строку в один дисковый
поиск (потому что индекс, вероятно, кэшируется). Для больших таблиц Вы можете
оценить, что, используя 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.
9.9.
Управление оптимизатором запросов
MySQL обеспечивает управление оптимизатором через системные переменные,
которые затрагивают, как планы запроса оценены, переключают оптимизацию,
подсказки оптимизатора и индекса и модель стоимости.
Сервер также поддерживает статистику о значениях столбцов, хотя
оптимизатор еще не использует эту информацию.
9.9.1.
Управление оценкой плана запроса
Задача оптимизатора состоит в том, чтобы найти оптимальный план
относительно выполнения запроса 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,
чтобы сказать оптимизатору определить значение автоматически.
9.9.2.
Управление переключаемой оптимизацией
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 |
Сбросьте каждую оптимизацию к ее значению по умолчанию. |
opt_name =default
|
Установите названную оптимизацию в ее значение по умолчанию. |
opt_name =off
|
Отключите названную оптимизацию. |
opt_name =on
|
Включите названную оптимизацию. |
Порядок команд в значении не имеет значения, хотя
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
9.9.3. Подсказки оптимизатору
Одно средство контроля стратегий оптимизатора состоит в том, чтобы
установить
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) */ ...
Когда комментарий подсказки содержит многократные подсказки, возможность
дубликатов и конфликтов существует:
Имена блока запроса это идентификаторы, они неотступно следуют обычным
правилам, какие имена допустимы и как заключить их в кавычки (см.
раздел 10.2).
Имена подсказки, имена блока запроса и имена стратегии не являются
чувствительными к регистру. Ссылки на имена таблицы и индекса следуют обычным
правилам чувствительности к регистру идентификатора (см.
раздел 10.2.2).
Подсказки оптимизатора на уровне таблицы
Эффект подсказок на уровне таблицы:
Эти типы подсказки относятся к определенным таблицам или всем
таблицам в блоке запроса.
Синтаксис подсказок на уровне таблицы:
hint_name ([@query_block_name ]
[tbl_name [, tbl_name ] ...])
hint_name ([tbl_name @query_block_name
[, tbl_name @query_block_name ] ...])
Синтаксис ссылается на эти термины:
hint_name : Эти имена подсказки разрешены:
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 : Эти имена подсказки разрешены:
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 ...)) ...
Получающийся эффект следующий:
Имена блока запроса это идентификаторы, они неотступно следуют обычным
правилам, какие имена допустимы и как заключить их в кавычки (см.
раздел 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.4. Индексные подсказки
Индексные подсказки дают оптимизатору информацию о том, как выбрать индекс
во время обработки запроса. Индексные подсказки, описанные здесь, отличаются
от подсказок оптимизатора, описанных в
разделе 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 (index_list )
говорит MySQL использовать только один из названных индексов, чтобы найти
строки в таблице. Альтернативный синтаксис IGNORE INDEX
(index_list ) говорит MySQL
не использовать некоторый индекс или индексы. Эти подсказки полезны, если
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 ... ;
9.9.5. Модель стоимости оптимизатора
Чтобы произвести планы выполнения, оптимизатор использует модель
стоимости, которая основана на оценках стоимости различных операций, которые
происходят во время выполнения запроса. Оптимизатор имеет ряд
констант стоимости по умолчанию, чтобы принять решения
относительно планов выполнения.
У оптимизатора также есть база данных смет, чтобы использовать во время
конструкции плана выполнения. Эти оценки сохранены в таблицах
server_cost и engine_cost в системной базе данных
mysql и конфигурируемы в любое время. Намерение этих таблиц
состоит в том, чтобы позволить легко скорректировать сметы, которые
оптимизатор использует, когда пытается достичь планов выполнения запроса.
Модель стоимости
Конфигурируемые оптимизатором модели стоимости работают так:
Сервер читает таблицы модели стоимости в память при запуске и
использует значения в памяти во время выполнения. Любая не-NULL
смета, определенная в таблицах, имеет приоритет перед
постоянной стоимостью по умолчанию. Любая NULL
оценка указывает оптимизатору использовать стоимость по умолчанию.
- Во время выполнения сервер может перечитать таблицы стоимости.
Это происходит, когда механизм хранения динамически загружен или когда
выполнен
FLUSH OPTIMIZER_COSTS .
- Таблицы стоимости позволяют администраторам сервера легко скорректировать
сметы, изменяя записи в таблицах. Также легко вернуться к
значению по умолчанию, устанавливая стоимость в
NULL .
Оптимизатор использует значения стоимости в памяти, так что изменения таблиц
должны сопровождаться FLUSH OPTIMIZER_COSTS
.
- Сметы в памяти, которые актуальны, когда сеанс клиента начинается,
применяются всюду по этому сеансу, пока это не заканчивается. В частности,
если сервер перечитывает таблицы стоимости, любые измененные оценки
применяются только к впоследствии запущенным сеансам.
Существующие сеансы не затронуты.
- Таблицы стоимости являются определенными для данного экземпляра сервера.
Сервер не копирует табличные изменения стоимости ведомым устройствам.
База данных модели стоимости
Образцовая база данных стоимости оптимизатора состоит из двух таблиц в
базе данных mysql , которые содержат информацию о смете для
операций, которые происходят во время выполнения запроса:
Таблица 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;
9.9.6. Статистика оптимизатора
Таблица column_stats базы данных mysql
разработана, чтобы сохранить статистику о значениях столбцов.
В настоящее время оптимизатор еще не консультируется с
column_stats в ходе выполнения запроса.
У таблицы column_stats есть эти характеристики:
У таблицы column_stats есть эти столбцы:
database_name , table_name ,
column_name : Названия базы данных, таблицы и столбца, для
которого применяются статистические данные. Эти имена формируют первичный
ключ для строк в column_stats .
histogram : Значение JSON
, описывающее статистику для столбца,
сохраненного как гистограмма.
column_stats использует
JSON , чтобы разрешить гибкость в
представлении статистики столбца. Статистические данные для столбца принимают
форму гистограммы, содержащей buckets для частей
диапазона значений, сохраненных в столбце.
9.10. Буферизация и кэширование
MySQL использует несколько стратегий, которые кэшируют в памяти
информацию, чтобы увеличить производительность.
9.10.1.
Оптимизация буферного пула InnoDB
InnoDB поддерживает область
хранения, названную буферным пулом
для того, чтобы кэшировать данные и индексы в памяти. Знание, как
работает пул и использование его в своих интересах, чтобы сохранить данные,
к которым часто получают доступ, в памяти, это важный аспект настройки MySQL.
Для объяснения внутренних работ пула InnoDB см.
раздел 16.6.3.1.
Для дополнительной конфигурации пула см. эти разделы:
9.10.2. Ключевой кэш MyISAM
Чтобы минимизировать дисковый ввод/вывод, MyISAM
эксплуатирует стратегию, которая используется многими системами управления
базой данных. Это использует механизм кэша, чтобы сохранить табличные блоки,
к которым наиболее часто получают доступ, в памяти:
Этот раздел сначала описывает основную работу ключевого кэша
MyISAM . Далее это обсуждает особенности, которые улучшают
работу ключевого кэша и позволяют Вам лучше управлять работой кэша:
Чтобы управлять размером ключевого кэша, используйте переменную
key_buffer_size .
Если эта переменная установлена равной 0, никакой ключевой кэш не
используется. Ключевой кэш также не используется, если значение
key_buffer_size
является слишком маленьким, чтобы выделить минимальное число буферов блоков.
Когда ключевой кэш не работает, к индексным файлам получают доступ,
используя только родную буферизацию файловой системы, обеспеченную
операционной системой. Другими словами, индексные блоки получены, используя
ту же самую стратегию, как для табличных блоков данных.
Индексный блок это непрерывный модуль доступа к индексным файлам
MyISAM . Обычно размер индексного блока равен размеру узлов
индексного B-дерева. Индексы представлены на диске, используя структуру
данных B-дерева. Узлы у основания дерева это узлы листа. Узлы выше узлов
листа это узлы нелиста.
Все блоки буферов в ключевой структуре кэша имеют тот же самый размер.
Этот размер может быть равным, больше чем, или меньше, чем размер индексного
блока таблицы. Обычно эти два значения кратны.
Когда к данным из индексного блока любой таблицы нужно получить доступ,
сервер сначала проверяет, доступно ли это в некотором буфере
ключевого кэша. Если это так, данные ищутся ключевом кэше, а не на диске.
Таким образом, это читает из кэша вместо того, чтобы читать с диска.
Иначе сервер выбирает кэш, содержащий иной индексный блок (или блоки) и
заменяет данные там копией необходимой информации. Как только новый индексный
блок находится в кэше, к индексным данным можно получить доступ.
Если происходит, что блок, выбранный для замены, был изменен, блок считают
грязным. В этом случае до замены его содержание
сбрасывается на диск.
Обычно сервер следует стратегии LRU (Least Recently Used):
выбирая блок для замены, это выбирает последний использованный индексный
блок. Чтобы сделать этот выбор легче, ключевой модуль кэша поддерживает все
используемые блоки в специальном списке (LRU chain),
упорядоченный временем использования. Когда к блоку получают доступ, это
используется и помещено в конце списка. Когда блоки должны быть заменены,
блоки в начале списка использованы последними и становятся первыми
кандидатами на выгрузку.
InnoDB тоже использует LRU,
чтобы управлять его буферным пулом. См.
раздел 16.6.3.1.
9.10.2.1. Совместно используемый ключевой доступ
Потоки могут обращаться к кэшу ключей одновременно,
согласно следующим условиям:
Совместно используемый доступ к ключевому кэшу позволяет серверу
улучшить пропускную способность значительно.
9.10.2.2. Многократные ключевые кэши
Совместно используемый доступ к ключевому кэшу улучшает работу, но не
устраняет проблемы среди сеансов полностью. Они все еще конкурируют за
структуры управления, которые управляют доступом к ключевым буферам кэша.
Чтобы уменьшить проблемы доступа кэша далее, 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% места,
выделенного для всех ключевых кэшей. Используйте это для таблиц, которые в
большой степени используются для поисков, но не обновлены.
- cold, который занимает 20% места, выделенного
для всех ключевых кэшей. Используйте этот кэш для интенсивно измененных
таблиц среднего размера, таких как временные таблицы.
- warm, который занимает 60%
ключевого пространства кэша. Используйте это как ключевой кэш по умолчанию,
чтобы использоваться по умолчанию для всех других таблиц.
Одна причина использование трех ключевых кэшей
состоит в том, что доступ к одной ключевой структуре кэша не блокирует
доступ к другим. Запросы, назначенные на один кэш, не конкурируют с
запросами, назначенными на другой кэш. Прирост производительности происходит
также и по другим причинам:
Горячий кэш используется только для запросов извлечения,
таким образом, его содержание никогда не изменяется.
Следовательно, всякий раз, когда индексный блок должен быть втянут с диска,
содержание блока кэша, выбранного для замены, не должно сбрасываться.
- Для индексирования назначенного на горячий кэш, если нет никаких
запросов, требующих просмотра индекса, есть высокая вероятность, что
блоки, соответствующие узлам нелиста B-дерева, остаются в кэше.
- Работа обновления, наиболее часто выполненная для временных таблиц,
выполнена намного быстрее, когда обновленный узел находится в кэше и не
должен быть считан с диска сначала. Если размер индексирования временных
таблиц сопоставим с размером холодного ключевого кэша, вероятность очень
высока, что обновленный узел находится в кэше.
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
9.10.2.3. Стратегия вставки середины
По умолчанию ключевая система управления кэша использует простую
стратегию 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. Тогда ценные
узлы сохранены в горячем подсписке во время работы просмотра индекса.
9.10.2.4.
Предварительно загруженный индекс
Если есть достаточно много блоков в ключевом кэше, чтобы разместить
блоки всего индекса или по крайней мере блоки, соответствующие его узлам
нелиста, имеет смысл предварительно загружать ключевой кэш индексными блоками
прежде, чем начать использовать это. Предварительная загрузка позволяет
поместить индексные блоки в ключевой буфер кэша самым эффективным способом:
читая индексные блоки с диска последовательно.
Без предварительной загрузки блоки все еще помещены в ключевой кэш как
необходимо запросам. Хотя блоки останутся в кэше, потому что есть достаточно
много буферов для всех них, они забраны с диска в случайном
порядке, а не последовательно.
Чтобы предварительно загрузить индексирование в кэш, используйте
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 , блоки
предварительно загружаются в этот кэш. Иначе индекс загружен в ключевой
кэш по умолчанию.
9.10.2.5. Ключевой размер блока кэша
Возможно определить размер буферов блоков для отдельного ключевого кэша,
используя
key_cache_block_size . Это разрешает настраивать
исполнение операций ввода/вывода для индексных файлов.
Лучшая работа для операций ввода/вывода достигнута, когда размер буферов
чтения равен размеру родных буферов ввода/вывода операционной системы. Но
установка размера ключевых узлов, равных размеру буфера ввода/вывода, не
всегда гарантирует лучшую эффективность работы.
Читая большие узлы листа, сервер вытягивает в большом количестве ненужные
данные, эффективно предотвращая чтение других узлов листа.
Чтобы управлять размером блоков в индексных файлах .MYI ,
используйте опцию
--myisam-block-size при запуске сервера.
9.10.2.6.
Реструктурирование ключевого кэша
Ключевой кэш может быть реструктурирован в любое время, обновляя
его значения. Например:
mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;
Если Вы назначаете
key_buffer_size или
key_cache_block_size
ключевому компоненту кэша значение, которое отличается от
текущего значения компонента, сервер разрушает старую структуру кэша и
создает новый, основанный на новых значениях. Если кэш содержит какие-либо
грязные блоки, сервер сохраняет их на диск прежде, чем разрушить и обновить
кэш. Реструктурирование не происходит, если Вы изменяете другие
ключевые параметры кэша.
Реструктурируя ключевой кэш, сервер сначала сбрасывает
содержание любых грязных буферов на диск.
После этого содержание кэша становится недоступным.
Однако, реструктурирование не блокирует запросы, которые должны
использовать индекс, назначенный на кэш. Вместо этого сервер непосредственно
получает доступ к индексам таблицы, используя родное кэширование файловой
системы. Кэширование файловой системы не столь же эффективно, как
использование ключевого кэша, хотя запросы выполняются, замедление может
ожидаться. После того, как кэш был реструктурирован,
это становится доступным снова для кэширования назначенных индексов,
и использование файловой системы для кэширования индексов прекращается.
9.10.3. Кэш запроса MySQL
Кэш запроса хранит текст SELECT
вместе с соответствующим результатом, который послали клиенту.
Если идентичный запрос получен позже, сервер получает результат
запроса из кэша вместо того, чтобы разобрать и выполнить запрос снова.
Кэш запроса совместно использован сеансами, таким образом, набор результатов,
произведенный одним клиентом, можно послать в ответ на тот же самый запрос,
выпущенный другим клиентом.
Кэш запроса может быть полезным в окружающей среде, где у Вас есть
таблицы, которые не изменяются очень часто и для которых сервер получает
много идентичных запросов. Это типичная ситуация для многих веб-серверов,
которые производят много динамических страниц, основанных на
контенте базы данных.
Кэш запроса не возвращает устаревшие данные. Когда таблицы изменены, любые
соответствующие записи в кэше запроса сбрасываются.
Кэш запроса не работает в окружающей среде, где у Вас есть многократные
серверы mysqld
, обновляющие те же самые таблицы MyISAM .
Кэш запроса используется для готовых запросов при условиях, описанных в
разделе 9.10.3.1.
Кэш запроса не поддержан для разделенных таблиц и автоматически отключен
для запросов, вовлекающих разделенные таблицы. Кэш запроса не может
быть включен для таких запросов.
Некоторые характеристики для кэша запроса приведены ниже.
Эти результаты были произведены, выполняя эталонный набор MySQL на Linux
Alpha 2*500MHz 2GB RAM с кэшем запроса в 64MB.
Если все запросы, которые Вы выполняете, просты (такие как выбор
строки из таблицы с одной строкой), но все еще отличаются так, чтобы запросы
не могли кэшироваться, издержки для того, чтобы иметь активный кэш запроса
составляют 13%. Это может быть расценено как худший вариант развития событий.
В действительности запросы имеют тенденцию быть намного более сложными,
таким образом, издержки обычно значительно ниже.
- Поиски единственной строки в таблице из единственной строки на 238%
быстрее с кэшем запроса, чем без него. Это может быть расценено как близкое к
минимальному ускорению, которое будет ожидаться для
запроса, который кэшируется.
Чтобы отключить кэш запроса при запуске сервера, установите
query_cache_size
в 0.
Кэш запроса предлагает потенциал для существенного исполнительного
усовершенствования, но не предполагайте, что это сделает так при всех
обстоятельствах. С некоторыми конфигурациями кэша запроса или рабочими
нагрузками сервера, Вы могли бы фактически видеть
даже снижение производительности:
Будьте осторожны с чрезмерно большим кэшем запроса,
который увеличивает издержки, требуемые, чтобы поддержать кэш, возможно,
сверх выгоды от его включения. Размеры в десятки мегабайтов обычно выгодны.
Размеры в сотни мегабайтов не нужны.
- Рабочая нагрузка сервера имеет существенный эффект на эффективность кэша
запроса. Соединение запроса, состоящее почти полностью из фиксированного
набора
SELECT , намного более
вероятно извлечет выгоду из включения кэшу, чем соединение, в котором частый
INSERT вызывает непрерывное
аннулирование результатов в кэше. В некоторых случаях обходное решение должно
использовать опцию SQL_NO_CACHE , чтобы предотвратить загрузку
данных в кэш для SELECT , которые
используют часто изменяемые таблицы. См.
раздел 9.10.3.2.
Чтобы проверить, что включение кэша запроса выгодно, проверьте работу
своего сервера MySQL с включенным и отключенным кэшем. Потом повторно
проверяйте периодически, потому что эффективность кэша запроса может
измениться в зависимости от того, как рабочая нагрузка сервера изменяется.
9.10.3.1. Как кэш запроса работает
Этот раздел описывает, как кэш запроса работает, когда это является
операционным. Раздел 9.10.3.3
описывает, как управлять, является ли это операционным.
Поступающие запросы сравниваются с кэшем запроса перед парсингом,
таким образом, следующие два запроса расценены как отличающиеся:
SELECT * FROM tbl_name
Select * from tbl_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 .
- Это не использует таблиц.
- Это производит предупреждения.
- У пользователя есть привилегия на уровне столбца для любой
из вовлеченных таблиц.
9.10.3.2.
Опции SELECT кэша запроса
Два запроса, связанные с кэшем, могут быть определены в
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;
9.10.3.3.
Конфигурация кэша запроса
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
влияет, как она работает. Эта переменная может быть
установлена в следующие значения:
Если 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 :
9.10.3.4.
Состояние кэша запроса и обслуживание
Чтобы проверить, присутствует ли кэш запроса в Вашем сервере 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.
9.10.4.
Кэширование готовых запросов и сохраненных программ
Для определенных запросов, которые клиент мог бы выполнить многократно во
время сеанса, сервер преобразовывает запрос к внутренней структуре и кэширует
структуру, которая будет использоваться во время выполнения.
Кэширование позволяет серверу выступить более эффективно, потому что это
избегает издержек перепреобразования запроса, которое должно быть необходимо
снова во время сеанса. Преобразование и кэширование происходят
для этих запросов:
Готовые запросы, обработанные на уровне 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 :
CASE case_expr
WHEN when_expr1 ...
WHEN when_expr2 ...
WHEN when_expr3 ...
...
END CASE
Если изменение метаданных затрагивает только WHEN
when_expr3 , то выражение повторно разобрано.
case_expr и другие WHEN
повторно не разобраны.
Перепарсинг использует базу данных значения по умолчанию и режим SQL,
которые были в действительности для оригинального преобразования
во внутреннюю форму.
Сервер пытается повторно разобрать до трех раз.
Ошибка происходит, если все попытки терпят неудачу.
Перепарсинг является автоматическим.
Для готовых запросов
Com_stmt_reprepare
отслеживает число переприготовлений.
9.11. Оптимизации операций блокировки
MySQL управляет контентом для таблиц с использованием
блокировки:
Внутренняя блокировка выполнена в пределах сервера MySQL
непосредственно, чтобы управлять контентом для таблицы многократными
потоками. Этот тип блокировки является внутренним, потому что это выполнено
полностью сервером и не вовлекает никакие другие программы. См.
раздел 9.11.1.
- Внешняя блокировка происходит, когда сервер и другие программы блокируют
MyISAM , чтобы
скоординировать между собой, какая программа может получить доступ к
таблицам. См. раздел 9.11.5.
9.11.1. Внутренние методы блокировки
Этот раздел обсуждает внутреннюю блокировку,
то есть, блокировкой, выполненной в пределах сервера MySQL непосредственно,
чтобы управлять табличным содержанием многократными сеансами.
Этот тип блокировки является внутренним, потому что это выполнено полностью
сервером и не вовлекает никакие другие программы. Для того, чтобы блокировать
файлы MySQL другими программами, см.
раздел 9.11.5.
Блокировка на уровне строки
MySQL использует блокировку на
уровне строки для InnoDB , чтобы поддержать одновременную
запись многократными сеансами, делая их подходящими для
многопользовательских, очень параллельных приложений OLTP.
Чтобы избежать тупиков,
с многократными параллельными операциями записи на InnoDB ,
приобретите необходимые блокировки в начале транзакции, запросив
SELECT ... FOR UPDATE для каждой группы строк, которая будет
изменена, даже если запросы изменения данных прибывают позже в транзакцию.
Если транзакции изменяют или блокируют больше, чем одну таблицу, делают
применимые запросы в том же самом порядке в пределах каждой транзакции.
Тупики затрагивают работу вместо того, чтобы представить серьезную ошибку,
потому что InnoDB автоматически
обнаруживает
условия тупика по умолчанию и откатывает до прежнего уровня одну
из затронутых транзакций.
На системах высокого параллелизма обнаружение тупика может вызвать
замедление, когда многочисленные потоки ждут той же самой блокировки.
Время от времени может быть более эффективно отключить обнаружение тупика и
положиться на настройку
innodb_lock_wait_timeout для операционной отмены, когда
тупик происходит. Обнаружение тупика может быть отключено, используя опцию
innodb_deadlock_detect .
Преимущества блокировки на уровне строки:
Блокировка на уровне таблицы
MySQL использует блокировку на
уровне таблицы для MyISAM , MEMORY и
MERGE , разрешая только одному сеансу обновить те таблицы за один
раз. Этот уровень блокировки делает эти механизмы хранения более подходящими
для однопользовательских приложений, только для чтения или
чтения главным образом.
Эти механизмы хранения избегают
тупиков, всегда прося все
необходимые блокировки сразу в начале запроса и всегда блокируя таблицы в том
же самом порядке. Проблема состоит в том, что эта стратегия уменьшает
параллелизм: другие сеансы, которые хотят изменить таблицу, должны ждать до
окончания запроса изменения данных.
Преимущества блокировки на уровне таблицы:
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;
Выбор типа блокировки
Вообще, табличные блокировки превосходят блокировки на уровне
строки в следующих случаях:
С высокоуровневыми блокировками Вы можете более легко настроить
приложения, поддерживая блокировки различных типов, потому что издержки
блокировки меньше, чем для блокировок на уровне строки.
Опции кроме блокировки на уровне строки:
Versioning (такой, как используемый в MySQL для параллельных
вставок), где возможно иметь много чтений с одной записью. Это означает, что
база данных или таблица поддерживают другие представления для данных в
зависимости от того, когда доступ начинается. Другие распространенные слова
для этого time travel, copy on write
или copy on demand.
- Копия по требованию во многих случаях превосходит блокировку на
уровне строки. Однако, в худшем случае, это может использовать намного
больше памяти, чем использование нормальных блокировок.
- Вместо того, чтобы использовать блокировки на уровне строки,
Вы можете использовать блокировки уровня приложения, такие как обеспеченные
функциями
GET_LOCK() и
RELEASE_LOCK() в
MySQL. Это консультативные блокировки, таким образом, они работают только с
приложениями, которые сотрудничают друг с другом. См.
раздел 13.18.
9.11.2. Табличные проблемы блокировки
InnoDB использует блокировку на уровне строки так, чтобы
многократные сеансы и приложения могли читать из и писать в ту же самую
таблицу одновременно, не заставляя друг друга ждать. Для этого механизма
хранения избегайте использования LOCK
TABLES , потому что это не предлагает дополнительной защиты, но
вместо этого уменьшает параллелизм. Автоматическая блокировка на уровне
строки делает эти таблицы подходящими для Ваших самых занятых баз данных с
Вашими самыми важными данными, также упрощая логику приложения, так как Вы не
должны заблокировать таблицы.
MySQL использует табличную блокировку (вместо страницы, строки или
блокировки столбца) для всех механизмов хранения, кроме InnoDB .
У операций самой блокировки нет многих издержек. Но потому что только один
сеанс может писать таблицу в любой момент, для лучшей работы с этими другими
механизмами хранения, используйте их прежде всего для таблиц, которые
запрашиваются часто и редко обновляются.
Исполнительное одобрение соображений InnoDB
Выбирая, создать ли таблицу InnoDB , имейте в виду следующие
недостатки табличной блокировки:
Табличная блокировка позволяет многим сеансам читать из таблицы в
то же самое время, но если сеанс хочет писать таблицу, это должно сначала
получить эксклюзивный доступ, означая, что этому, возможно, придется ждать
других сеансов, чтобы закончить с таблицей сначала. Во время обновления
должны ждать все другие сеансы, которые хотят получить доступ к этой особой
таблице, пока обновление не сделано.
SELECT , который занимает много
времени, препятствует тому, чтобы другие сеансы обновили таблицу тем
временем, заставляя другие сеансы казаться медленными или безразличными.
В то время как сеанс ждет, чтобы получить эксклюзивный доступ к таблице для
обновлений, другие сеансы, создавшие запрос
SELECT ,
будут стоять в очереди позади этого, уменьшая параллелизм даже для сеансов
только для чтения.
Обходные решения для проблем блокировки
Следующие элементы описывают некоторые способы избежать или уменьшить
проблемы, вызванные табличной блокировкой:
9.11.3. Параллельные вставки
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 (параллельные вставки),
в то время, как блокировка проводится. Однако, это не может использоваться,
если Вы собираетесь управлять процессами использования базы данных, внешними
к серверу в то время, как Вы держите блокировку.
9.11.4. Блокировка метаданных
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 , выпущены
как только запрос был подготовлен, даже если подготовка происходит в пределах
транзакции многократного запроса.
9.11.5. Внешняя блокировка
Внешняя блокировка это использование блокировки файловой системы, чтобы
управлять сдержимым базы данных
MyISAM многократными процессами. Внешняя блокировка
используется в ситуациях, где единственный процесс, такой как сервер MySQL,
как может предполагаться, не является единственным процессом, который требует
доступа к таблицам. Вот некоторые примеры:
Если Вы выполняете много серверов, которые используют тот же самый
каталог базы данных (не рекомендуется так делать!), каждому серверу нужно
было включить внешнюю блокировку.
- Если Вы используете
myisamchk, чтобы выполнить табличные операции
обслуживания на
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, Вы должны гарантировать, что
следующие условия удовлетворены:
Самый легкий способ удовлетворить эти условия состоит в том, чтобы
всегда использовать
--external-locking с
--delay-key-write=OFF и
--query-cache-size=0
. Это не сделано по умолчанию, потому что во многих установках
полезно иметь смесь предыдущих опций.
9.12. Оптимизация MySQL Server
Этот раздел обсуждает методы оптимизации для сервера базы данных,
прежде всего имея дело с системной конфигурацией вместо того,
чтобы настроить запросы SQL. Информация в этом разделе является подходящей
для DBA, кто хочет гарантировать работу и масштабируемость через серверы,
которыми они управляют, для разработчиков, создающих скрипт установки,
которые включают базы данных, и выполнение MySQL непосредственно для
развития, тестирования и так далее для тех, кто хочет максимизировать
их собственную производительность.
9.12.1. Оптимизация дискового I/O
Этот раздел описывает способы сконфигурировать устройства хранения данных,
когда Вы можете посвятить больше и более быстрые аппаратные средства хранения
серверу базы данных. Для информации об оптимизации 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
Striping подразумевает, что Вы имеете много дисков и помещаете первый блок
на первый диск, второй блок на второй диск и N -й блок
на диск (N MOD number_of_disks
). Это означает, что если Ваш нормальный размер данных меньше,
чем размер полосы (или отлично выровнен), Вы получаете намного лучшую работу.
Striping очень зависит от операционной системы и размера полосы, так
определите эффективность своего приложения с различными размерами полосы. См.
раздел 9.13.2.
Скорость для striping очень зависит
от параметров. В зависимости от того, как Вы устанавливаете
параметры и число дисков, Вы можете измерить различия на порядки величин.
Вы должны оптимизировать для случайного или последовательного доступа.
- Для надежности Вы можете хотеть использовать RAID 0+1 (striping плюс
зеркало), но в этом случае Вы нуждаетесь в 2*
N дисках
для хранения N блоков данных. Это, вероятно, наилучший
вариант, если у Вас есть деньги для него. Однако, Вам, вероятно, также
придется вложить капитал в некоторое программное обеспечение управления,
чтобы обработать это эффективно.
- Хорошая опция должна изменить уровень RAID согласно тому, насколько
важный тип данных использован. Например, храните полуважные данные, которые
могут быть восстановлены, на RAID 0, но храните действительно важные данные,
такие как информация об узле, на RAID 0+1 или RAID
N .
RAID N может быть проблемой, если Вы имеете, много
записей из-за времени, требуемого, чтобы обновить биты четности.
- Вы можете также установить параметры для файловой системы,
которую использует база данных:
Если Вы не должны знать, когда файлы были в последний раз использованы,
Вы можете установить свои файловые системы с опцией -o noatime .
Это пропускает обновления последнего времени доступа в inodes
на файловой системе.
На многих операционных системах Вы можете установить файловую систему,
которая будет обновлена асинхронно, устанавливая это с опцией -o
async . Если Ваш компьютер разумно устойчив, это должно дать Вам лучшую
работу, не жертвуя слишком большой надежностью.
Этот флаг идет по умолчанию в Linux.
9.12.2. Использование символических ссылок
Вы можете переместить базы данных или таблицы от каталога базы данных
в другое место и заменить их символическими ссылками к новым местоположениям.
Вы могли бы хотеть сделать это, например, чтобы переместить базу данных в
файловую систему с большим свободным пространством или увеличить скорость
Вашей системы, распространяя Ваши таблицы к различным дискам.
Для InnoDB используйте DATA DIRECTORY в
CREATE TABLE
вместо символических ссылок, как объяснено в
разделе 16.7.5.
Чтобы определить местоположение Вашего каталога данных,
используйте этот запрос:
SHOW VARIABLES LIKE 'datadir';
9.12.2.1.
Используя символические ссылки для баз данных по Unix
В 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 , проблемы, вероятно, произойдут.
9.12.2.2.
Используя символические ссылки для таблиц MyISAM на Unix
Символьные ссылки полностью поддержаны только для MyISAM .
Для файлов, используемых таблицами для других механизмов хранения, Вы можете
получить странные проблемы, если Вы пытаетесь использовать символические
ссылки. Для InnoDB используйте альтернативный метод,
объясненный в разделе 16.7.5.
Не делайте таблицы символьной ссылки на системах, у которых нет полностью
рабочего вызова realpath() . Linux и Solaris поддерживают
realpath() . Чтобы определить, поддерживает ли Ваша система
символические ссылки, проверьте значение переменной
have_symlink :
SHOW VARIABLES LIKE 'have_symlink';
Обработка символических ссылок для MyISAM
работает следующим образом:
В каталоге данных у Вас всегда есть файлы данных
(.MYD ) и индекса (.MYI ).
Они могут быть перемещены в другое место и заменены в каталоге
данных символьными ссылками.
- Вы можете сделать символьной ссылкой файл с данными и индексный файл
независимо к различным каталогам.
- Чтобы проинструктировать рабочий сервер MySQL выполнять работу со
ссылками, используйте опции
DATA DIRECTORY и
INDEX DIRECTORY в CREATE
TABLE . См. раздел 14.1.15.
Альтернативно, если mysqld
не работает, эффект может быть достигнут вручную,
используя ln -s в командной строке.
Путь, используемый с DATA
DIRECTORY и INDEX DIRECTORY , возможно, не включает
каталог MySQL data (Bug #32167).
- myisamchk
не заменяет символьную ссылку файлом с данными или индексным файлом. Это
работает непосредственно над файлом, на который указывает символьная ссылка.
Любые временные файлы создаются в каталоге, где файл с данными или индексный
файл расположены. То же самое верно для
ALTER TABLE ,
OPTIMIZE TABLE и
REPAIR TABLE .
- Когда Вы удаляете таблицу, которая использует символьные ссылки, удалены
символьная ссылка и файл, на который она указывает.
Это чрезвычайно серьезное основание не выполнять
mysqld
как
root или системный пользователь с доступом на запись к
каталогам базы данных MySQL.
- Если Вы переименовываете таблицу с
ALTER TABLE ... RENAME или
RENAME TABLE и Вы не перемещаете таблицу в другую базу данных,
символьные ссылки в каталоге базы данных переименованы к новым именам,
файл с данными и индексный файл переименованы соответственно.
- Если Вы используете
ALTER TABLE
... RENAME или RENAME TABLE
, чтобы переместить таблицу в другую базу данных, таблица
перемещена в другой каталог базы данных. Если имя таблицы изменилось,
символьные ссылки в новом каталоге базы данных переименованы к новым именам,
файл с данными и индексный файл переименованы соответственно.
- Если Вы не используете символьные ссылки, запустите
mysqld с
опцией
--skip-symbolic-links , чтобы гарантировать, что никто не может
использовать mysqld
, чтобы удалить или переименовать файл за
пределами каталога данных.
Эти табличные операции для символьной ссылки не поддержаны:
9.12.2.3.
Используя символические ссылки для баз данных в Windows
В 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 .
9.12.3. Оптимизация использования памяти
9.12.3.1. Как MySQL использует память
MySQL выделяет буферы и кэши, чтобы улучшить исполнение операций базы
данных. Конфигурация значения по умолчанию разработана, чтобы позволить
MySQL Server запускаться на виртуальной машине, у которой есть приблизительно
512 МБ RAM. Вы можете улучшить работу MySQL, увеличивая значения кэша и
связанных с буфером системных переменных. Вы можете также изменить
конфигурацию по умолчанию, чтобы выполнить MySQL на
системах с ограниченной памятью.
Следующий список описывает некоторые из способов, которыми MySQL
использует память. Где применимо, на соответствующие системные переменные
ссылаются. Некоторые элементы определены для механизмов хранения.
Буферный пул InnoDB область памяти, которая
кэширует данные для таблиц, индексов и другие вспомогательные буферы.
Для эффективности большого объема операций буферный пул разделен на
страницы, которые могут потенциально
держать много строк. Для эффективности управления кэшем буферный пул
осуществлен как связанный список страниц, данные, которые редко используются,
удаляются из кэша, используя измененный алгоритм
LRU. См.
раздел 16.6.3.1.
Размер буферного пула важен для системной работы.
- MySQL Performance Schema
это особенность контроля выполнения сервера MySQL на низком уровне.
Performance Schema динамически выделяет память с приращением, масштабируя ее
к фактической загрузке сервера, вместо того, чтобы выделить требуемую память
во время запуска сервера. Как только память выделена, она не освобождена,
пока сервер не перезапущен. Для получения дополнительной информации см.
раздел 23.14.
- Все потоки совместно используют кэш ключей
MyISAM ,
его размер определен
key_buffer_size . Другие буферы, используемые сервером, выделены,
когда необходимо. См. раздел 6.1.1
.
Для каждой открытой таблицы MyISAM
индексный файл открыт однажды, файл с данными открыт однажды для каждого
рабочего потока. Для каждого параллельного потока выделены: структуры
таблицы, структуры столбца для каждого столбца и буфер размера 3 *
N (здесь N это
максимальная длина строки, не учитывая BLOB
). Столбец BLOB
требует пяти-восьми байтов плюс длина данных
BLOB . MyISAM
поддерживает один дополнительный буфер строки для внутреннего пользования.
- Каждый поток, который используется, чтобы управлять соединениями клиента,
использует некоторое определенное для потока пространство. Следующий список
указывает на них и какие переменные управляют размером:
Буфер соединения и результата начинают с размера, равного
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 определяет, на сколько таблице разрешают
вырасти, нет никакого преобразования в формат на диске.
- Большинство запросов, которые выполняют сортировку, выделяют буфер
и от ноля до двух временных файлов, в зависимости от размера набора
результатов. См. раздел B.5.3.5.
- Почти весь парсинг и вычисление сделаны в местных потоках
и пулах памяти многократного использования. Никакая память не необходима для
мелочей, таким образом, нормального медленного распределения памяти и
освобождения избегают. Память выделена только для неожиданно больших строк.
- Для каждого столбца
BLOB
буфер увеличен динамически, чтобы читать значение
BLOB .
Если Вы просматриваете таблицу, выделен буфер, столь же большой, как самое
большое значение BLOB .
- MySQL требует памяти и описателей для табличного кэша. Структуры
обработчиков для всех используемых таблиц сохранены в табличном кэше и
управляются как First In, First Out (FIFO).
Начальный табличный размер кэша определен
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
с несколькими датчиками утечки памяти, таким образом не должно быть
никаких утечек памяти.
Контроль использования памяти MySQL
Следующий пример демонстрирует, как использовать
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.
9.12.3.2.
Включение поддержки большой страницы
Немногие архитектуры аппаратных средств/операционной системы поддерживают
страницы памяти, больше чем значение по умолчанию (обычно 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
9.12.4.
Оптимизация сетевого использования
9.12.4.1.
Как MySQL использует потоки для соединений клиента
Менеджер соединений распараллеливает запросы соединения клиента
на сетевых интерфейсах, которые сервер слушает. На всех платформах один поток
менеджера обрабатывает запросы соединения 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 .
9.12.4.2. Оптимизация поиска DNS
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.
- Если флаг проверки допустимости для входа IP клиента ложен, сервер делает
попытку разрешения IP к имени хоста. Если это успешно, имя хоста обновлено с
решенным именем хоста, и флаг проверки допустимости установлен в истину. Если
разрешение неудачно, предпринятые меры зависят от того, является ли ошибка
постоянной или переходной. Для постоянных отказов остается имя хоста
NULL , а флаг проверки допустимости установлен в истину. Для
переходных отказов имя хоста и флаг проверки допустимости остаются
неизменными. Другая попытка разрешения DNS происходит в следующий раз, когда
клиент соединяется от этого IP.
- Если ошибка происходит, обрабатывая поступающее соединение клиента от
данного IP-адреса, сервер обновляет соответствующие счетчики во входе для
этого IP. Для описания зарегистрированных ошибок см.
раздел 23.9.16.1.
Сервер выполняет разрешение имени хоста, используя безопасные для
потока вызовы gethostbyaddr_r() и
gethostbyname_r() , если операционная система поддерживает их.
Иначе поток, выполняющий поиск, блокирует mutex и вызывает
gethostbyaddr() и gethostbyname() .
В этом случае никакой другой поток не может решить имена хоста, которые не
находятся в кэше узла, пока поток, держащий блокировку mutex,
не выпускает ее.
Сервер использует кэш узла в нескольких целях:
Чтобы открыть заблокированные узлы, сбросьте кэш узла командой
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_xxx (см.
раздел 6.1.7).
9.13.
Сопоставительный анализ (Benchmarking)
Чтобы определить эксплуатационные качества, рассмотрите следующие факторы:
Измеряете ли Вы скорость единственной работы на тихой системе
или как ряд операций в течение времени. С простыми тестами Вы обычно
проверяете, как изменение одного аспекта (установка конфигурации, набор
индексов на таблице, пункты SQL в запросе) влияет на работу. Точки отсчета
это типично продолжительные и тщательно продуманные тесты производительности,
где результаты могли продиктовать высокоуровневый выбор, такой как аппаратные
средства и конфигурация хранения, или как скоро обновиться до
новой версии MySQL.
- Для того, чтобы определить эффективность, иногда Вы должны моделировать
тяжелую рабочую нагрузку базы данных, чтобы получить точную картину.
- Работа может измениться в зависимости от очень многих различных факторов,
так что различие в несколько процентов не может быть решающей победой.
Результаты могли бы сместиться на противоположный путь, когда Вы проверяете в
различной окружающей среде.
- Особенности MySQL помогают или не помогают работе в зависимости от
рабочей нагрузки. Для законченности всегда нужно проведение испытаний с теми
или иными особенностями, включенными и выключенными. Двумя самыми важными
особенностями, чтобы попробовать при каждой рабочей нагрузке, является
кэш запроса MySQL и
адаптивный хеш-индекс
для таблиц
InnoDB .
Этот раздел прогрессирует от методов простого и прямого измерения,
которые единственный разработчик может сделать, к более сложным, которые
требуют дополнительной экспертизы и интерпретации результатов.
9.13.1.
Измерение скорости выражений и функций
Чтобы измерить скорость определенного выражения MySQL или функции,
вызовите BENCHMARK() ,
используя mysql
. Ее синтаксис:
BENCHMARK(loop_count ,expression )
. Возвращаемое значение всегда ноль, но
mysql
печатает строку, выводящую на экран приблизительно, сколько времени
запрос взял. Например:
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()
превосходный инструмент для того, чтобы узнать, является ли некоторая функция
проблемой для Ваших запросов.
9.13.2.
Используя Ваши собственные точки отсчета
Определите эффективность своего приложения и базы данных, чтобы узнать,
где узкие места. После установки одного узкого места (или заменяя это
with a пустым модулем), Вы можете продолжить
идентифицировать следующее узкое место. Даже если эффективность работы для
Вашего приложения в настоящее время является приемлемой, Вы должны, по
крайней мере, сделать план относительно каждого узкого места и решить, как
решать это, если когда-нибудь Вы действительно будете
нуждаться в дополнительной производительности.
Свободный эталонный набор Open Source Database Benchmark,
можно скачать с
http://osdb.sourceforge.net/.
Это очень характерно для проблемы проявляться только,
когда система очень в большой степени загружена. У нас было много клиентов,
которые связываются с нами, когда они имеют (проверенную) систему в
производстве и столкнулись с проблемами загрузки. В большинстве случаев
проблемы, оказывается, происходят из-за проблем основного проектирования баз
данных (например, сканирование таблицы не хорошо при высокой загрузке) или
проблемы с операционной системой или библиотеками. Большую часть времени эти
проблемы было бы намного легче решить, если бы системы еще не работали.
Чтобы избежать проблем, определите эффективность своего целого приложения
при худшей загрузке:
Эти программы или пакеты могут положить систему, так что надо
убедиться, что использовали их только на Ваших системах развития.
9.13.3.
Определение эксплуатационных качеств с performance_schema
Вы можете запросить таблицы в performance_schema , чтобы
видеть информацию в реальном времени о технических характеристиках Вашего
сервера и приложений, см. главу 23.
9.14.
Информация о потоке исследования
Когда Вы пытаетесь установить то, что делает Ваш сервер MySQL,
может быть полезно исследовать список процессов, который является набором
потоков, в настоящее время выполняющихся в пределах сервера.
Информация о списке процесса доступна из этих источников:
Доступ к 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 по категориям. Значение для
некоторых из этих значений самоочевидно. Для других
обеспечено дополнительное описание.
9.14.1.
Значения команды потока
У потока может быть любое из следующих значений 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
Не использован.
9.14.2.
Общие состояния потока
Следующий список описывает значения 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 :
Эти состояния указывают на ожидание блокировки метаданных:
Для информации о табличных индикаторах блокировки см.
раздел 9.11.1.
Для информации о блокировке метаданных см.
раздел 9.11.4.
Чтобы видеть, какие блокировки блокируют запросы блокировки, используйте
таблицы блокировки Performance Schema, описанные в
разделе 23.9.12.
Waiting on cond
Состояние, в котором поток ждет условия. Никакая определенная
информация не доступна.
Writing to net
Сервер пишет пакет в сеть.
9.14.3.
Состояния потока кэша запроса
Эти состояния потока связаны с кэшем запроса (см.
раздел 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
и т.д.
9.14.4.
Ведущие состояния потока репликации
Следующий список показывает наиболее распространенные состояния, которые
Вы можете видеть в столбце 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
Очень краткое состояние, которое происходит как поток, останавливается.
9.14.5.
Ведомые состояния потока ввода/вывода
Следующий список показывает наиболее распространенные состояния, которые
Вы видите в столбце 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) прежде, чем попытаться повторно соединиться.
9.14.6.
Ведомое устройство ответа состояния потока SQL
Следующий список показывает наиболее распространенные состояния, которые
Вы можете видеть в столбце 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 может также показать текст
запроса. Это указывает, что поток считал событие
из журнала реле, извлек запрос из этого и может его выполнять.
9.14.7.
Ведомые состояния соединения потока
Эти состояния потока происходят на ведомом устройстве, но связаны с
потоками соединения, а не с потоками 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
.
9.14.8.
Состояния потока планировщика событий
Эти состояния происходят для потока Event Scheduler, потоков, которые
создаются, чтобы запустить намеченные события, или потоков,
которые заканчивают планировщик.
Clearing
Поток планировщика или поток, который запускал событие, заканчиваются
или собираются закончиться.
Initialized
Поток планировщика или поток, который запустит
событие, были инициализированы.
Waiting for next activation
У планировщика есть непустая очередь событий, но следующая
активация находится в будущем.
Waiting for scheduler to stop
Поток запустил SET GLOBAL event_scheduler=OFF
и ждет завершения планировщика.
Waiting on empty queue
Очередь планировщика событий пуста, и он спит.
|
|