RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Глава 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 для таблицы и пропускать строки как можно скорее.
  • Все постоянные таблицы считаны сначала перед любыми другими таблицами в запросе. Постоянная таблица это любое из следующего:

    • Пустая таблица или таблица с одной строкой.

    • Таблица, которая используется с WHERE в PRIMARY KEY или индексе UNIQUE, где все индексные части сравниваются с постоянными выражениями и определены как NOT NULL.

    Все следующие таблицы используются в качестве постоянных таблиц:

    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.

Постоянная величина в предыдущих описаниях означает одно из следующего:

  • Константа от строки запроса.

  • Столбец таблицы const или system из того же самого соединения.
  • Результат некоррелированого подзапроса.
  • Любое выражение, составленное полностью из подвыражений предыдущих типов.

Вот некоторые примеры запросов с условиями диапазона в 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:

  1. Начнем с оригинального WHERE:

    (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
    (key1 < 'bar' AND nonkey = 4) OR
    (key1 < 'uux' AND key1 > 'z')
    
  2. Удалим 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')
    
  3. Уберем условия, которые всегда являются истиной или ложью:

    • (key1 LIKE 'abcde%' OR TRUE) всегда true.

    • (key1 < 'uux' AND key1 > 'z') всегда false.

    Заменяя эти условия константами, получим:

    (key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)
    

    Удалим ненужные константы TRUE и FALSE:

    (key1 < 'abc') OR (key1 < 'bar')
    
  4. Комбинируя накладывающиеся интервалы получим заключительное условие, которое будет использоваться для просмотра диапазона:

    (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, оценка строки для каждого диапазона 1, потому что самое большее у одной строки может быть данное значение.

  • Иначе любой индекс на col_name является групповым, и оптимизатор может оценить счет строки для каждого диапазона, используя погружения в индексирование или индексную статистику.

Оптимизатор делает погружение в каждом конце диапазона и использует число строк в диапазоне как оценка. Например, выражение col_name IN (10, 20, 30) имеет три диапазона равенства, и оптимизатор делает два погружения на диапазон, чтобы произвести оценку строки. Каждая пара погружений приводит к оценке числа строк, у которых есть данное значение.

Погружения обеспечивают точные оценки строки, но поскольку число сравнительных значений в выражении увеличивается, оптимизатор тратит больше времени, чтобы произвести оценку строки. Использование индексной статистики менее точно, чем индексные погружения, но разрешает более быструю оценку строки для больших списков значений.

eq_range_index_dive_limit позволяет Вам сконфигурировать число значений, при которых оптимизатор переключается от одной стратегии оценки строки на другую. Чтобы разрешить использование погружения для сравнений до N диапазонов равенства, установите eq_range_index_dive_limit в N + 1. Чтобы отключить использование статистики и всегда использовать погружения независимо от N, задайте eq_range_index_dive_limit = 0.

Чтобы обновить таблицу индексную статистику для наилучших оценок используют ANALYZE TABLE.

9.2.1.3.4. Ограничение использования памяти для оптимизации диапазона

Чтобы управлять памятью, доступной оптимизатору, используйте range_optimizer_max_mem_size:

  • 0 значит отсутствие ограничений.

  • Со значением больше 0 оптимизатор отслеживает память, потребляемую, рассматривая метод доступа диапазона. Если указанный предел превышен, метод доступа диапазона оставлен, и другие методы, включая полное сканирование таблицы, рассматриваются вместо этого. Это могло быть менее оптимальным. Если это происходит, следующее предупреждение происходит (где N текущее значение range_optimizer_max_mem_size):
    Warning 3170 Memory capacity of N bytes for
            'range_optimizer_max_mem_size' exceeded. Range
            optimization was not done for this query.
    

Для отдельных запросов, которые превышают доступную память оптимизации диапазона и для которого оптимизатор отступает к менее оптимальным планам, увеличение range_optimizer_max_mem_size может улучшить работу.

Чтобы оценить объем памяти, надо использовать эти направляющие линии:

  • Для простого запроса, где есть один возможный ключ для для метода доступа диапазона, каждый предикат, объединенный с OR, использует приблизительно 230 байтов:

    SELECT COUNT(*) FROM t
           WHERE a=1 OR a=2 OR a=3 OR .. . a=N;
    
  • Так же для запроса каждый предикат, объединенный с AND использует приблизительно 125 байтов:

    SELECT COUNT(*) FROM t WHERE a=1 AND b=1 AND c=1 ... N;
    
  • Для запроса с IN() предикаты:

    SELECT COUNT(*) FROM t
           WHERE a IN (1,2, ..., M) AND b
           IN (1,2, ..., N);
    

    Каждое буквальное значение в списке IN() считается как предикат, объединенный с OR. Если есть два списка IN(), число предикатов, объединных с OR , это произведение числа буквальных значений в каждом списке. Таким образом, число предикатов с OR в предыдущем случае M*N.

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');

Для оптимизатора, чтобы использовать просмотр диапазона, запросы должны удовлетворить этим условиям:

  • Используются только предикаты IN(), не NOT IN().

  • На левой стороне IN() конструктор строки содержит только ссылки столбца.
  • На правой стороне IN() конструкторы строки содержат только константы во время выполнения, которые являются литералами или местными ссылками столбца, которые связаны с константами во время выполнения.
  • На правой стороне IN() есть больше, чем один конструктор строки.

См. раздел 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 выключена:

  1. Получите следующую строку, читая индексный кортеж, а затем при использовании индексного кортежа определите местонахождение и считайте полную строку таблицы.

  2. Проверьте часть WHERE, которая относится к этой таблице. Примите или отклоните строку.

С Index Condition Pushdown просмотра идет так:

  1. Получите следующую строку индексного кортежа (но не полную строку таблицы).

  2. Проверьте часть WHERE, которая относится к этой таблице и может быть проверена, используя только индексированные столбцы. Если условие не удовлетворено, перейдите к индексному кортежу для следующей строки.
  3. Если условие удовлетворено, используйте индексный кортеж, чтобы определить местонахождение и считать полную строку таблицы.
  4. Проверьте остающуюся часть 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 следующим образом:

  • Таблица B установлена в зависимости от таблицы A и всех таблиц, от которых зависит A.

  • Таблица A установлена в зависимости от всех таблиц (кроме B), которые используются в выражении LEFT JOIN.
  • LEFT JOIN используется, чтобы решить, как получить строки от таблицы B. Другими словами, любое условие в WHERE не используется.
  • Вся стандартная оптимизация соединения выполнена за исключением того, что таблица всегда читается после всех таблиц, от которых она зависит. Если есть круговая зависимость, MySQL выпускает ошибку.
  • Вся стандартная оптимизация WHERE выполнена.
  • Если есть строка в A, которая соответствует WHERE, но нет никакой строки в B, которая соответствует выражению ON, дополнительная строка в B произведена со всем набором столбцов в NULL.
  • Если Вы используете LEFT JOIN, чтобы найти строки, которые не существуют в некоторой таблице, и у Вас есть следующий тест: col_name IS NULL в WHERE, где col_name столбец, который объявлен как NOT NULL, MySQL прекращает искать больше строк (для особого сочетания ключей) после того, как он нашел одну строку, которая соответствует LEFT JOIN.

Выполнение RIGHT JOIN походит на LEFT JOIN с тем, что роли таблиц поменялись местами.

Оптимизатор соединения вычисляет порядок, в котором нужно присоединить таблицы. Табличный порядок чтения, вызванный LEFT JOIN или STRAIGHT_JOIN помогает оптимизатору, делая его работу намного более быстрой, потому что есть меньше табличных перестановок, чтобы проверить. Отметьте, что это означает, что, если Вы делаете запрос следующего типа, MySQL делает полный просмотр на b так как LEFT JOIN предписывает, чтобы это было считано прежде d:

SELECT * FROM a JOIN b LEFT JOIN c ON (c.key=a.key)
         LEFT JOIN d ON (d.key=a.key)
         WHERE b.key=d.key;

Затруднительное положение в этом случае является обратным порядком, в котором a и b перечислены в FROM:

SELECT * FROM b JOIN a LEFT JOIN c ON (c.key=a.key)
         LEFT JOIN d ON (d.key=a.key)
         WHERE b.key=d.key;

Для LEFT JOIN, если WHERE всегда ложно для произведенной строки NULL, LEFT JOIN изменен на нормальное соединение. Например, WHERE был бы ложен в следующем запросе, если t2.column1 NULL:

SELECT * FROM t1 LEFT JOIN t2 ON (column1) WHERE t2.column2=5;

Поэтому безопасно преобразовать запрос в нормальное соединение:

SELECT * FROM t1, t2 WHERE t2.column2=5 AND t1.column1=t2.column1;

Это может быть сделано быстрее, потому что MySQL может использовать таблицу t2 до t1, если выполнение привело бы к лучшему плану запроса. Чтобы обеспечить подсказку о табличном порядке соединения, надо использовать STRAIGHT_JOIN, см. раздел 14.2.9. Но STRAIGHT_JOIN может не дать использовать индекс, потому что это отключает преобразования полусоединения. См. раздел 9.2.1.18.1.

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 использует буферизацию соединения при этих условиях:

  • join_buffer_size определяет размер каждого буфера соединения.

  • Буферизация соединения может использоваться, когда соединение имеет тип ALL или index (другими словами, когда никакие возможные ключи не могут использоваться, и полный просмотр сделан, данных или индексных строки, соответственно), или range. Использование буферизации также применимо к внешним соединениям, как описано в разделе 9.2.1.14.
  • Один буфер выделен для каждого соединения, которое может быть буферизовано, таким образом, данный запрос мог бы быть обработан, используя многократные буферы соединения.
  • Буфер соединения никогда не выделяется для первой непостоянной таблицы, даже если это имело бы тип ALL или index.
  • Буфер соединения выделен до выполнения соединения и освобожден после того, как запрос сделан.
  • Только столбцы для соединения сохранены в буфере соединения, не целые строки.

Для соединения в качестве примера, описанного ранее для алгоритма NLJ (не буферизуя), сделано соединение с использованием буферизации соединения:

for each row in t1 matching range {
  for each row in t2 matching reference key {
    store used columns from t1, t2 in join buffer
    if buffer is full {
       for each row in t3 {
         for each t1, t2 combination in join buffer {
           if row satisfies join conditions,
           send to client
         }
       }
       empty buffer
    }
  }
}
if buffer is not empty {
   for each row in t3 {
     for each t1, t2 combination in join buffer {
       if row satisfies join conditions,
       send to client
     }
   }
}

Если S размер каждого сохраненного t1, t2 комбинация буфера соединения и C, числа комбинаций в буфере, сколько раз таблица t3 просмотрена:

(S * C)/join_buffer_size + 1

Число t3 уменьшается по мере того, как значение join_buffer_size растет, пока join_buffer_size является достаточно большим, чтобы содержать все предыдущие комбинации строки. В этом пункте нет никакой скорости, которая будет получена, делая это больше.

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 имеют следующее состояние:

  • Таблица t1 содержит строки (1), (2)

  • Таблица t2 содержит строки (1,101).
  • Таблица t3 содержит строки (101).

В этом случае первое выражение возвращает набор результатов, включая строки (1,1,101,101), (2,NULL,NULL,NULL), тогда как второе выражение возвращает строки (1,1,101,101), (2,NULL,NULL,101):

mysql> SELECT * FROM t1 LEFT JOIN
    ->          (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
    ->          ON t1.a=t2.a;
+---+------+------+------+
| a | a    | b    | b    |
+---+------+------+------+
| 1 | 1    |  101 |  101 |
| 2 | NULL | NULL | NULL |
+---+------+------+------+

mysql> SELECT * FROM (t1 LEFT JOIN t2 ON t1.a=t2.a)
    ->          LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL;
+---+------+------+-----+
| a | a    | b    | b   |
+---+------+------+-----+
| 1 | 1    |  101 | 101 |
| 2 | NULL | NULL | 101 |
+---+------+------+-----+

В следующем примере внешняя работа соединения используется вместе с внутренней работой соединения:

t1 LEFT JOIN (t2, t3) ON t1.a=t2.a

Это выражение не может быть преобразовано в следующее выражение:

t1 LEFT JOIN t2 ON t1.a=t2.a, t3.

Для данных табличных состояний эти два выражения возвращают различные наборы строк:

mysql> SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;
+---+------+------+------+
| a | a    | b    | b    |
+---+------+------+------+
| 1 | 1    |  101 |  101 |
| 2 | NULL | NULL | NULL |
+---+------+------+------+

mysql> SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;
+---+------+------+-----+
| a | a    | b    | b   |
+---+------+------+-----+
| 1 | 1    |  101 | 101 |
| 2 | NULL | NULL | 101 |
+---+------+------+-----+

Поэтому, если мы опускаем круглые скобки в выражении соединения с внешними операторами соединения, мы могли бы изменить набор результатов для оригинального выражения.

Более точно, мы не можем проигнорировать круглые скобки в правом операнде соединения left outer и в левом операнде соединения right join. Другими словами, мы не можем проигнорировать круглые скобки для внутренних табличных выражений внешних операций соединения. Круглые скобки для другого операнда (операнд для внешней таблицы) могут быть проигнорированы.

Следующее выражение:

(t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b)

эквивалентно этому выражению:

t1, t2 LEFT JOIN t3 ON P(t2.b,t3.b)

для любых таблиц t1,t2,t3 и любого условия P по признакам t2.b и t3.b.

Всякий раз, когда порядок выполнения операций соединения в выражении (join_table) не слева направо, мы говорим о вложенных соединениях. Рассмотрите следующие запросы:

SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a
         WHERE t1.a > 1
SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
         WHERE (t2.b=t3.b OR t2.b IS NULL) AND t1.a > 1

Те запросы, как полагают, содержат эти вложенные соединения:

t2 LEFT JOIN t3 ON t2.b=t3.b
t2, t3

Вложенное соединение сформировано в первом запросе с left join, тогда как во втором запросе это сформировано с inner join.

В первом запросе могут быть опущены круглые скобки: грамматическая структура выражения соединения продиктует тот же самый порядок выполнения для операций соединения. Для второго запроса не могут быть опущены круглые скобки, хотя выражение соединения здесь может интерпретироваться однозначно без них. В нашем расширенном синтаксисе круглые скобки в (t2, t3) из второго запроса требуются, хотя теоретически запрос мог быть разобран без них: у нас все еще была бы однозначная синтаксическая структура для запроса, потому что LEFT JOIN и ON играл бы роль левых и правых разделителей для выражения (t2,t3).

Предыдущие примеры демонстрируют эти пункты:

  • Для выражений соединения, вовлекающих только внутренние соединения (но не outer join), могут быть удалены круглые скобки. Вы можете удалить круглые скобки и оценить слева направо (или, фактически, Вы можете оценить таблицы в любом порядке).

  • То же самое не истина для внешних соединений или для внешних соединений, смешанных с внутренними соединениями. Удаление круглых скобок может изменить результат.

Запросы с вложенными внешними соединениями выполнены в той же самой манере трубопровода как запросы с внутренними соединениями. Более точно, эксплуатируется изменение алгоритма соединения вложенной петли. Предположите, что у нас есть запрос соединения более, чем 3 таблиц T1,T2,T3:

SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2) INNER JOIN T3 ON P2(T2,T3)
         WHERE P(T1,T2,T3).

Здесь P1(T1,T2) и P2(T3,T3) некоторые условия соединения (по выражениям), тогда как P(T1,T2,T3) условие по столбцам таблиц T1,T2,T3.

Алгоритм соединения вложенной петли выполнил бы этот запрос в следующей манере:

FOR each row t1 in T1 {
  FOR each row t2 in T2 such that P1(t1,t2) {
    FOR each row t3 in T3 such that P2(t2,t3) {
      IF P(t1,t2,t3)
      {
         t:=t1||t2||t3;
         OUTPUT t;
      }
    }
  }
}

Запись t1||t2||t3 значит строка, созданная, связывая столбцы строк t1, t2 и t3. В некоторых из следующих примеров NULL где имя строки означает, что NULL используется для каждого столбца той строки. Например, t1||t2||NULL значит строка, созданная, связывая столбцы строк t1, t2 и NULL для каждого столбца t3.

Теперь давайте рассматривать запрос с вложенными внешними соединениями:

SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON P2(T2,T3)) ON P1(T1,T2)
         WHERE P(T1,T2,T3).

Для этого запроса мы изменяем образец вложенной петли:

FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t2 in T2 such that P1(t1,t2) {
    BOOL f2:=FALSE;
    FOR each row t3 in T3 such that P2(t2,t3) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3;
         OUTPUT t;
      }
      f2=TRUE;
      f1=TRUE;
    }
    IF (!f2) {
       IF P(t1,t2,NULL) {
          t:=t1||t2||NULL;
          OUTPUT t;
       }
       f1=TRUE;
    }
  }
  IF (!f1) {
     IF P(t1,NULL,NULL) {
        t:=t1||NULL||NULL;
        OUTPUT t;
     }
  }
}

Вообще для любой вложенной петли для первой внутренней таблицы в outer join флаг введен, который выключен перед петлей и проверен после петли. Флаг включен, когда для текущей строки из внешней таблицы, найдено соответствие из таблицы, представляющей внутренний операнд. Если в конце петли циклически повторяются, флаг все еще выключен, никакое соответствие не было найдено для текущей строки внешней таблицы. В этом случае строка дополнена значениями NULL для столбцов внутренних таблиц. Строку результата передают к последней проверке для вывода или в следующую вложенную петлю, но только если строка удовлетворяет условие соединения всех встроенных внешних соединений.

В нашем примере встроена внешняя таблица соединения, выраженная следующим выражением:

(T2 LEFT JOIN T3 ON P2(T2,T3))

Для запроса с внутренними соединениями оптимизатор мог выбрать различный порядок вложенных петель, такой как этот:

FOR each row t3 in T3 {
  FOR each row t2 in T2 such that P2(t2,t3) {
    FOR each row t1 in T1 such that P1(t1,t2) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3;
         OUTPUT t;
      }
    }
  }
}

Для запросов с внешними соединениями оптимизатор может выбрать только такой порядок, где петли для внешних таблиц предшествуют петлям для внутренних таблиц. Таким образом, для нашего запроса с внешними соединениями, только один порядок вложения возможен. Для следующего запроса оптимизатор оценит два различных вложения:

SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3)
         WHERE P(T1,T2,T3)

Вложения будут такие:

FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t2 in T2 such that P1(t1,t2) {
    FOR each row t3 in T3 such that P2(t1,t3) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3;
         OUTPUT t;
      }
      f1:=TRUE
    }
  }
  IF (!f1) {
     IF P(t1,NULL,NULL) {
        t:=t1||NULL||NULL;
        OUTPUT t;
     }
  }
}

и:

FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t3 in T3 such that P2(t1,t3) {
    FOR each row t2 in T2 such that P1(t1,t2) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3;
         OUTPUT t;
      }
      f1:=TRUE
    }
  }
  IF (!f1) {
     IF P(t1,NULL,NULL) {
        t:=t1||NULL||NULL;
        OUTPUT t;
     }
  }
}

В обоих вложениях T1 должен быть обработан во внешней петле, потому что она используется во внешнем соединении. T2 и T3 используются во внутреннем соединении так, чтобы соединение было обработано во внутренней петле. Однако, потому что соединение внутреннее, T2 и T3 могут быть обработаны в любом порядке.

Обсуждая алгоритм вложенной петли для внутренних соединений, мы опустили некоторые детали, воздействие которых на исполнение запроса может быть огромным. Мы не упоминали так называемый pushed-down. Предположите, что в нашем WHERE условие P(T1,T2,T3) может быть представлено соединительной формулой:

P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3).

В этом случае MySQL фактически использует следующую схему вложенной петли для выполнения запроса с внутренними соединениями:

FOR each row t1 in T1 such that C1(t1) {
  FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2)  {
    FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3;
         OUTPUT t;
      }
    }
  }
}

Вы видите, что каждый из C1(T1), C2(T2), C3(T3) продвинуты из самой внутренней петли к самой внешней петле, где это может быть оценено. Если C1(T1) очень строгое условие, это условие может очень сократить количество строк от таблицы T1 к внутренним петлям. В результате время выполнения для запроса может улучшиться.

Для запроса с внешними соединениями WHERE состоит в том, чтобы быть проверенным только после того, как было найдено, что у текущей строки от внешней таблицы есть соответствие во внутренних таблицах. Таким образом, оптимизация продвижения условий из внутренних вложенных петель не может быть применена непосредственно к запросам с внешними соединениями. Здесь мы должны ввести условные вниз продвинутые предикаты, которые охраняют флаги, которые включены, когда столкнулись с соответствием.

Для нашего примера с внешними соединениями:

P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)

схема вложенной петли, используя продвинутые вниз условия, похожа на это:

FOR each row t1 in T1 such that C1(t1) {
  BOOL f1:=FALSE;
  FOR each row t2 in T2 such that P1(t1,t2) AND (f1?C2(t2):TRUE) {
    BOOL f2:=FALSE;
    FOR each row t3 in T3 such that P2(t2,t3) AND
        (f1&&f2?C3(t3):TRUE) {
      IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) {
         t:=t1||t2||t3;
         OUTPUT t;
      }
      f2=TRUE;
      f1=TRUE;
    }
    IF (!f2) {
       IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) {
          t:=t1||t2||NULL;
          OUTPUT t;
       }
       f1=TRUE;
    }
  }
  IF (!f1 && P(t1,NULL,NULL)) {
     t:=t1||NULL||NULL;
     OUTPUT t;
  }
}

Вообще, вниз продвинутые предикаты могут быть извлечены из условий соединения P1(T1,T2) и P(T2,T3). В этом случае вниз продвинутый предикат охраняет также флаг, который предотвращает проверку предиката для NULL-дополненной строки, произведенной соответствующей внешней операцией outer join.

Доступ ключом от одной внутренней таблицы к другой в том же самом вложенном соединении запрещен, если это вызвано предикатом от WHERE. Мы могли использовать условный ключевой доступ в этом случае, но этот метод еще не используется в MySQL.

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 просты. Условие отклонено нулем в следующих случаях:

  • Если это имеет форму A IS NOT NULL, где A признак любой из внутренних таблиц.

  • Если это предикат, содержащий ссылку на внутреннюю таблицу, которая оценивается к UNKNOWN, когда один из параметров NULL.
  • Если это соединение, содержащее отклоненное нулем условие как соединенное.
  • Если это дизъюнкция отклоненных нулем условий.

Условие может быть отклонено нулем для одной внешней работы соединения в запросе и не отклонено нулем для другой. В запросе:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A LEFT JOIN T3 ON T3.B=T1.B
         WHERE T3.C > 0

WHERE отклонено нулем для второго outer join, но не отклонено нулем для первого.

Если WHERE отклонено нулем для outer join в запросе, outer join соединения заменен inner join.

Например, предыдущий запрос заменен запросом:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A INNER JOIN T3 ON T3.B=T1.B
         WHERE T3.C > 0

Для оригинального запроса оптимизатор оценил бы планы, совместимые только с одним порядком доступа T1,T2,T3. Для запроса замены это дополнительно рассматривает последовательность доступа T3,T1,T2.

Преобразование одного outer join может вызвать преобразование другого. Таким образом, запрос:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A LEFT JOIN T3 ON T3.B=T2.B
         WHERE T3.C > 0

будет сначала преобразован в запрос:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A INNER JOIN T3 ON T3.B=T2.B
         WHERE T3.C > 0

который эквивалентен запросу:

SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3
         WHERE T3.C > 0 AND T3.B=T2.B

Теперь остающийся outer join может быть заменен inner join, потому что условие T3.B=T2.B отклонено нулем и мы получаем запрос без внешних соединений вообще:

SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3
         WHERE T3.C > 0 AND T3.B=T2.B

Иногда мы преуспеваем в том, чтобы заменить встроенный outer join, но не можем преобразовать встраивающий outer join. Следующий запрос:

SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A
         WHERE T3.C > 0

преобразован в:

SELECT * FROM T1 LEFT JOIN (T2 INNER JOIN T3 ON T3.B=T2.B)
         ON T2.A=T1.A WHERE T3.C > 0,

Это может быть переписано только к форме, все еще содержащей встроенный outer join:

SELECT * FROM T1 LEFT JOIN (T2,T3) ON (T2.A=T1.A AND T3.B=T2.B)
         WHERE T3.C > 0.

Пытаясь преобразовать встроенный outer join в запросе, мы должны принять во внимание, что условие соединения для внешнего встраивания объединяется с WHERE. В запросе:

SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B=T2.B) ON T2.A=T1.A AND
         T3.C=T1.C WHERE T3.D > 0 OR T1.D > 0

WHERE не отклонено нулем для встроенного outer join, но но условия соединения outer join T2.A=T1.A AND T3.C=T1.C отклонено нулем. Таким образом, запрос может быть преобразован в:

SELECT * FROM T1 LEFT JOIN (T2, T3) ON T2.A=T1.A AND T3.C=T1.C AND
         T3.B=T2.B WHERE T3.D > 0 OR T1.D > 0

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-соединения.

  1. Часть индексных кортежей накоплена в буфере.

  2. Кортежи в буфере сортированы по их ID строки данных.
  3. К строкам данных получают доступ согласно сортированной последовательности индексных кортежей.

Сценарий B: MRR может использоваться для NDB для мультидиапазонного индексного просмотра или выполняя equi-соединение.

  1. Часть диапазонов, возможно, одноключевые диапазоны, накоплена в буфере на центральном узле, где запрос представлен.

  2. Диапазоны посылают в узлы выполнения, которые обращаются к строке.
  3. Строки, к которым получают доступ, упакованы в пакеты и отосланы назад к центральному узлу.
  4. Полученные пакеты со строками данных помещены в буфер.
  5. Строки данных считаны из буфера.

Когда 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 .

Оригинальный алгоритм работает следующим образом:

  1. Считать все строки согласно ключу или табличному просмотру. Пропустить строки, которые не соответствуют WHERE.

  2. Для каждой строки в буфере сортировки хранится кортеж, состоящий из пары значений (значение ключа сортировки и ID строки).
  3. Если все пары вписываются в буфер, никакой временный файл не создается. Иначе, когда буфер становится полным, выполняется qsort (quicksort) в памяти и пишется результат во временный файл. Сохраняется указатель на отсортированный блок.
  4. Повторите предыдущие шаги, пока все строки не считаны.
  5. Сделайте мультислияние до MERGEBUFF (7) к одному блоку в другом временном файле. Повторите, пока все блоки от первого файла не будут во втором файле.
  6. Повторите пока нет меньше, чем MERGEBUFF2 (15) блоков.
  7. На последнем мультислиянии только ID строки (последняя часть пары значения) написано в файл результата.
  8. Считайте строки в сортированном порядке, используя ID строки в файле результата. Чтобы оптимизировать это, читайте в большом блоке ID строки, сортируйте их, и используйте их, чтобы считать строки в сортированном порядке в буфер строки. Размер буфера строки read_rnd_buffer_size . Код для этого шага находится в исходном файле sql/records.cc.

Одна проблема с этим подходом состоит в том, что он читает строки дважды: во время оценки WHERE и снова после сортировки пар значения. И даже если к строкам получили доступ последовательно в первый раз (например, если сканирование таблицы сделано), во второй раз к ним получают доступ беспорядочно. Ключи сортировки упорядочены, но позиции строк нет.

Измененный алгоритм filesort включает оптимизацию, чтобы избежать читать строки дважды: это делает запись значения ключа сортировки, но вместо ID строки, это делает запись столбцов, на которые ссылается запрос. Измененный filesort работает так:

  1. Считайте строки, которые соответствуют WHERE .

  2. Для каждой строки в буфере сортировки хранится кортеж, состоящий из значения ключа сортировки и столбцов, на которые ссылается запрос.
  3. Когда буфер становится полным, кортежи сортируются значением ключа в памяти, и это пишется во временный файл.
  4. После сортировки слияния временного файла получите строки в сортированном порядке, но считайте столбцы, требуемые запросом непосредственно от сортированных кортежей, а не получая доступ к таблице во второй раз.

Кортежи, используемые измененным 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), свободный просмотр ищет первый ключ каждой группы, которая удовлетворяет условиям диапазона, и снова читает наименее возможное число ключей. Это возможно при следующих условиях:

  • Запрос по единственной таблице.

  • GROUP BY называет только столбцы, которые формируют крайний левый префикс индекса и никаких других столбцов. Если вместо GROUP BY у запроса есть DISTINCT, все отличные признаки обращаются к столбцам, которые формируют крайний левый префикс индекса. Например, если таблица t1 имеет индексирование на (c1,c2,c3), свободный просмотр применим, если запрос имеет GROUP BY c1, c2,. Это не применимо, если запрос имеет GROUP BY c2, c3 (столбцы не крайний левый префикс) или GROUP BY c1, c2, c4 (c4 не находится в индексировании).
  • Единственные совокупные функции в избранном списке (если есть) это MIN() и MAX(), все они обращаются к тому же самому столбцу. Столбец должен быть в индексе и должен немедленно следовать за столбцами в GROUP BY.
  • Любые другие части индексирования чем от GROUP BY должны быть константами (то есть, на них нужно сослаться в равенствах с константами), за исключением параметра функций MIN() или MAX().
  • Для столбцов в индексировании полные значения столбцов должны быть индексированы, не только префикс. Например, с c1 VARCHAR(20), INDEX (c1(10)) индекс не может использоваться для свободного просмотра.

Если свободный просмотр, применим к запросу, EXPLAIN показывает Using index for group-by в столбце Extra.

Предположите, что есть индекс idx(c1,c2,c3) на таблице t1(c1,c2,c3,c4). Свободный просмотр может использоваться для следующих запросов:

SELECT c1, c2 FROM t1 GROUP BY c1, c2;
SELECT DISTINCT c1, c2 FROM t1;
SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
SELECT c1, c2 FROM t1 WHERE c1 < const
       GROUP BY c1, c2;
SELECT MAX(c3), MIN(c3), c1, c2 FROM t1
       WHERE c2 > const GROUP BY c1, c2;
SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;

Следующие запросы не могут быть выполнены с этим методом по приведенным причинам:

  • Есть совокупные функции кроме MIN() или MAX():

    SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
    
  • Столбцы в GROUP BY не формируют крайний левый префикс из индекса:
    SELECT c1, c2 FROM t1 GROUP BY c2, c3;
    
  • Запрос обращается к части ключа, которая после GROUP BY и для которой нет никакого равенства с константой:

    SELECT c1, c3 FROM t1 GROUP BY c1, c2;
    

    Если был запрос, чтобы включать WHERE c3=const , свободный индексный просмотр мог использоваться.

Метод доступа может быть применен к другим формам совокупных функциональных ссылок в избранном списке, в дополнение к MIN() и MAX():

  • AVG(DISTINCT), SUM(DISTINCT) и COUNT(DISTINCT) поддерживаются. AVG(DISTINCT) и SUM(DISTINCT) берут единственный параметр. COUNT(DISTINCT) может иметь в параметре больше одного столбца.

  • Не должно быть GROUP BY или DISTINCT в запросе.
  • Ограничения просмотра, описанные ранее, все еще применяются.

Предположите, что есть индекс idx(c1,c2,c3) на таблице t1(c1,c2,c3,c4). Свободный просмотр может использоваться для следующих запросов:

SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;
SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;

Свободный просмотр не может использоваться для следующих запросов:

SELECT DISTINCT COUNT(DISTINCT c1) FROM t1;
SELECT COUNT(DISTINCT c1) FROM t1 GROUP BY c1;
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) у оптимизатора есть этот выбор:

  • Материализация

  • EXISTS

Для полученных таблиц (подзапросы в 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 преобразовывает его в полусоединение и делает выбор на основе издержек из этих стратегий:

  • Преобразуйте подзапрос в соединение или используйте табличное отступление и выполните запрос как внутреннее соединение между таблицами подзапроса и внешними таблицами. Табличное отступление вытягивает таблицу из подзапроса к внешнему запросу.

  • Выполните полусоединение, как будто это было соединение, и удалите дубликаты записей, используя временную таблицу.
  • FirstMatch: Когда внутренние таблицы просматриваются для комбинаций строки и там есть много случаев данной группы значения, выбирается одна вместо того, чтобы возвратить их все. Этот просмотр "ярлыков" и устраняет производство ненужных строк.
  • LooseScan: Просмотрите таблицу подзапроса, используя индексирование, что позволяет единственному значению быть выбранным из группы значений каждого подзапроса.
  • Осуществите подзапрос во временную таблицу с индексированием и используйте временную таблицу, чтобы выполнить соединение. Индексирование используется, чтобы удалить дубликаты. Индексирование могло бы также использоваться позже для поисков, присоединяясь к временной таблице с внешними таблицами, в противном случае таблица просмотрена.

Каждая из этих стратегий может быть включена с использованием переменной 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 ...) оценивается следующим образом:

  • NULL, если SELECT производит любую строку, где inner_expr = NULL.

  • FALSE, если SELECT производит значения только не NULL или не производит вовсе.

В этой ситуации поиск строк с 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 ...) должен оцениться как:

  • NULL, если SELECT производит любые строки.

  • FALSE, если SELECT строк не производит.

Для надлежащей оценки необходимо быть в состоянии проверить, произвел ли 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) это специальная функция, которая оценивается к следующим значениям:

  • X, когда связанное внешнее выражение oe_i не NULL.

  • TRUE, когда связанное внешнее выражение oe_i NULL.

Триггерные функции это не триггеры в смысле запроса CREATE TRIGGER.

Равенства, которые обернуты в trigcond(), это не предикаты первого класса для оптимизатора. Большинство оптимизации не может иметь дело с предикатами, которые могут быть включены во время выполнения запроса, таким образом, они принимают любую trigcond(X) как неизвестную функцию и проигнорируют это. В настоящее время вызванные равенства могут использоваться этой оптимизацией:

  • Ссылочная оптимизация: trigcond(X= Y [OR Y IS NULL]) может использоваться, чтобы создать табличные доступы ref, eq_ref или ref_or_null.

  • Основанные на поиске в индексе механизмы выполнения подзапроса: trigcond(X=Y) может использоваться, чтобы создать доступы unique_subquery или index_subquery .
  • Генератор табличного условия: если подзапрос будет соединением нескольких таблиц, то вызванное условие будет проверено как можно скорее.

Когда оптимизатор использует вызванное условие, чтобы создать некоторый основанный на индексном поиске доступ (что касается первых двух элементов предыдущего списка), у этого должна быть стратегия отступления для случая, когда условие выключено. Эта стратегия отступления всегда сделать полное сканирование таблицы. В выводе EXPLAIN это обнаруживается как Full scan on NULL key в столбце Extra:

mysql> EXPLAIN SELECT t1.col1, t1.col1 IN
    ->         (SELECT t2.key1 FROM t2 WHERE t2.col2=t1.col2) FROM t1\G
*************************** 1. row ***************************
 id: 1
  select_type: PRIMARY
table: t1
...
*************************** 2. row ***************************
 id: 2
  select_type: DEPENDENT SUBQUERY
table: t2
 type: index_subquery
possible_keys: key1
key: key1
key_len: 5
ref: func
 rows: 2
Extra: Using where; Full scan on NULL key

Если Вы выполняете EXPLAIN EXTENDED и SHOW WARNINGS , Вы можете видеть вызванное условие:

*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: select `test`.`t1`.`col1` AS `col1`,
 <in_optimizer>(`test`.`t1`.`col1`,
 <exists>(<index_lookup>(<cache>(`test`.`t1`.`col1`) in t2
 on key1 checking NULL
 where (`test`.`t2`.`col2` = `test`.`t1`.`col2`) having
 trigcond(<is_not_null_test>(`test`.`t2`.`key1`))))) AS
 `t1.col1 IN (select t2.key1 from t2 where t2.col2=t1.col2)` from `test`.`t1`

У использования вызванных условий есть некоторые исполнительные значения. Выражение NULL IN (SELECT ...) теперь может вызвать полное сканирование таблицы (которое является медленным), когда оно ранее не сделало. Это цена, заплаченная за правильные результаты (цель стратегии более аккуратного условия состояла в том, чтобы улучшить согласие, а не скорость).

Для многотабличных подзапросов выполнение NULL IN (SELECT ...) будет особенно медленным, потому что соединение оптимизатор не оптимизирует для случая, где внешнее выражение NULL. Это принимает, что такие подзапросы с NULL на левой стороне очень редки, даже если есть статистические данные, которые указывают иное. С другой стороны, если внешнее выражение могло бы быть NULL, но никогда фактически не будет, нет никакого исполнительного штрафа.

Чтобы помочь оптимизатору лучше выполнять Ваши запросы, используют эти подсказки:

  • Объявите столбец как NOT NULL, если это действительно так. Это также помогает другим аспектам оптимизатора, упрощая тестирование условия для столбца.

  • Если Вы не должны отличить NULL от FALSE, Вы можете легко избежать медленного пути выполнения. Замените сравнение, которое похоже на это:
    outer_expr IN (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 строк от очереди. Если задан M, пропустите первые M строк и возвращайте следующие N строк.

Ранее сервер выполнил эту работу при использовании файла слияния для сортировки:

  • Просмотрите таблицу, повторяя эти шаги до конца таблицы:

    • Выберите строки, пока буфер не заполнен.

    • Запишите первые N строк в буфере (M+N строк, если задан M) в файл слияния.

  • Сортируйте файл слияния и возвратите первые N строк. Если задан M, пропустите M строк и верните следующие N строк.

Стоимость сканирования таблицы та же самая для методов очереди и файла слияния, таким образом, оптимизатор выбирает между методами, основанными на других затратах:

  • Метод очереди вовлекает больше ресурсов центрального процессора для того, чтобы вставить строки в очередь в нужном порядке.

  • У метода файла слияния есть затраты ввода/вывода, чтобы написать и считать файл и ресурсы центрального процессора, чтобы сортировать это.

Оптимизатор рассматривает баланс между этими факторами для особых значений 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 использует полный просмотр таблицы, чтобы решить запрос. Это обычно происходит при следующих условиях:

  • Таблица является настолько маленькой, что быстрее выполнить сканирование таблицы чем обеспокоиться ключевым поиском. Это характерно для таблиц меньше чем с 10 строками и короткой длиной строки.

  • Нет никаких применимых ограничений в ON или WHERE для индексированных столбцов.
  • Вы сравниваете индексированные столбцы с постоянными величинами, и MySQL вычислил (основываясь на индексном дереве), что константы покрывают слишком большую часть таблицы и сканирование таблицы было бы быстрее. См. раздел 9.2.1.2.
  • Вы используете ключ с низким количеством элементов (много строк соответствуют значению ключа) через другой столбец. В этом случае 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 , которые являются представлениями о таблицах словаря данных.

  • Попытайтесь запросить только для статических метаданных. Выбор столбцов или использование условий извлечения для динамических метаданных наряду со статическими метаданными добавляют издержки, чтобы обработать динамические метаданные.

Поведение сравнения для имен базы данных и имен таблиц в 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 возвратила бы все строки в таблице, требуя, чтобы оптимизатор позже оценил условия для каждой строки, чтобы произвести окончательный результат.

Индексы Performance Schema предопределены и не могут быть удалены, добавлены или изменены.

Индексы Performance Schema подобны хеш-индексам, например:

  • Они используются только для сравнений равенства, которые используют операторы = или <=>.

  • Если у результата запроса должны быть определенные характеристики упорядочивания строки, включайте ORDER BY.

См. раздел 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. Запросы с этими характеристиками особенно эффективны:

  • Запросы FULLTEXT, которые возвращают только ID документа или ID документа и разряд поиска.

  • Запросы FULLTEXT, которые сортируют соответствующие строки в порядке убывания счета и применяют LIMIT, чтобы взять первые N соответствий строк. Для этой оптимизации не должно быть WHERE и только один ORDER BY в порядке убывания.
  • Запросы FULLTEXT, которые получают только значение COUNT(*) строк, соответствующих критерию поиска, без дополнительного WHERE. Кодируйте WHERE как WHERE MATCH(text) AGAINST ('other_text '), без любых операторов сравнения > 0.

Пространственный индекс

Вы можете создать индексы на пространственных типах данных. MyISAM и InnoDB понимают индексы R-tree на пространственных типах. Другие механизмы хранения используют B-деревья для того, чтобы индексировать пространственные типы (за исключением ARCHIVE, который не поддерживает пространственную индексацию).

Индексы в механизме хранения MEMORY

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        |
+------------+------------+

Невидимый индекс позволяют проверить эффект удаления индексирования на работе запроса, не производя разрушительное изменение. Передобавление индексирования может быть дорогим для большой таблицы, тогда как переключение невидимости индекса является быстрыми оперативными операциями.

Если невидимый индекс фактически необходим или используется оптимизатором, есть несколько способов заметить эффект его отсутствия на запросах для таблицы:

  • Ошибки происходят для запросов, которые включают индексные подсказки, которые обращаются к невидимому индексу.

  • Performance Schema показывает увеличение рабочей нагрузки для затронутых запросов.
  • Запросы имеют отличающийся план выполнения EXPLAIN.
  • Запросы появляются в медленном журнале запроса, хотя не появлялись там ранее.

Невидимость не затрагивает обслуживание. Например, индексирование продолжает обновляться при изменении строк таблицы, и уникальный индекс предотвращает вставку дубликатов в столбец, независимо от того, видимо ли индексирование или невидимо.

У таблицы без явного первичного ключа может все еще быть эффективный неявный первичный ключ, если у этого есть индекс UNIQUE на столбцах NOT NULL. В этом случае первый такой индекс имеет то же самое ограничение на строки таблицы как явный первичный ключ и индекс не может быть сделан невидимым. Рассмотрите следующее табличное определение:

CREATE TABLE t2 (i INT NOT NULL, j INT NOT NULL, UNIQUE j_idx (j))
       ENGINE = InnoDB;

Определение не включает явного первичного ключа, но индекс на столбце j NOT NULL помещает то же самое ограничение на строки, как первичный ключ и не может быть сделан невидимым:

mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE;
ERROR 3522 (HY000): A primary key index cannot be invisible.

Теперь предположите, что явный первичный ключ добавлен к таблице:

ALTER TABLE t2 ADD PRIMARY KEY (i);

Явный первичный ключ не может быть сделан невидимым. Кроме того, уникальные индексы на j действуют как неявный первичный ключ и в результате не могут быть сделаны невидимыми:

mysql> ALTER TABLE t2 ALTER INDEX j_idx INVISIBLE;
Query OK, 0 rows affected (0.03 sec)

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 закрывает неиспользованную таблицу и удаляет это из табличного кэша при следующих обстоятельствах:

  • Когда кэш полон, и поток пытается открыть таблицу, которая не находится в кэше.

  • Когда кэш содержит больше, чем table_open_cache записей, и таблица в кэше больше не используются никакими потоками.
  • Когда табличная работа сброса происходит. Это происходит, когда кто-то вызывает FLUSH TABLES или mysqladmin flush-tables (или mysqladmin refresh).

Когда табличный кэш заполняется, сервер использует следующую процедуру, чтобы определить местонахождение использумой записи кэша:

  • Таблицы, которые не используются в настоящее время, выпущены, начиная с последней использованной таблицы.

  • Если новая таблица должна быть открыта, но кэш полон, и никакие таблицы не могут быть выпущены, кэш временно расширен по мере необходимости. Когда кэш находится во временно расширенном статусе, и таблица переходит от используемого состояния в неиспользуемое, таблица закрыта и выпущена из кэша.

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

В некоторых случаях сервер составляет внутренние временные таблицы, обрабатывая запросы. У пользователей нет никакого прямого управления, когда это происходит.

Сервер составляет временные таблицы при таких условиях:

  • Оценка UNION с некоторыми исключениями, описанными позже.

  • Оценка некоторых представлений, как те, которые используют алгоритм TEMPTABLE, UNION или разную агрегацию.
  • Оценка полученных таблиц (подзапросы в FROM).
  • Таблицы, составленные для подзапроса или материализации полусоединения (см. раздел 9.2.1.18).
  • Оценка запросов, которые содержат ORDER BY и разные GROUP BY или для которых ORDER BY или GROUP BY содержит столбцы из таблиц, кроме первой таблицы в очереди соединения.
  • Оценка DISTINCT объединенный с ORDER BY может потребовать временной таблицы.
  • Для запросов, которые используют опцию SQL_SMALL_RESULT, MySQL использует временную таблицу в памяти, если запрос также не содержит элементов (описаны позже), которые требуют хранения на диске.
  • Оценка многотабличного UPDATE.
  • Оценка GROUP_CONCAT() или COUNT(DISTINCT) .

Чтобы определить, требует ли запрос временной таблицы, надо использовать EXPLAIN и проверьте столбец Extra, чтобы видеть, говорит ли это Using temporary (см. раздел 9.8.1). EXPLAIN не обязательно скажет Using temporary для полученных или осуществленных временных таблиц.

Когда сервер составляет внутреннюю временную таблицу (в памяти или на диске), это постепенно увеличивает переменную Created_tmp_tables . Если сервер составляет таблицу на диске (первоначально или преобразовывая таблицу в памяти), это постепенно увеличивает переменную Created_tmp_disk_tables.

Некоторые условия запроса предотвращают использование временной таблицы в памяти, когда сервер использует таблицу на диске вместо этого:

  • Присутствие столбца BLOB или TEXT в таблице.

  • Присутствие любого строкового столбца с максимальной длиной больше 512 (байт для двоичных строк, символов для недвоичных строк) в списке SELECT, если применено UNION или UNION ALL.
  • SHOW COLUMNS и DESCRIBE используют BLOB как тип столбца, таким образом временная таблица, используемая для результатов, является таблицей на диске.

Сервер не использует временную таблицу для UNION, которые встречают определенные квалификации. Вместо этого это сохраняет из временной таблицы только структуры данных, необходимые, чтобы выполнить подбор типов. Таблица не полностью инстанцирует, и никакие строки не написаны или считаны из нее, строки посылают непосредственно клиенту. Результат уменьшение требований к памяти и диску и меньшая задержка прежде, чем первую строку пошлют клиенту, потому что сервер не должен ждать, пока последний блок запроса будет выполнен. EXPLAIN и трассировка оптимизатора отражают эту стратегию выполнения: блок запроса UNION RESULT не присутствует, потому что тот блок соответствует части, которая читает из временной таблицы.

Эти условия квалифицируют UNION для оценки без временной таблицы:

  • Союз UNION ALL, но не UNION или UNION DISTINCT.

  • Нет глобального ORDER BY.
  • Союз не высокоуровневый блок запроса {INSERT | REPLACE} ... SELECT ....

Механизмы хранения, используемые для временных таблиц

Внутренняя временная таблица может быть проведена в памяти и обработана MEMORY или сохранена на диске как InnoDB или MyISAM.

Если внутренняя временная таблица составлена как таблица в памяти, но становится слишком большой, MySQL автоматически преобразовывает ее в таблицу на диске. Максимальный размер для временных таблиц в памяти определен от значений переменных tmp_table_size и max_heap_table_size (какое меньше). Это отличается от MEMORY, явно составленных с CREATE TABLE: для таких таблиц, только переменная max_heap_table_size определяет, как сильно разрешают вырасти большой таблице и нет никакого преобразования в формат на диске.

Переменная internal_tmp_disk_storage_engine определяет, который механизм хранения использует сервер, чтобы управлять внутренними временными таблицами на диске. Разрешенные значения INNODB (по умолчанию) и MYISAM.

Используя internal_tmp_disk_storage_engine=INNODB, запросы, которые производят временное табличное превышение лимита строк или столбцов в InnoDB вернут ошибку Row size too large или Too many columns. Обходное решение должно установить internal_tmp_disk_storage_engine в MYISAM.

Временный табличный формат хранения

В памяти временными таблицами управляет механизм хранения MEMORY, который использует формат строки фиксированной длины. VARCHAR и VARBINARY дополнены к максимальной длине столбца, в действительности храня их как столбцы CHAR и BINARY.

На диске временными таблицами управляют InnoDB или MyISAM (в зависимости от internal_tmp_disk_storage_engine). Оба механизма хранят временные таблицы, используя формат строки динамической ширины. Столбцы берут только столько места, сколько надо, что уменьшает дисковый ввод/вывод, требования пространства и время обработки по сравнению с таблицами на диске с использованием фиксированной длины строки.

Для запросов, которые первоначально составляют внутреннюю временную таблицу в памяти, затем преобразуют ее в таблицу на диске, лучшая работа могла бы быть достигнута, пропуская конверсионный шаг и составляя таблицу на диске для начала. Системная переменная big_tables может использоваться, чтобы вызвать дисковое хранение внутренних временных таблиц.

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_change_buffering=all так, чтобы операции обновления и удаления были буферизованы в дополнение к вставкам.
    • Рассмотрите запросы COMMIT периодически во время большой работы изменения данных, возможно деля одиночный запрос на несколько, которые воздействуют на меньшее число строк.

    Чтобы избавиться от безудержной обратной перемотки, как только это происходит, увеличьте буферный пул так, чтобы обратная перемотка стала ограничиваться центральным процессором и работала быстро, или уничтожьте сервер и перезапустите его с innodb_force_recovery=3, см. раздел 16.17.1.

    Эта проблема, как ожидают, будет нечастой с настройкой по умолчанию innodb_change_buffering=all, которая позволяет операции обновления и удаления, которые будут кэшироваться в памяти, делая их быстрее, и также быстрее откатиться, если нужно. Удостоверьтесь, что использовали эту установку параметра на серверах, которые обрабатывают продолжительные транзакции со многими вставками, обновлениями или удалениями.

  • Если Вы можете допустить потерю некоторых из последних переданных транзакций, если катастрофический отказ происходит, Вы можете установить innodb_flush_log_at_trx_commit в 0. InnoDB будет пытаться сбросить журнал однажды в секунду, хотя сброс не гарантируется.
  • Когда строки изменены или удалены, строки и связанные журналы отмены физически не удалены немедленно или даже немедленно после того, как транзакция передается. Старые данные сохранены до завершения транзакций, которые запускались ранее или одновременно, чтобы те транзакции могли получить доступ к предыдущему состоянию измененных или удаленных строк. Таким образом, продолжительная транзакция может предотвратить чистку данных, которые были изменены иной транзакцией.
  • Когда строки изменены или удалены в пределах продолжительной транзакции, другие транзакции, используя уровни изоляции READ COMMITTED и REPEATABLE READ должны сделать больше работы, чтобы восстановить более старые данные, если они читают те же самые строки.
  • Когда продолжительная транзакция изменяет таблицу, запросы против той таблицы от других транзакций не используют покрывающий индекс. Запросы, которые обычно могли получать все столбцы результата от вторичного индекса, вместо этого ищут соответствующие значения от табличных данных.

    Если у вторичных индексных страниц есть PAGE_MAX_TRX_ID, который слишком новый, или если записи во вторичном индексе отмечены как удаленные, InnoDB, возможно, должен искать записи, используя кластеризируемый индекс.

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, следуйте за этим набором шагов:

    1. Определите столбец 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);
      
    2. Загрузите данные в таблицу.
    3. Создайте индекс 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:

    • Длина списка истории низкая, ниже нескольких тысяч.

    • Буферные слияния близки к вставленным строкам.
    • Измененные страницы в буферном пуле последовательно значительно ниже innodb_max_dirty_pages_pct из буферного пула в то время, когда сервер не делает больших вставок, это нормально во время большой вставки.
    • Log sequence number - Last checkpoint меньше, чем 7/8 или идеально меньше, чем 6/8 полного размера файлов системного журнала InnoDB.

  • Вы можете использовать в своих интересах 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, когда у таблицы есть много индексов. Используйте следующую процедуру:

    1. Выполните FLUSH TABLES или mysqladmin flush-tables.

    2. Используйте myisamchk --keys-used=0 -rq /path/to/db/tbl_name , чтобы удалить все индексы для таблицы.
    3. Вставьте данные в таблицу с LOAD DATA INFILE. Это не обновляет индексы и поэтому очень быстро.
    4. Если Вы намереваетесь только читать из таблицы в будущем, используйте myisampack , см. раздел 17.2.3.3.
    5. Обновите индексирование с myisamchk -rq /path/to/db/tbl_name. Это создает индексное дерево в памяти прежде, чем написать на диск, что намного быстрее, чем обновление индексирования во время LOAD DATA INFILE, потому что это избегает многих дисковых поисков. Получающиеся индексное дерево также отлично сбалансировано.
    6. Выполните 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.

    Блокировка также удаляет полное время для тестов многократного соединения, хотя максимум ожидания для отдельных соединений могло бы повыситься, потому что они ждут блокировок. Предположите, что пять клиентов пытаются вставлять одновременно следующим образом:

    • Соединение 1 делает 1000 вставок.

    • Соединения 2, 3 и 4 делают 1 вставку.
    • Соединение 5 делает 1000 вставок.

    Если Вы не используете блокировку, соединения 2, 3 и 4 закончат до 1 и 5. Если Вы используете блокировку, соединения 2, 3 и 4, вероятно, не заканчиваются прежде 1 или 5, но полное время должно быть приблизительно на 40% быстрее.

    INSERT, UPDATE и DELETE очень быстры в MySQL, но Вы можете получить лучшую эффективность работы, добавляя блокировки вокруг всего, что делает больше, чем приблизительно пять последовательных вставок или обновлений. Если Вы делаете очень много последовательных вставок, Вы могли бы сделать LOCK TABLES, сопровождаемый UNLOCK TABLES время от времени (приблизительно каждые 1000 строк), чтобы разрешить другим потокам получать доступ к таблице. Это все еще привело бы к хорошему приросту производительности.

    INSERT все еще намного медленнее для того, чтобы загрузить данные, чем LOAD DATA INFILE, даже когда использование стратегий только обрисовано в общих чертах.

  • Чтобы ускорить работу для MyISAM, для обоих LOAD DATA INFILE и INSERT, увеличьте ключевой кэш, увеличивая переменную key_buffer_size, см. раздел 6.1.1.

9.6.3. Скорость REPAIR TABLE

REPAIR TABLE для MyISAM подобен использованию myisamchk для операций ремонта, и часть той же самой исполнительной оптимизации применяется:

  • myisamchk имеет переменные для управления распределением памяти. Вы можете быть в состоянии улучшить работу, устанавливая эти переменные, как описано в разделе 5.6.4.6.

  • Для REPAIR TABLE тот же самый принцип применяется, но потому что ремонт сделан сервером, Вы устанавливаете системные переменные сервера вместо myisamchk. Кроме того, в дополнение к установке переменных распределения памяти, увеличение myisam_max_sort_file_size увеличивает вероятность, что ремонт будет использовать быстрый метод filesort и избегать более медленного ремонта методом ключевого кэша. Установите переменную в максимальный размер файла для Вашей системы, после проверки, что есть достаточное свободное пространство, чтобы разместить копию табличных файлов. Свободное пространство должно быть доступным в файловой системе, содержащей оригинальные табличные файлы.

Предположите, что myisamchk работает, используя следующие опции, чтобы установить переменные распределения памяти:

--key_buffer_size=128M --myisam_sort_buffer_size=256M
--read_buffer_size=64M --write_buffer_size=64M

Некоторые из тех переменных myisamchk соответствуют переменным сервера:

Переменная myisamchk Системная переменная
key_buffer_size key_buffer_size
myisam_sort_buffer_size myisam_sort_buffer_size
read_buffer_size read_buffer_size
write_buffer_sizeНет

Каждая из системных переменных сервера может быть установлена во время выполнения, и некоторые из них ( myisam_sort_buffer_size, read_buffer_size ) имеют значение сеанса в дополнение к глобальному значению. Установка значения сеанса ограничивает эффект изменения Вашего текущего сеанса и не затрагивает других пользователей. Замена глобальной переменной (key_buffer_size , myisam_max_sort_file_size) затрагивает других пользователей также. Для key_buffer_size Вы должны принять во внимание, что буфер совместно использован с теми пользователями. Например, если Вы устанавливаете переменную myisamchk key_buffer_size в 128MB, Вы могли установить key_buffer_size больше чем это (если это еще не установлено больше), чтобы разрешить ключевое буферное использование деятельностью в других сеансах. Однако, изменение глобального ключевого размера буфера лишает законной силы буфер, вызывая увеличенный дисковый ввод/вывод и замедление для других сеансов. Альтернатива, которая избегает этой проблемы, должна использовать отдельный ключевой кэш для индексов из восстанавливаемой таблицы, который надо освободить, когда ремонт закончен. См. раздел 9.10.2.2.

Основанный на предыдущих замечаниях, REPAIR TABLE может быть сделан следующим образом, чтобы использовать настройки, подобные myisamchk. Здесь отдельный ключевой буфер 128 МБ выделен, и файловая система, как предполагается, разрешает размер файла по крайней мере 100 GB.

SET SESSION myisam_sort_buffer_size = 256*1024*1024;
SET SESSION read_buffer_size = 64*1024*1024;
SET GLOBAL myisam_max_sort_file_size = 100*1024*1024*1024;
SET GLOBAL repair_cache.key_buffer_size = 128*1024*1024;
CACHE 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: SELECT, DELETE, INSERT, REPLACE и UPDATE.

  • Когда EXPLAIN используется с объяснимым запросом, MySQL выводит на экран информацию от оптимизатора о плане выполнения запроса. Таким образом, MySQL объясняет, как он обработал бы запрос, включая информацию о том, как к таблицам присоединяются и в каком порядке. Для информации об использовании EXPLAIN, чтобы получить информацию о плане выполнения, см. раздел 9.8.2 .
  • Когда EXPLAIN используется с FOR CONNECTION connection_id вместо объяснимого запроса, это выводит на экран план выполнения относительно запроса в названном соединении. См. раздел 9.8.4.
  • Опция FORMAT может использоваться, чтобы выбрать выходной формат. TRADITIONAL представляет вывод в табличном формате. Это значение по умолчанию, если нет опции FORMAT. Формат JSON выводит на экран информацию в формате JSON. С FORMAT = JSON вывод включает расширенные данные и информацию о разделе

С помощью EXPLAIN Вы можете видеть, где Вы должны добавить индекс к таблицам так, чтобы запрос выполнился быстрее при использовании индекса, чтобы найти строки. Вы можете также использовать EXPLAIN, чтобы проверить, присоединяется ли оптимизатор к таблицам в оптимальном порядке. Чтобы дать подсказку оптимизатор, чтобы использовать порядок соединения, соответствующий порядку, в котором таблицы называют в SELECT, начните запрос с SELECT STRAIGHT_JOIN вместо SELECT, см. раздел 14.2.9. Однако, STRAIGHT_JOIN может не дать использовать индексы, потому что это отключает преобразования полусоединения. См. раздел 9.2.1.18.1.

Оптимизатор может иногда предоставлять информацию, дополнительную к EXPLAIN. Однако, формат трассировки оптимизатора и контент подвержены изменениям между версиями. Для деталей см. MySQL Internals: Tracing the Optimizer.

Если у Вас есть проблема с индексом, выполните ANALYZE TABLE, чтобы обновить табличную статистику, такую как количество элементов ключей, которые могут затронуть выбор, который делает оптимизатор. См. раздел 14.7.2.1.

EXPLAIN может также использоваться, чтобы получить информацию о столбцах в таблице. EXPLAIN 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 .
  • Дополнительная информация EXPLAIN.
  • Интерпретация вывода EXPLAIN.

Столбцы вывода EXPLAIN

Этот раздел описывает выходные столбцы, произведенные EXPLAIN. Более поздние разделы обеспечивают дополнительную информацию о столбцах type и Extra.

Каждая выходная строка EXPLAIN предоставляет информацию об одной таблице. Каждая строка содержит значения, полученные в итоге в таблице 9.1, и описанные более подробно после таблицы. Имена столбцов показывают в первом столбце таблицы, второй столбец обеспечивает эквивалентное имя свойства, показанное в выводе, когда используется FORMAT=JSON.

Таблица 9.1. Столбцы вывода EXPLAIN

СтолбецИмя JSON Значение
id select_id Идентификатор SELECT
select_typeНетТип SELECT
table table_nameТаблица для выходной строки
partitionspartitions Соответствующее разделение
type access_typeТип соединения
possible_keyspossible_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)

    Название таблицы, к которой обращается строка вывода. Это может также быть одним из следующих значений:

    • <unionM,N> : Строка обращается к союзу строк с id M и N.

    • <derivedN>: Строка обращается к полученному табличному результату для строки с id N. Полученная таблица может закончиться, например, от подзапроса в FROM.
    • <subqueryN>: Строка обращается к результату осуществленного подзапроса для строки с id N. См. раздел 9.2.1.18.2.

  • partitions (Имя JSON: partitions)

    Раздел, от которого записи были бы соответствующими запросу. Этот столбец выведен на экран только, если использован параметр PARTITIONS. NULL для неразделенных таблиц. См. раздел 20.3.5.

  • type (Имя JSON: access_type)

    Тип соединения. Для описаний различных типов см. Типы соединения EXPLAIN .

  • possible_keys (Имя JSON: possible_keys)

    Столбец possible_keys указывает, который индекс MySQL может использоваться, чтобы находить строки в этой таблице. Отметьте, что этот столбец полностью независим от порядка таблиц, как выведено на экран в выводе EXPLAIN. Это означает, что некоторые из ключей possible_keys не могут быть применимы практически с произведенным табличным порядком.

    Если этот столбец NULL (или неопределенный в JSON), там нет релевантных индексов. В этом случае Вы можете быть в состоянии улучшить исполнение своего запроса, исследуя WHERE, чтобы проверить, обращается ли это к некоторому столбцу или столбцам, которые были бы подходящими для того, чтобы индексировать. Если так, создайте соответствующий индекс и проверьте запрос с EXPLAIN, см. раздел 14.1.7.

    Чтобы видеть все индексы таблицы, используйте SHOW INDEX FROM tbl_name.

  • key (Имя JSON: key)

    Столбец key указывает ключ (индекс), который MySQL фактически решил использовать. Если MySQL решает использовать один из possible_keys, чтобы искать строки, индекс перечислен как значение ключа.

    Возможно, что key назовет индекс, который не присутствует в possible_keys. Это может произойти, если ни один из possible_keys не подходящий для того, чтобы искать строки, но все столбцы, выбранные запросом, являются столбцами некоторого другого индекса. Таким образом, названный индекс покрывает выбранные столбцы, так что, хотя это не используется, чтобы определить, какие строки получить, индексный просмотр более эффективен, чем просмотр строк данных.

    Для InnoDB вторичный индекс мог бы покрыть выбранные столбцы, даже если запрос также выбирает первичный ключ, потому что InnoDB хранит значение первичного ключа в каждом вторичном индексе. Если key = NULL, MySQL не имеет индекса для того, чтобы выполнить запрос более эффективно.

    Чтобы вынудить MySQL использовать или проигнорировать индекс перечисленный в possible_keys, укажите в запросе FORCE INDEX, USE INDEX или IGNORE INDEX, см. раздел 9.9.4.

    Для MyISAM ANALYZE TABLE помогает оптимизатору выбрать лучший индекс. Для MyISAM myisamchk --analyze делает то же самое. См. разделы 14.7.2.1 и 8.6.

  • key_len (Имя JSON: key_length)

    key_len указывает на длину ключа, который MySQL решил использовать. Значение key_len позволяет Вам определить, сколько частей ключа MySQL фактически использует. Если key = NULL , len_len тоже NULL.

    Из-за ключевого формата хранения, длина ключа больше для столбца, который может быть NULL, чем для NOT NULL.

  • ref (Имя JSON: ref)

    ref показывает, которые столбцы или константы сравниваются с индексом, названным в key, чтобы выбрать строки из таблицы.

    Если значение равно func, используемое значение является результатом некоторой функции. Чтобы видеть, которая это функция, надо использовать EXPLAIN EXTENDED вместе с SHOW WARNINGS . Функция могла бы фактически быть оператором, таким как арифметический оператор.

  • rows (Имя JSON: rows)

    rows указывает на число строк, которые MySQL должен исследовать, чтобы выполнить запрос.

    Для InnoDB это число только оценка и, возможно, всегда не точно.

  • filtered (Имя JSON: filtered)

    filtered указывает на предполагаемый процент строк таблицы, которые будут фильтроваться по табличному условию. Таким образом, rows показывает предполагаемое число исследованных строк, а rows * filtered/100 показывает число строк, к которым присоединятся с предыдущими таблицами.

  • Extra (Имя JSON: нет)

    Этот столбец содержит дополнительную информацию о том, как MySQL решает запрос. Для описания различных значений см. EXPLAINExtra 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 считал все таблицы constsystem) и уведомляет, что WHERE всегда ложен.

  • LooseScan(m..n) (Свойство JSON: message)

    Стратегия LooseScan используется. m и n номера частей ключа.

  • No matching min/max row (Свойство JSON: message )

    Никакая строка не удовлетворяет условие для такого запроса, как SELECT MIN(...) FROM ... WHERE condition.

  • no matching row in const table (Свойство JSON: message)

    Для запроса с соединением была пустая таблица, или таблица без строк, удовлетворяющих уникальное индексное условие.

  • No matching rows after partition pruning (Свойство JSON: message)

    Для DELETE или UPDATE оптимизатор не нашел ничего для удаления или обновления после сокращения разделения. Это подобно значению Impossible WHERE для SELECT.

  • No tables used (Свойство JSON: message)

    Запрос не имеет FROM или имеет FROM DUAL.

    Для INSERT или REPLACE EXPLAIN покажет это значение, когда нет части SELECT. Например, это появляется для EXPLAIN INSERT INTO t VALUES(10), потому что это эквивалентно EXPLAIN INSERT INTO t SELECT 10 FROM DUAL .

  • Not exists (Свойство JSON: message)

    MySQL смог сделать оптимизацию LEFT JOIN на запросе и не исследует больше строк в этой таблице для предыдущей комбинации строки после того, как это находит одну строку, которая соответствует LEFT JOIN. Вот пример типа запроса, который может быть оптимизирован так:

    SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id
             WHERE t2.id IS NULL;
    

    Предположим, t2.id определен как NOT NULL. В этом случае MySQL просмотрит t1 и ищет строки в t2, используя t1.id. Если MySQL находит соответствующую строку в t2, это знает, что t2.id никогда не может быть NULL и не просматривает через остальную часть строк в t2, у которых есть тот же самый id. Другими словами, для каждой строки в t1 MySQL MySQL должен сделать только единственный поиск в t2 независимо от того, сколько строк фактически соответствуют в t2.

  • Plan isn't ready yet (Свойство JSON: нет)

    Это значение происходит с EXPLAIN FOR CONNECTION, когда оптимизатор не закончил создавать план выполнения относительно запроса в названном соединении. Если вывод плана выполнения включает многократные строки, у любых из них могло бы быть это значение Extra, в зависимости от продвижения оптимизатора в определении полного плана выполнения.

  • Range checked for each record (index map: N) (Свойство JSON: message)

    MySQL не нашел хороший индекс, но нашел, что часть из индексов могла бы использоваться после того, как значения столбцов от предыдущих таблиц будут известны. Для каждой комбинации строк в предыдущих таблицах MySQL проверяет, возможно ли использовать метод доступа range или index_merge, чтобы получить строки. Это не очень быстро, но быстрее, чем выполнение соединения без индексов вообще. Критерии применимости описаны в разделах 9.2.1.3 и 9.2.1.4 за исключением того, что все значения столбцов для предыдущей таблицы известны и, как полагают, константы.

    Индексы пронумерованы, начиная с 1, в том же самом порядке, как показано SHOW INDEX для таблицы. Значение индексной карты N это значение битовой маски, которое указывает, который индекс кандидат. Например, значение 0x19 (двоичное 11001) означает, что рассмотрят индексы 1, 4 и 5.

  • Scanned N databases (Свойство JSON: message)

    Это указывает, сколько просмотров каталога сервер выполняет, обрабатывая запрос для таблиц INFORMATION_SCHEMA, см. раздел 9.2.4. Значение N может быть 0, 1 или all.

  • Select tables optimized away (Свойство JSON: message )

    Оптимизатор решил, что: 1) самое большее одна строка должна быть возвращена, и 2) чтобы произвести эту строку, детерминированный набор строк должен быть считан. Когда строки, которые будут считаны, могут быть считаны во время фазы оптимизации (например, чтением индекса строк), нет никакой потребности читать любые таблицы во время выполнения запроса.

    Первое условие выполнено, когда запрос неявно сгруппирован (содержит совокупную функцию, но нет GROUP BY). Второе условие выполнено, когда один поиск строки выполнен на применение индекса. Число прочитанных индексов определяет число строк, чтобы читать.

    Рассмотрите следующий неявно сгруппированный запрос:

    SELECT MIN(c1), MIN(c2) FROM t1;
    

    Предположите, что MIN(c1) может быть получен, читая одну строку индекса и MIN(c2) может быть получен, читая одну строку из другого индекса. Таким образом, для каждого столбца c1 и c2 там существует индекс, где столбец это первый столбец индекса. В этом случае одна строка возвращена (и произведена), читая две детерминированных строки.

    Это значение Extra не происходит, если строки для чтения не детерминированы. Рассмотрите этот запрос:

    SELECT MIN(c2) FROM t1 WHERE c1 <= 10;
    

    Допустим, (c1, c2) покрывающий индекс. Используя этот индекс, все строки с c1 <= 10 должны быть просмотрены, чтобы найти минимум c2. В отличие от этого, рассмотрите этот запрос:

    SELECT MIN(c2) FROM t1 WHERE c1 = 10;
    

    В этом случае первая индексная строка с c1 = 10 содержит минимум c2. Только одна строка должна быть считана, чтобы произвести возвращенную строку.

    Для механизмов хранения, которые поддерживают точное количество строк на таблицу (такой как MyISAM, но не InnoDB), это значение Extra может произойти для запросов COUNT(*), для которых WHERE отсутствует или всегда истина и нет GROUP BY. Это случай неявно сгруппированного запроса, где механизм хранения влияет на то, может ли быть считано детерминированное число строк.

  • Skip_open_table, Open_frm_only, Open_full_table (Свойство JSON: message)

    Эти значения указывают на открывающую файл оптимизацию, которая относится к запросам к таблицам INFORMATION_SCHEMA.

    • Skip_open_table: Табличные файлы не должны быть открыты. Информация уже доступна из словаря данных.

    • Open_frm_only: Только словарь данных должен быть считан для информации о таблице.
    • Open_full_table: Неоптимизированный информационный поиск. Информация о таблице должна быть считана из словаря данных и читая табличные файлы.

  • Start temporary, End temporary (Свойство JSON: message)

    Это указывает на временное табличное использование для стратегии Duplicate Weedout.

  • unique row not found (Свойство JSON: message)

    Для такого запроса, как SELECT ... FROM tbl_name, никакие строки не удовлетворяют условию для индекса UNIQUE или PRIMARY KEY на таблице.

  • Using filesort (Свойство JSON: using_filesort)

    MySQL должен сделать дополнительный проход, чтобы узнать, как получить строки в сортированном порядке. Сортировка сделана, проходя все строки, согласно типу соединения и храня ключ сортировки и указатель на строку для всех строк, которые соответствуют WHERE. Ключи тогда отсортированы, и строки получены в сортированном порядке. См. раздел 9.2.1.15.

  • Using index (Свойство JSON: using_index)

    Информация о столбце получена от таблицы, используя только информацию в индексном дереве, не имея необходимости делать дополнительный поиск, чтобы читать фактическую строку. Эта стратегия может использоваться, когда запрос использует только столбцы, которые являются частью одного индекса.

    Для InnoDB с определяемым пользователем кластеризуемым индексом могут использоваться, даже когда Using index отсутствует в столбце Extra. Дело обстоит так, если type = index , а key = PRIMARY.

  • Using index condition (Свойство JSON: using_index_condition)

    Таблицы считаны доступом к индексным кортежам и проводится тестирование их сначала, чтобы определить, читать ли полные строки таблицы. Таким образом, индексная информация используется, чтобы задержать (push down) чтение полных строк таблицы, если это не необходимо. См. раздел 9.2.1.6 .

  • Using index for group-by (Свойство JSON: using_index_for_group_by)

    Подобно методу доступа Using index, Using index for group-by указывает, что MySQL нашел индекс, который может использоваться, чтобы получить все столбцы запроса GROUP BY или DISTINCT без любого дополнительного дискового доступа к фактической таблице. Дополнительно, индексирование используется самым эффективным способом так, чтобы для каждой группы были считаны только некоторые индексные записи. Для деталей см. раздел 9.2.1.16 .

  • Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access) (Свойство JSON: using_join_buffer)

    Таблицы от более ранних соединений считаны в буфер соединения, а затем их строки используются, чтобы выполнить соединение с текущей таблицей. (Block Nested Loop) указывает на использование алгоритма Block Nested-Loop и (Batched Key Access) указывает на использование алгоритма Batched Key Access. Таким образом, ключи от таблицы на предыдущей строке вывода EXPLAIN будут буферизованы, и соответствующие строки будут принесены в пакетах от таблицы, представленной строкой, в которой появляется Using join buffer.

    В JSON-отформатированном выводе значение using_join_buffer всегда Block Nested Loop или Batched Key Access.

  • Using MRR (Свойство JSON: message)

    Таблицы считаны, используя стратегию оптимизации Multi-Range Read, см. раздел 9.2.1.13.

  • Using sort_union(...), Using union(...), Using intersect(...) (Свойство JSON: message)

    Они указывают, как индексные просмотры слиты для типа соединения index_merge. См. раздел 9.2.1.4.

  • Using temporary (Свойство JSON: using_temporary_table)

    Чтобы решить запрос, MySQL должен составить временную таблицу, чтобы держать результат. Это как правило происходит, если запрос содержит GROUP BY и ORDER BY.

  • Using where (Свойство JSON: attached_condition)

    WHERE используется, чтобы ограничить, которые строки соответствуют следующей таблице или посланы клиенту. Если Вы определенно не намереваетесь принести или исследовать все строки от таблицы, у Вас может быть что-то не так в Вашем запросе, если Extra не Using where и табличный тип соединения ALL или index.

    Using where не имеет никакой прямой копии в JSON-отформатированном выводе, свойство attached_condition содержит любые условия WHERE.

  • Using where with pushed condition (JSON property: message)

    Этот элемент относится только к NDB. Это означает, что MySQL Cluster использует оптимизацию Condition Pushdown, чтобы улучшить эффективность прямого сравнения между неиндексированным столбцом и константой. В таких случаях условие продвинуто вниз узлам данных кластера и оценено на всех узлах данных одновременно. Это избавляет от необходимости посылать несоответствие строк по сети, и может ускорить такие запросы в 5-10 раз. Для получения дополнительной информации см. раздел 9.2.1.5.

  • Zero limit (Свойство JSON: message)

    У запроса был LIMIT 0, невозможно выбрать строки.

Выходная интерпретация 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;

Для этого примера, сделайте следующие предположения:

  • Сравниваемые столбцы были объявлены следующим образом.

    ТаблицаСтолбец Тип данных
    ttActualPC CHAR(10)
    ttAssignedPC CHAR(10)
    ttClientID CHAR(10)
    etEMPLOYID CHAR(15)
    doCUSTNMBR CHAR(15)
  • У таблиц есть следующие индексы.

    ТаблицаИндекс
    ttActualPC
    ttAssignedPC
    ttClientID
    etEMPLOYID (primary key)
    doCUSTNMBR (primary key)
  • Значения tt.ActualPC равномерно не распределены.

Первоначально, прежде, чем любая оптимизация была выполнена, EXPLAIN производит следующую информацию:

table type possible_keys key  key_len ref  rows  Extra
et    ALL  PRIMARY       NULL NULL    NULL 74
do    ALL  PRIMARY       NULL NULL    NULL 2135
et_1  ALL  PRIMARY       NULL NULL    NULL 74
tt    ALL  AssignedPC,   NULL NULL    NULL 3872
           ClientID,
           ActualPC
Range checked for each record (index map: 0x23)

Так как type = ALL для каждой таблицы этот вывод указывает, что MySQL производит декартово произведение всех таблиц, то есть, каждой комбинация строк. Это занимает длительное время, потому что произведение числа строк в каждой таблице должно быть исследовано. Для данного случая это 74*2135*74*3872= 45268558720 строк. Если бы таблицы были больше, Вы можете только вообразить, сколько времени это заняло бы...

Одна проблема здесь состоит в том, что MySQL может использовать индекс на столбцах более эффективно, если они объявлены как тот же самый тип и размер. В этом контексте VARCHAR и CHAR считаются тем же самым, если они объявлены как тот же самый размер. tt.ActualPC объявлен как CHAR(10) и et.EMPLOYID как CHAR(15), таким образом, есть несоответствие длины.

Чтобы устранить это неравенство между длинами столбца, надо использовать ALTER TABLE и удлинить ActualPC с 10 символов до 15:

mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

Теперь tt.ActualPC и et.EMPLOYID оба VARCHAR(15). Выполнение EXPLAIN приводит к этому результату:

table type   possible_keys key     key_len ref         rows Extra
tt    ALL    AssignedPC,   NULL    NULL    NULL        3872 Using
             ClientID,                                      where
             ActualPC
do    ALL    PRIMARY       NULL    NULL    NULL        2135
Range checked for each record (index map: 0x1)
et_1  ALL    PRIMARY       NULL    NULL    NULL        74
Range checked for each record (index map: 0x1)
et    eq_ref PRIMARY       PRIMARY 15      tt.ActualPC 1

Это не прекрасно, но намного лучше: произведение значений rows меньше в 74 раза. Эта версия выполняется за пару секунд.

Второе изменение может быть сделано, чтобы устранить несоответствия длины столбца для tt.AssignedPC = et_1.EMPLOYID и tt.ClientID = do.CUSTNMBR:

mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
    ->       MODIFY ClientID   VARCHAR(15);

После этой модификации EXPLAIN производит вывод, показанный здесь:

table type   possible_keys key      key_len ref           rows Extra
et    ALL    PRIMARY       NULL     NULL    NULL          74
tt    ref    AssignedPC,   ActualPC 15      et.EMPLOYID   52   Using
             ClientID,                                         where
             ActualPC
et_1  eq_ref PRIMARY       PRIMARY  15      tt.AssignedPC 1
do    eq_ref PRIMARY       PRIMARY  15      tt.ClientID   1

В этом пункте запрос оптимизирован почти так, как возможно. Остающаяся проблема состоит в том, что по умолчанию MySQL предполагает, что значения в tt.ActualPC равномерно распределены, но дело обстоит не так для tt. К счастью, легко сказать MySQL анализировать ключевое распределение:

mysql> ANALYZE TABLE tt;

С дополнительной индексной информацией соединение прекрасно и EXPLAIN приводит к этому результату:

table type   possible_keys key     key_len ref           rows Extra
tt    ALL    AssignedPC    NULL    NULL    NULL          3872 Using where
             ClientID,
             ActualPC
et    eq_ref PRIMARY       PRIMARY 15      tt.ActualPC   1
et_1  eq_ref PRIMARY       PRIMARY 15      tt.AssignedPC 1
do    eq_ref PRIMARY       PRIMARY 15      tt.ClientID   1

Столбец rows в выводе EXPLAIN образован предположением от оптимизатора соединения MySQL. Проверьте, являются ли числа близко к правде, сравнивая произведение rows с фактическим числом строк, которые возвращает запрос. Если числа очень отличаются, Вы могли бы получить лучшую работу при использовании STRAIGHT_JOIN в SELECT и пытаясь перечислить таблицы в различном порядке в FROM. Но STRAIGHT_JOIN может блокировать использование индексов, потому что это отключает преобразования полусоединения. См. раздел 9.2.1.18.1.

Возможно в некоторых случаях выполнить запросы, которые изменяют данные, когда EXPLAIN SELECT используется с подзапросом, для получения дополнительной информации см. раздел 14.2.10.8.

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-Loopblock_nested_loop Использование алгоритма BNL joinON
Condition Filteringcondition_fanout_filter Использование фильтрации условияON
Engine Condition Pushdown engine_condition_pushdownУсловие механизма pushdown ON
Index Condition Pushdown index_condition_pushdownУправление индексным pushdown ON
Index Extensionsuse_index_extensions Использование средств индексного расширения ON
Index Mergeindex_merge Управляет Index MergeON
index_merge_intersection Управляет Index Merge Intersection AccessON
index_merge_sort_union Управляет Index Merge Sort-Union AccessON
index_merge_union Управляет Index Merge Union Access optimizationON
Multi-Range Readmrr Управляет стратегией Multi-Range ReadON
mrr_cost_based Управляет MRR на основе издержек, если mrr=on ON
Semi-joinsemijoin Контролирует все стратегии полусоединенияON
firstmatch Управляет стратегией FirstMatchON
loosescan Управляет стратегией LooseScan strategy (не путать с LooseScan для GROUP BY)ON
duplicateweedout Управляет стратегией Duplicate WeedoutON
Subquery materializationmaterialization Управляет материализацией (включая материализацию полусоединения)ON
subquery_materialization_cost_based Используемый выбор материализации на основе издержек ON
Derived table mergingderived_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) */ ...

Когда комментарий подсказки содержит многократные подсказки, возможность дубликатов и конфликтов существует:

  • Двойные подсказки: Для такой подсказки, как /*+ MRR(idx1) MRR(idx1) */, MySQL использует первую подсказку и выпускает предупреждение о двойной подсказке.

  • Противоречивые подсказки: Для такой подсказки, как /*+ MRR(idx1) NO_MRR(idx1) */, MySQL использует первую подсказку и выпускает предупреждение о второй противоречивой подсказке.

Имена блока запроса это идентификаторы, они неотступно следуют обычным правилам, какие имена допустимы и как заключить их в кавычки (см. раздел 10.2).

Имена подсказки, имена блока запроса и имена стратегии не являются чувствительными к регистру. Ссылки на имена таблицы и индекса следуют обычным правилам чувствительности к регистру идентификатора (см. раздел 10.2.2).

Подсказки оптимизатора на уровне таблицы

Эффект подсказок на уровне таблицы:

  • Использование алгоритмов обработки соединения Block Nested-Loop (BNL) и Batched Key Access (BKA) (см. раздел 9.2.1.14).

  • Должны ли полученные таблицы или представления быть слиты во внешний блок запроса или осуществить использование внутренней временной таблицы.

Эти типы подсказки относятся к определенным таблицам или всем таблицам в блоке запроса.

Синтаксис подсказок на уровне таблицы:

hint_name([@query_block_name]
[tbl_name [, tbl_name] ...])
hint_name([tbl_name@query_block_name
[, tbl_name@query_block_name] ...])

Синтаксис ссылается на эти термины:

  • hint_name: Эти имена подсказки разрешены:

    • BKA, NO_BKA: Включите или отключите BKA для указанных таблиц.

    • BNL, NO_BNL: Включите или отключите BNL для указанных таблиц.
    • MERGE, NO_MERGE: Включите или отключите слияние для указанных таблиц или представлений.

  • tbl_name: Название таблицы, которая используется в запрос. Подсказка относится ко всем таблицам, которые она называет. Если подсказка не называет таблицу, она относится ко всем таблицам блока запроса, в котором она происходит.

    Если у таблицы есть псевдоним, подсказки должны обратиться к псевдониму, не к имени таблицы.

    Имена таблиц в подсказках не могут быть квалифицированы с именами схемы.

  • query_block_name: Блок запроса, к которому применяется подсказка. Если подсказка не включает @query_block_name, подсказка относится к блоку запроса, в котором она происходит. Для формата tbl_name@query_block_name подсказка относится к названной таблице в названном блоке запроса. Чтобы назначить имя блоку запроса, см. сюда.

Примеры:

SELECT /*+ NO_BKA(t1, t2) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3;
SELECT /*+ NO_BNL() BKA(t1) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3;
SELECT /*+ NO_MERGE(dt) */ * FROM (SELECT * FROM t1) AS dt;

Подсказка на уровне таблицы относится к таблицам, которые получают отчеты от предыдущих таблиц, а не таблиц отправителя. Рассмотрите этот запрос:

SELECT /*+ BNL(t2) */ FROM t1, t2;

Если оптимизатор хочет обрабатывать t1, это применяет соединение Block Nested-Loop к t2 буферизуя строки из t1 прежде, чем начать читать из t2. Если оптимизатор вместо этого хочет обрабатывать сначала t2, подсказка не имеет никакого эффекта, потому что t2 это таблица отправителя.

Для подсказок MERGE и NO_MERGE эти правила приоритета применяются:

  • Подсказка имеет приоритет перед любой эвристикой оптимизатора, которая не является техническим ограничением. Если предложение подсказки не имеет никакого эффекта, у оптимизатора есть причина игнорирования ее.

  • Подсказка имеет приоритет перед флагом derived_merge в переменной optimizer_switch.
  • Для представлений ALGORITHM={MERGE|TEMPTABLE} в определении представления имеет приоритет перед подсказкой, определенной в запросе, ссылающемся на представление.

Подсказки оптимизатора уровня индекса

Подсказки оптимизатора уровня индекса указывают, какие стратегии обработки индекса оптимизатор использует для особых таблиц или индексов. Это влияет на использование оптимизаций Index Condition Pushdown (ICP), Multi-Range Read (MRR) и range (см. раздел 9.2.1).

Синтаксис индексного уровня подсказки:

hint_name([@query_block_name]
tbl_name [index_name
[, index_name] ...])
hint_name(tbl_name@query_block_name
[index_name [, index_name] ...])

Синтаксис ссылается на эти термины:

  • hint_name: Эти имена подсказки разрешены:

    • MRR, NO_MRR: Включите или отключите MRR для указанных таблиц или индексов. Подсказки MRR применяются только к InnoDB и MyISAM.

    • NO_ICP: Отключите ICP для указанных таблиц или индексов. По умолчанию ICP стратегия оптимизации кандидата, таким образом нет никакой подсказки для того, чтобы включить ее.
    • NO_RANGE_OPTIMIZATION: Отключите индексный доступ диапазона для указанных таблиц или индексов. Эта подсказка также отключает Index Merge и Loose Index Scan для таблиц или индексов. По умолчанию доступ диапазона стратегия оптимизации кандидата, таким образом нет никакой подсказки для того, чтобы включить ее.

      Эта подсказка может быть полезной, когда число диапазонов может быть высоким, и оптимизация диапазона потребовала бы многих ресурсов.

  • tbl_name: Таблица, к которой применяется подсказка.
  • index_name: Название индексирования в названной таблице. Подсказка относится ко всем индексам, которые она называет. Если подсказка не называет индекс, она относится ко всем индексам в таблице.

    Чтобы обратиться к первичному ключу, используйте имя PRIMARY. Чтобы посмотреть имена индексов, используйте SHOW INDEX.

  • query_block_name: Блок запроса, к которому применяется подсказка. Если подсказка не включает @query_block_name, она относится к блоку запроса, в котором она происходит. Для формата tbl_name@query_block_name подсказка относится к названной таблице в названном блоке запроса. Чтобы назначить имя блоку запроса, см. сюда.

Примеры:

SELECT /*+ MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3;
SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1
       FROM t3 WHERE f1 > 30 AND f1 < 33;
INSERT INTO t3(f1, f2, f3)
       (SELECT /*+ NO_ICP(t2) */ t2.f1, t2.f2, t2.f3 FROM t1,t2
        WHERE t1.f1=t2.f1 AND t2.f2 BETWEEN t1.f1 AND t1.f2 AND
        t2.f2 + 1 >= t1.f1 + 1);

Подсказки оптимизатора подзапросов

Подсказки оптимизатора подзапросов указывают, использовать ли преобразования полусоединения и которые стратегии разрешить, когда полусоединения не используются, использовать ли материализацию подзапроса или преобразование IN-to-EXISTS. См. раздел 9.2.1.18.

Синтаксис подсказок стратегии полусоединения такой:

hint_name([@query_block_name]
[strategy [, strategy] ...])

Синтаксис ссылается на эти термины:

  • hint_name: Эти имена подсказки разрешены:

    • SEMIJOIN, NO_SEMIJOIN: Включите или отключите названные стратегии полусоединения.

  • strategy: Стратегия полусоединения, которая будет включена или отключена. Эти имена стратегии разрешены: DUPSWEEDOUT, FIRSTMATCH, LOOSESCAN, MATERIALIZATION.

    Для SEMIJOIN(), если никакие стратегии не называют, полусоединение используются, если возможн, основаннон на стратегиях, включенных согласно optimizer_switch. Если стратегии называют, но неподходящие для запроса, используется DUPSWEEDOUT.

    Для NO_SEMIJOIN(), если если никакие стратегии не называют, полусоединение не используются. Если стратегии называют, которые исключают все применимые стратегии запроса, используются DUPSWEEDOUT.

Если один подзапрос вложен в пределах другого, и оба слиты в полусоединение внешнего запроса, любая спецификация стратегий полусоединения самого внутреннего запроса проигнорирована. SEMIJOIN() и NO_SEMIJOIN() могут все еще использоваться, чтобы включить или отключить преобразования полусоединения для таких вложенных подзапросов.

Если выключена DUPSWEEDOUT, оптимизатор может произвести план запроса, который совсем не оптимален. Это происходит из-за эвристического сокращения во время поиска, которого можно избежать, устанавливая optimizer_prune_level=0.

Примеры:

SELECT /*+ NO_SEMIJOIN(@subq1 FIRSTMATCH, LOOSESCAN) */ * FROM t2
       WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3);
SELECT /*+ SEMIJOIN(@subq1 MATERIALIZATION, DUPSWEEDOUT) */ * FROM t2
       WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3);

Синтаксис подсказок, которые затрагивают, использовать ли материализацию подзапроса или преобразования IN-to-EXISTS:

SUBQUERY([@query_block_name] strategy)

Имя подсказки всегда SUBQUERY.

Для SUBQUERY() разрешены эти значения strategy: INTOEXISTS, MATERIALIZATION.

Примеры:

SELECT id, a IN (SELECT /*+ SUBQUERY(MATERIALIZATION) */ a FROM t1) FROM t2;
SELECT * FROM t2 WHERE t2.a IN (SELECT /*+ SUBQUERY(INTOEXISTS) */ a FROM t1);

Для полусоединения и SUBQUERY() @query_block_name определяет блок запроса, к которому применяется подсказка. Если подсказка не включает @query_block_name, подсказка относится к блоку запроса, в котором она происходит.

Если комментарий подсказки содержит многократные подсказки подзапроса, используется первая. Если есть другие после подсказок этого типа, они производят предупреждение. Следующие подсказки других типов тихо проигнорированы.

Подсказки выполнения запроса

MAX_EXECUTION_TIME() разрешают только для SELECT. Это устанавливает границу N (значение тайм-аута в миллисекундах) на то, сколько времени запросу разрешают выполняться:

MAX_EXECUTION_TIME(N)

Пример с тайм-аутом в 1 секунду (1000 миллисекунд):

SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM t1 INNER JOIN t2 WHERE ...

MAX_EXECUTION_TIME(N) устанавливает тайм-аут выполнения запроса в N миллисекунд. Если эта опция отсутствует или N 0, тайм-аут запроса установлен max_execution_time.

MAX_EXECUTION_TIME() применима следующим образом:

  • Для запросов с многократным SELECT, например, союзы или запросы с подзапросами, MAX_EXECUTION_TIME() относится ко всему запросу и должна появиться после первого SELECT.

  • Это относится к SELECT только для чтения. Запросы, которые не только для чтения, являются теми, которые вызывают сохраненную функцию, которая изменяет данные как побочный эффект.
  • Это не относится к запросам SELECT в сохраненных программах и проигнорировано.

Подсказки оптимизатору для обозначения блоков запроса

На уровне таблицы, индекса и подзапроса определенные блоки разрешают подсказки оптимизатора, которые включают имя как часть их синтаксиса параметра. Чтобы создать эти имена, используйте подсказку QB_NAME(), которая назначает имя блоку запроса, в котором она происходит:

QB_NAME(name)

QB_NAME() могут использоваться, чтобы сделать явным способ, которым запрашивают блоки, к которым относятся другие подсказки. Они также разрешают, чтобы все имена блока были определены в пределах единственного комментария подсказки для более легкого понимания сложных запросов. Рассмотрите следующий запрос:

SELECT ... FROM (SELECT ... FROM (SELECT ... FROM ...)) ...

QB_NAME() назначает имена блокам в запросе:

SELECT /*+ QB_NAME(qb1) */ ...
       FROM (SELECT /*+ QB_NAME(qb2) */ ...
       FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...

Тогда другие подсказки могут использовать те имена, чтобы обратиться к соответствующим блокам запроса:

SELECT /*+ QB_NAME(qb1) MRR(@qb1 t1) BKA(@qb2) NO_MRR(@qb3t1 idx1, id2) */ ...
       FROM (SELECT /*+ QB_NAME(qb2) */ ...
       FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...

Получающийся эффект следующий:

  • MRR(@qb1 t1) относится к таблице t1 в блоке запроса qb1.

  • BKA(@qb2) относится к блоку запроса qb2.
  • NO_MRR(@qb3 t1 idx1, id2) относится к индексам idx1 и idx2 в таблице t1 в блоке запроса qb3.

Имена блока запроса это идентификаторы, они неотступно следуют обычным правилам, какие имена допустимы и как заключить их в кавычки (см. раздел 10.2). Например, имя блока запроса, которое содержит пробелы, должно быть заключено в кавычки, что может быть сделан, например, так:

SELECT /*+ BKA(@`my hint name`) */ ...
       FROM (SELECT /*+ QB_NAME(`my hint name`) */ ...) ...

Если включен режим SQL ANSI_QUOTES, также возможно заключить имена блока запроса в кавычки в пределах двойных кавычек:

SELECT /*+ BKA(@"my hint name") */ ...
       FROM (SELECT /*+ QB_NAME("my hint name") */ ...) ...

9.9.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);

Индексные подсказки применены для каждого контекста в следующем порядке:

  1. {USE|FORCE} INDEX применен, если есть. В противном случае используется набор индексов, определенный оптимизатором.

  2. 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: Сметы оптимизатора для общих операций сервера.

  • engine_cost: Сметы оптимизатора для операций, определенных для особых механизмов хранения.

Таблица server_cost содержит эти столбцы:

  • cost_name

    Название сметы используется в модели стоимости. Имя не является чувствительным к регистру. Если сервер не признает имя стоимости, когда читает эту таблицу, он пишет предупреждение в журнал ошибок.

  • cost_value

    Значение сметы. Если значение не-NULL, сервер использует это в качестве стоимости. Иначе это использует оценку по умолчанию. DBA может изменить смету, обновляя этот столбец. Если сервер находит, что значение стоимости недопустимо (неположительно), когда читает эту таблицу, он пишет предупреждение в журнал ошибок.

    Чтобы переопределить смету по умолчанию (для записи, которая определяет NULL), установите стоимость в не-NULL. Чтобы вернуться к значению по умолчанию, установите значение в NULL. Тогда выполните FLUSH OPTIMIZER_COSTS, чтобы сказать серверу перечитать таблицы.

  • last_update

    Время последнего обновления строки.

  • comment

    Описательный комментарий. DBA может использовать этот столбец, чтобы предоставить информацию о том, почему строка сметы хранит особое значение.

Первичный ключ для server_cost это столбец cost_name, таким образом, невозможно создать многократные записи для любой сметы.

Сервер признает значения cost_name для server_cost:

  • disk_temptable_create_cost (по умолчанию 40.0), disk_temptable_row_cost (1.0).

    Сметы для внутренне составленных временных таблиц, сохраненных в основанном на диске механизме хранения (InnoDB или MyISAM). Увеличение этих значений увеличивает смету использования внутренних временных таблиц и заставляет оптимизатор предпочесть планы запроса с меньшим количеством их использования. Для информации о таких таблицах см. раздел 9.4.4.

    Большие значения по умолчанию для этих дисковых параметров по сравнению со значениями по умолчанию для соответствующих параметров памяти (memory_temptable_create_cost, memory_temptable_row_cost) отражают большую стоимость обработки основанных на диске таблиц.

  • key_compare_cost (по умолчанию 0.1).

    Стоимость сравнения ключей. Увеличение этого значения вызывает план запроса, который сравнивает много ключей, чтобы стать более дорогим. Например, план запроса, который выполняет filesort становится относительно более дорогим по сравнению с планом запроса, который избегает сортировать при использовании индексирования.

  • memory_temptable_create_cost (по умолчанию 2.0), memory_temptable_row_cost (по умолчанию 0.2).

    Сметы для внутренне составленных временных таблиц, сохраненных в MEMORY. Увеличение этих значений увеличивает смету использования внутренних временных таблиц и заставляет оптимизатор предпочесть планы запроса с меньшим количеством их использования. Для информации о таких таблицах см. раздел 9.4.4 .

    Меньшие значения по умолчанию для этих параметров по сравнению со значениями по умолчанию для соответствующих дисковых параметров (disk_temptable_create_cost, disk_temptable_row_cost) отражают меньшую стоимость обработки основанных на памяти таблиц.

  • row_evaluate_cost (по умолчанию 0.2).

    Стоимость оценки условий. Увеличение этого значения вызывает план запроса, который исследует много строк, чтобы стать более дорогим по сравнению с планом запроса, который исследует меньше строк. Например, сканирование таблицы становится относительно более дорогим по сравнению с просмотром диапазона, который читает меньше строк.

Таблица engine_cost содержит эти столбцы:

  • engine_name

    Название механизма хранения, к которому применяется эта смета. Имя не является чувствительным к регистру. Если значение default, это относится ко всем механизмам хранения, у которых нет никакой собственной записи. Если сервер не признает имя механизма, когда читает эту таблицу, он пишет предупреждение журналу ошибок.

  • device_type

    Тип устройства, к которому применяется эта смета. Столбец предназначен для того, чтобы определить различные сметы для различных типов устройства хранения данных, таких как жесткие диски против SSD. В настоящее время эта информация не используется, 0 единственное разрешенное значение.

  • cost_name

    То же самое, как в таблице server_cost.

  • cost_value

    То же самое, как в таблице server_cost.

  • last_update

    То же самое, как в таблице server_cost.

  • comment

    То же самое, как в таблице server_cost.

Первичный ключ для engine_cost это кортеж, включающий (cost_name, engine_name, device_type), таким образом, невозможно создать многократные записи для любой комбинации значений в тех столбцах.

Сервер признает эти значения cost_name для engine_cost:

  • io_block_read_cost (по умолчанию 1.0)

    Стоимость чтения индексирования или блока данных с диска. Увеличение этого значения вызывает план запроса, который читает много дисковых блоков, чтобы стать более дорогим по сравнению с планом запроса, который читает меньше дисковых блоков. Например, сканирование таблицы становится относительно более дорогим по сравнению с просмотром диапазона, который читает меньше блоков.

  • memory_block_read_cost (по умолчанию 1.0)

    Подобно io_block_read_cost, но представляет стоимость чтения индексирования или блока данных из буфера базы данных в памяти.

Произведение изменений в базе данных модели стоимости

Для DBA, кто хочет изменить параметры модели стоимости, попытайтесь удвоить или разделить на два значение и измерить эффект.

Изменения io_block_read_cost и memory_block_read_cost, наиболее вероятно, приведут к стоящим результатам. Эти значения параметра позволяют моделям стоимости для методов доступа к данным принять во внимание затраты чтения информации из различных источников, то есть, стоимость чтения информации с диска против чтения информации уже в буфере памяти. Например, при прочих равных условиях, установка io_block_read_cost к значению, больше чем memory_block_read_cost заставляет оптимизатор предпочитать планы запроса, которые читают информацию, уже имеющуюся в памяти, планам, которые должны читать с диска.

Этот пример показывает, как изменить значение по умолчанию для io_block_read_cost:

UPDATE mysql.engine_cost SET cost_value = 2.0
       WHERE cost_name = 'io_block_read_cost';
FLUSH OPTIMIZER_COSTS;

Этот пример показывает, как изменить значение io_block_read_cost только для InnoDB:

INSERT INTO mysql.engine_cost
       VALUES ('InnoDB', 0, 'io_block_read_cost', 3.0,
               CURRENT_TIMESTAMP, 'Using a slower disk for InnoDB');
FLUSH OPTIMIZER_COSTS;

9.9.6. Статистика оптимизатора

Таблица column_stats базы данных mysql разработана, чтобы сохранить статистику о значениях столбцов.

В настоящее время оптимизатор еще не консультируется с column_stats в ходе выполнения запроса.

У таблицы column_stats есть эти характеристики:

  • Таблица содержит статистику для столбцов всех типов данных, кроме типов геометрии (пространственные данные) и JSON.

  • Эта таблица делает статистику столбца постоянной так, чтобы они не были созданы каждый раз, когда сервер запускается.
  • Таблица использует InnoDB.
  • Таблица подвергается репликации.
  • Это предназначено, что обновления column_stats были выполнены сервером, не пользователями.

У таблицы column_stats есть эти столбцы:

  • database_name, table_name, column_name: Названия базы данных, таблицы и столбца, для которого применяются статистические данные. Эти имена формируют первичный ключ для строк в column_stats.

  • histogram: Значение JSON , описывающее статистику для столбца, сохраненного как гистограмма.

column_stats использует JSON, чтобы разрешить гибкость в представлении статистики столбца. Статистические данные для столбца принимают форму гистограммы, содержащей buckets для частей диапазона значений, сохраненных в столбце.

9.10. Буферизация и кэширование

MySQL использует несколько стратегий, которые кэшируют в памяти информацию, чтобы увеличить производительность.

9.10.1. Оптимизация буферного пула InnoDB

InnoDB поддерживает область хранения, названную буферным пулом для того, чтобы кэшировать данные и индексы в памяти. Знание, как работает пул и использование его в своих интересах, чтобы сохранить данные, к которым часто получают доступ, в памяти, это важный аспект настройки MySQL.

Для объяснения внутренних работ пула InnoDB см. раздел 16.6.3.1.

Для дополнительной конфигурации пула см. эти разделы:

9.10.2. Ключевой кэш MyISAM

Чтобы минимизировать дисковый ввод/вывод, MyISAM эксплуатирует стратегию, которая используется многими системами управления базой данных. Это использует механизм кэша, чтобы сохранить табличные блоки, к которым наиболее часто получают доступ, в памяти:

  • Для индексных блоков специальная структура, названная ключевым кэшем (или ключевым буфером) поддержана. Структура содержит много буферов блоков, где наиболее используемые индексные блоки помещены.

  • Для блоков данных MySQL не использует специального кэша. Вместо этого это полагается на родной кэш файловой системы операционной системы.

Этот раздел сначала описывает основную работу ключевого кэша 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).

Запрос не может кэшироваться, если он содержит какую-либо из функций, показанных в следующей таблице.

AES_DECRYPT() AES_ENCRYPT() BENCHMARK()
CONNECTION_ID() CONVERT_TZ() CURDATE()
CURRENT_DATE() CURRENT_TIME() CURRENT_TIMESTAMP()
CURRENT_USER() CURTIME() DATABASE()
ENCRYPT() with one parameter FOUND_ROWS() GET_LOCK()
IS_FREE_LOCK() IS_USED_LOCK() LAST_INSERT_ID()
LOAD_FILE() MASTER_POS_WAIT() NOW()
PASSWORD() RAND() RANDOM_BYTES()
RELEASE_ALL_LOCKS() RELEASE_LOCK() SLEEP()
SYSDATE() UNIX_TIMESTAMP() with no parameters USER()
UUID() UUID_SHORT()

Запрос также не кэшируется при этих условиях:

  • Это обращается к определяемым пользователем функциям (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 влияет, как она работает. Эта переменная может быть установлена в следующие значения:

  • 0 или OFF предотвращает кэширование или извлечение кэшируемых результатов.

  • 1 или ON позволяет кэшировать кроме тех запросов, которые начинаются с SELECT SQL_NO_CACHE.
  • 2 или DEMAND включает кэширование причин только тех запросов, которые начинаются с SELECT SQL_CACHE.

Если query_cache_size = 0, Вы должны также установить query_cache_type в 0. В этом случае сервер не приобретает mutex для кэша запроса вообще, что означает, что кэш запроса не может быть включен во время выполнения.

Установка GLOBAL query_cache_type определяет поведение кэша запроса для всех клиентов, которые соединяются после того, как изменение произведено. Отдельные клиенты могут управлять поведением кэша для своего собственного соединения, устанавливая SESSION query_cache_type . Например, клиент может отключить использование кэша запроса для его собственных запросов так:

mysql> SET SESSION query_cache_type = OFF;

Если Вы устанавливаете query_cache_type при запуске сервера (а не во время выполнения с помощью SET), только числовые значения разрешены.

Чтобы управлять максимальным размером отдельных результатов запроса, которые могут кэшироваться, query_cache_limit . Значение по умолчанию составляет 1 МБ.

Бойтесь устанавливать слишком большой размер кэша. Из-за потребности потоков в блокировке кэша во время обновлений, Вы можете видеть проблемы блокировки с очень большим кэшем.

Вы можете установить максимальный размер, который может быть определен для кэша запроса во время выполнения с помощью SET при использовании --maximum-query_cache_size=32M в командной строке или в конфигурационном файле.

Когда запрос должен кэшироваться, его результат (данные, посланные клиенту), сохранен в кэше запроса во время извлечения результата. Поэтому данные обычно не обрабатываются в одном большом куске. Кэш запроса выделяет блоки для того, чтобы хранить эти данные по требованию, так что когда один блок заполнен, новый блок выделен. Поскольку работа распределения памяти является дорогостоящей, кэш запроса выделяет блоки с минимальным размером, заданным query_cache_min_res_unit. Когда запрос выполнен, последний блок результата обрезан к фактическому размеру данных так, чтобы неиспользованная память была освобождена. В зависимости от типов запросов, которые Ваш сервер выполняет, Вы могли бы счесть полезным настроить значение query_cache_min_res_unit:

  • Значение по умолчанию query_cache_min_res_unit 4KB. Это должно быть достаточным для большинства случаев.

  • Если у Вас есть много запросов с маленькими результатами, размер блока по умолчанию может привести к фрагментации памяти, как обозначено большим количеством свободных блоков. Фрагментация может вынудить кэш запроса сократить (удалять) запросы из кэша из-за нехватки памяти. В этом случае уменьшите значение query_cache_min_res_unit. Число свободных блоков и запросов, удаленных из-за сокращения, дано значениями Qcache_free_blocks и Qcache_lowmem_prunes.
  • Если у большинства Ваших запросов есть большие результаты (проверьте Qcache_total_blocks и Qcache_queries_in_cache), Вы можете увеличить работу, увеличивая query_cache_min_res_unit. Однако, делайте все возможное не сделать это слишком большим (см. предыдущий элемент).

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, разрешая только одному сеансу обновить те таблицы за один раз. Этот уровень блокировки делает эти механизмы хранения более подходящими для однопользовательских приложений, только для чтения или чтения главным образом.

Эти механизмы хранения избегают тупиков, всегда прося все необходимые блокировки сразу в начале запроса и всегда блокируя таблицы в том же самом порядке. Проблема состоит в том, что эта стратегия уменьшает параллелизм: другие сеансы, которые хотят изменить таблицу, должны ждать до окончания запроса изменения данных.

Преимущества блокировки на уровне таблицы:

  • Относительно маленькая требуемая память (блокировка строки требует памяти на строку или группу заблокированных строк).

  • Быстро, когда используется на значительной части таблицы, потому что только единственная блокировка вовлечена.
  • Быстро, если Вы часто делаете операции GROUP BY на значительной части данных или должны часто просматривать всю таблицу.

MySQL допускает, что таблица блокируется на запись так:

  1. Если нет блокировок на таблице, поместить блокировку.

  2. Иначе вставить запрос блокировки в очередь блокировки.

MySQL допускает, что таблица блокируется на чтение так:

  1. Если нет блокировки записи на таблице, поместить блокировку на чтение.

  2. Иначе поместить запрос блокировки в очередь блокировки чтения.

Табличным обновлениям дают более высокий приоритет, чем табличным извлечениям. Поэтому, когда блокировка снята, блокировка сделана доступной для запросов в очереди записи, а уже потом в очереди чтения. Это гарантирует, что обновления таблицы не виснут, даже когда там сложный 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;

Выбор типа блокировки

Вообще, табличные блокировки превосходят блокировки на уровне строки в следующих случаях:

  • Большинство запросов для таблицы это чтение.

  • Запросы для таблицы это соединение чтений и записей, где обновления или удаления для единственной строки, которая может быть принесена с одним ключевым чтением:
    UPDATE tbl_name SET column=value
           WHERE unique_key_col=key_value;
    DELETE FROM tbl_name
           WHERE unique_key_col=key_value;
    
  • SELECT с параллельным INSERT и очень немногими UPDATE или DELETE.
  • Много просмотров или GROUP BY на всей таблице без любых записей.

С высокоуровневыми блокировками Вы можете более легко настроить приложения, поддерживая блокировки различных типов, потому что издержки блокировки меньше, чем для блокировок на уровне строки.

Опции кроме блокировки на уровне строки:

  • Versioning (такой, как используемый в MySQL для параллельных вставок), где возможно иметь много чтений с одной записью. Это означает, что база данных или таблица поддерживают другие представления для данных в зависимости от того, когда доступ начинается. Другие распространенные слова для этого time travel, copy on write или copy on demand.

  • Копия по требованию во многих случаях превосходит блокировку на уровне строки. Однако, в худшем случае, это может использовать намного больше памяти, чем использование нормальных блокировок.
  • Вместо того, чтобы использовать блокировки на уровне строки, Вы можете использовать блокировки уровня приложения, такие как обеспеченные функциями GET_LOCK() и RELEASE_LOCK() в MySQL. Это консультативные блокировки, таким образом, они работают только с приложениями, которые сотрудничают друг с другом. См. раздел 13.18.

9.11.2. Табличные проблемы блокировки

InnoDB использует блокировку на уровне строки так, чтобы многократные сеансы и приложения могли читать из и писать в ту же самую таблицу одновременно, не заставляя друг друга ждать. Для этого механизма хранения избегайте использования LOCK TABLES, потому что это не предлагает дополнительной защиты, но вместо этого уменьшает параллелизм. Автоматическая блокировка на уровне строки делает эти таблицы подходящими для Ваших самых занятых баз данных с Вашими самыми важными данными, также упрощая логику приложения, так как Вы не должны заблокировать таблицы.

MySQL использует табличную блокировку (вместо страницы, строки или блокировки столбца) для всех механизмов хранения, кроме InnoDB. У операций самой блокировки нет многих издержек. Но потому что только один сеанс может писать таблицу в любой момент, для лучшей работы с этими другими механизмами хранения, используйте их прежде всего для таблиц, которые запрашиваются часто и редко обновляются.

Исполнительное одобрение соображений InnoDB

Выбирая, создать ли таблицу InnoDB, имейте в виду следующие недостатки табличной блокировки:

  • Табличная блокировка позволяет многим сеансам читать из таблицы в то же самое время, но если сеанс хочет писать таблицу, это должно сначала получить эксклюзивный доступ, означая, что этому, возможно, придется ждать других сеансов, чтобы закончить с таблицей сначала. Во время обновления должны ждать все другие сеансы, которые хотят получить доступ к этой особой таблице, пока обновление не сделано.

  • SELECT, который занимает много времени, препятствует тому, чтобы другие сеансы обновили таблицу тем временем, заставляя другие сеансы казаться медленными или безразличными. В то время как сеанс ждет, чтобы получить эксклюзивный доступ к таблице для обновлений, другие сеансы, создавшие запрос SELECT, будут стоять в очереди позади этого, уменьшая параллелизм даже для сеансов только для чтения.

Обходные решения для проблем блокировки

Следующие элементы описывают некоторые способы избежать или уменьшить проблемы, вызванные табличной блокировкой:

  • Рассмотрите переключение таблицы к InnoDB с помощью CREATE TABLE ... ENGINE=INNODB при создании или через ALTER TABLE ... ENGINE=INNODB для существующей таблицы. См. главу 16.

  • Оптимизируйте SELECT, чтобы работать быстрее, чтобы они заблокировали таблицы в течение более короткого времени. Вам, возможно, придется создать некоторые сводные таблицы, чтобы сделать это.
  • Запустите mysqld с --low-priority-updates. Для механизмов хранения, которые используют только блокировку на уровне таблицы (MyISAM, MEMORY и MERGE), это дает всем запросам обновления (изменения) таблицы более низкий приоритет, чем SELECT. В этом случае второй SELECT в предыдущем скрипте выполнился бы перед UPDATE и не ждал бы завершения первого SELECT.
  • Чтобы определить, что все обновления, выпущенные в определенном соединении, должны быть сделаны с низким приоритетом, установите low_priority_updates в 1.
  • Чтобы дать определенному INSERT , UPDATE или DELETE более низкий приоритет, используйте параметр LOW_PRIORITY.
  • Чтобы дать определенному SELECT более высокий приоритет, используйте параметр HIGH_PRIORITY, см. раздел 14.2.9.
  • Запустите mysqld с низким значением для max_write_lock_count , чтобы вынудить MySQL временно поднять приоритет всех SELECT, которые ждут таблицы после определенного числа вставок к таблице. Это разрешает блокировки READ после определенного числа блокировок WRITE.
  • Если у Вас есть проблемы с INSERT в сочетании с SELECT, рассмотрите переключение на MyISAM, которые поддерживают параллельные SELECT и INSERT, см. раздел 9.11.3.
  • Если у Вас есть проблемы с SELECT в сочетании с DELETE, опция LIMIT в DELETE может помочь, см. раздел 14.2.2.
  • Испольуйте SQL_BUFFER_RESULT с SELECT. Это может помочь сделать продолжительность табличных блокировок короче. См. раздел 14.2.9.
  • Разделение табличного содержания на отдельные таблицы может помочь, позволяя запросам работать в одной таблице в то время, как обновления ограничены столбцами в иной таблице.
  • Вы можете изменить код блокировки в mysys/thr_lock.c, чтобы использовать единственную очередь. В этом случае у блокировок чтения и записи будет тот же самый приоритет, который мог бы помочь некоторым приложениям.

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, Вы должны гарантировать, что следующие условия удовлетворены:

  • Не используйте кэш запроса для запросов, которые используют таблицы, которые обновлены другим процессом.

  • Не запускайте сервер с опцией --delay-key-write=ALL или используйте опцию таблицы DELAY_KEY_WRITE=1 для любых совместно используемых таблиц. Иначе может произойти повреждение индекса.

Самый легкий способ удовлетворить эти условия состоит в том, чтобы всегда использовать --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 , чтобы удалить или переименовать файл за пределами каталога данных.

Эти табличные операции для символьной ссылки не поддержаны:

  • ALTER TABLE игнорирует DATA DIRECTORY и INDEX DIRECTORY.

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. Эта команда требует административных привилегий.

  1. Перейдите в каталог данных:

    C:\> cd \path\to\datadir
    
  2. В каталоге данных создайте ссылку 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.

    Размер буферного пула важен для системной работы.

    • Как правило, рекомендуют сконфигурировать innodb_buffer_pool_size к 50-75 процентам системной памяти.

    • InnoDB выделяет память для всего буферного пула при запуске сервера. Распределение памяти выполнено malloc(). Буферный размер пула определен опцией innodb_buffer_pool_size. innodb_buffer_pool_size может быть сконфигурирован динамически, в то время как сервер работает. Для получения дополнительной информации см. раздел 16.6.3.2.
    • На системах с большим объемом памяти Вы можете улучшить параллелизм, деля буферный пул на многократные экземпляры. Число экземпляров буферного пула определено опцией innodb_buffer_pool_instances.
    • Буферный пул, который является слишком небольшим, может вызвать чрезмерное взбалтывание, поскольку страницы сбрасываются из буферного пула только, чтобы загрузиться снова немного позже.
    • Буферный пул, который является слишком большим, может вызвать свопинг из-за соревнования за память.

  • 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 это конкретный инструмент.

  1. Чтобы рассмотреть доступные инструменты памяти 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 и другие.

  2. Чтобы включить инструменты памяти, добавьте правило 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    |
    ...
    
  3. В этом примере инструментальные данные о памяти запрошены в таблице 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.

Сервер обрабатывает записи в кэше узла так:

  1. Когда первое соединение клиента TCP достигает сервера от данного IP-адреса, новый вход создается, чтобы сделать запись IP клиента, имени хоста и флага проверки допустимости поиска клиента. Первоначально имя хоста установлено в NULL и флаг ложен. Этот вход также используется для последующих соединений клиента от того же самого IP.

  2. Если флаг проверки допустимости для входа IP клиента ложен, сервер делает попытку разрешения IP к имени хоста. Если это успешно, имя хоста обновлено с решенным именем хоста, и флаг проверки допустимости установлен в истину. Если разрешение неудачно, предпринятые меры зависят от того, является ли ошибка постоянной или переходной. Для постоянных отказов остается имя хоста NULL, а флаг проверки допустимости установлен в истину. Для переходных отказов имя хоста и флаг проверки допустимости остаются неизменными. Другая попытка разрешения DNS происходит в следующий раз, когда клиент соединяется от этого IP.
  3. Если ошибка происходит, обрабатывая поступающее соединение клиента от данного IP-адреса, сервер обновляет соответствующие счетчики во входе для этого IP. Для описания зарегистрированных ошибок см. раздел 23.9.16.1.

Сервер выполняет разрешение имени хоста, используя безопасные для потока вызовы gethostbyaddr_r() и gethostbyname_r(), если операционная система поддерживает их. Иначе поток, выполняющий поиск, блокирует mutex и вызывает gethostbyaddr() и gethostbyname(). В этом случае никакой другой поток не может решить имена хоста, которые не находятся в кэше узла, пока поток, держащий блокировку mutex, не выпускает ее.

Сервер использует кэш узла в нескольких целях:

  • Кэшируя результаты поисков IP к имени хоста, сервер избегает делать поиск DNS для каждого соединения клиента. Вместо этого для данного узла это должно выполнить поиск только для первого соединения от того узла.

  • Кэш содержит информацию об ошибках, которые происходят во время процесса соединения. Некоторые ошибки считают блокировками. Если слишком многие из них происходят последовательно от данного узла без успешного соединения, сервер блокирует дальнейшие соединения от того узла. max_connect_errors определяет число разрешенных ошибок прежде, чем блокировка произойдет. См. раздел B.5.2.5.

Чтобы открыть заблокированные узлы, сбросьте кэш узла командой FLUSH HOSTS или mysqladmin flush-hosts.

Для заблокированного узла возможно стать открытым даже без FLUSH HOSTS, если деятельность от других узлов произошла, начиная с последней попытки соединения от заблокированного узла. Это может произойти, потому что сервер отказывается от последнего использованного входа кэша, чтобы создать место для нового входа, если кэш полон, когда соединение прибывает от IP клиента не в кэше. Если вход, от которого отказываются, был для заблокированного узла, тот узел становится открытым.

Кэш узла включен по умолчанию. Чтобы отключить это, установите host_cache_size в 0 при запуске сервера или во время выполнения.

Чтобы отключить поиски имени хоста DNS, запустите сервер с --skip-name-resolve. В этом случае сервер использует только IP-адреса, но не имена хоста, чтобы соответствовать строкам в таблицах привилегий. Только учетные записи, определенные в тех таблицах, используя IP-адреса, могут использоваться. Убедитесь, что учетная запись, которая определяет IP-адрес существует, или Вы можете быть не в состоянии соединиться.

Если у Вас есть очень медленный DNS и много узлов, Вы могли бы быть в состоянии улучшить работу, отключая поиски DNS с --skip-name-resolve или увеличивая значение host_cache_size, чтобы сделать кэш узла больше.

Чтобы отвергнуть соединения TCP/IP полностью, запустите сервер с опцией --skip-networking.

Некоторые ошибки соединения не связаны с соединениями TCP, происходят очень рано в процессе соединения (даже прежде, чем IP-адрес будет известен), или не являются определенными для любого особого IP-адреса (такие, как условия памяти). Для информации об этих ошибках проверьте Connection_errors_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/.

Это очень характерно для проблемы проявляться только, когда система очень в большой степени загружена. У нас было много клиентов, которые связываются с нами, когда они имеют (проверенную) систему в производстве и столкнулись с проблемами загрузки. В большинстве случаев проблемы, оказывается, происходят из-за проблем основного проектирования баз данных (например, сканирование таблицы не хорошо при высокой загрузке) или проблемы с операционной системой или библиотеками. Большую часть времени эти проблемы было бы намного легче решить, если бы системы еще не работали.

Чтобы избежать проблем, определите эффективность своего целого приложения при худшей загрузке:

  • mysqlslap может быть полезной для того, чтобы моделировать высокую загрузку, произведенную многими клиентами, выпускающими запросы одновременно. См. раздел 5.5.8.

  • Вы можете также попытаться определить эффективность пакетов, таких как SysBench и DBT2, доступных на https://launchpad.net/sysbench и http://osdldbt.sourceforge.net/#dbt2.

Эти программы или пакеты могут положить систему, так что надо убедиться, что использовали их только на Ваших системах развития.

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 следующие операции могли происходить:

    • Удаление записи кэша запроса после того, как данные в таблице изменены.

    • Запись события в двоичный журнал.
    • Освобождение буферов памяти, включая blob.

  • Killed

    Кто-то послал потоку запрос KILL и это должно прерваться в следующий раз, когда это проверяет флаг уничтожения. Флаг проверен в каждом главном цикле в MySQL, но в некоторых случаях могло бы все еще потребоваться короткое время для потока, чтобы завершиться. Если поток заблокирован некоторым другим потоком, уничтожение вступает в силу, как только другой поток выпускает свою блокировку.

  • Locking system tables

    Поток пытается заблокировать системную таблицу (например, часовой пояс или таблицу журнала).

  • logging slow query

    Поток пишет запрос в журнал медленного запроса.

  • login

    Начальное состояние для потока соединения, пока клиент не был заверен успешно.

  • manage keys

    Сервер включает или отключает индекс таблицы.

  • NULL

    Это состояние используется для SHOW PROCESSLIST.

  • Opening system tables

    Поток пытается открыть системную таблицу (например, часовой пояс или таблицу журнала).

  • Opening tables

    Поток пытается открыть таблицу. Это должна быть очень быстрая процедура, если что-то не предотвращает открытие. Например, ALTER TABLE или LOCK TABLE может предотвратить открытие таблицы, пока запрос не закончено. Также стоит проверить что Ваше значение table_open_cache является достаточно большим.

    Для системных таблиц вместо этого используется Opening system tables.

  • optimizing

    Сервер выполняет начальную оптимизацию для запроса.

  • preparing

    Это состояние происходит во время оптимизации запроса.

  • Purging old relay logs

    Поток удаляет ненужные файлы системного журнала реле.

  • query end

    Это состояние происходит после обработки запроса, но перед freeing items.

  • Receiving from client

    Сервер читает пакет от клиента.

  • Removing duplicates

    Запрос использовал SELECT DISTINCT таким способом, которым MySQL не мог оптимизировать работу на ранней стадии. Из-за этого MySQL требует, чтобы дополнительный этап удалил все дублированные строки прежде, чем послать результат клиенту.

  • removing tmp table

    Поток удаляет внутреннюю временную таблицу после обработки SELECT. Это состояние не используется, если никакая временная таблица не была составлена.

  • rename

    Поток переименовывает таблицу.

  • rename result table

    Поток обрабатывает ALTER TABLE , который составил новую таблицу и переименовывает ее, чтобы заменить оригинальную таблицу.

  • Reopen tables

    Поток получил блокировку для таблицы, но после получения блокировки, которая изменила основную структуру таблицы. Это освободило блокировку, закрыло таблицу и пытается вновь открыть.

  • Repair by sorting

    Код ремонта использует сортировку, чтобы создать индексы.

  • preparing for alter table

    Сервер готовится выполнять оперативное ALTER TABLE.

  • Repair done

    Поток завершил мультипоточный ремонт для MyISAM.

  • Repair with keycache

    Код ремонта использует создание ключей один за другим через ключевой кэш. Это намного медленнее, чем Repair by sorting.

  • Rolling back

    Поток удаляет транзакцию.

  • Saving state

    Для операций MyISAM, таких как ремонт или анализ, поток сохраняет новое табличное состояние в заголовок файла .MYI. Состояние включает такую информацию, как число строк, счетчик AUTO_INCREMENT и ключевые распределения.

  • Searching rows for update

    Поток делает первую фазу, чтобы найти все строки соответствия прежде, чем обновить их. Это должно быть сделано, если UPDATE изменяет индексирование, которое используется, чтобы найти вовлеченные строки.

  • Sending data

    Поток читает и обрабатывает строки для SELECT и посылает данные клиенту. Поскольку операции, происходящие во время этого состояния, имеют тенденцию выполнять большое количество дискового доступа (чтения), это часто самое продолжительное состояние данного запроса.

  • Sending to client

    Сервер пишет пакет клиенту.

  • setup

    Поток начинает ALTER TABLE.

  • Sorting for group

    Поток делает сортировку, чтобы удовлетворить GROUP BY.

  • Sorting for order

    Поток делает сортировку, чтобы удовлетворить ORDER BY.

  • Sorting index

    Поток сортирует индексные страницы для более эффективного доступа во время оптимизации таблицы MyISAM.

  • Sorting result

    Для SELECT это подобно Creating sort index, но не для временных таблиц.

  • statistics

    Сервер вычисляет статистику, чтобы развить план выполнения запроса. Если поток находится в этом статусе в течение долгого времени, сервер, вероятно, занят выполнением другой работы.

  • System lock

    Поток вызвал mysql_lock_tables(), и состояние потока не было обновлено с тех пор. Это очень общее состояние, которое может произойти по многим причинам.

    Например, поток собирается просить или ждет внутренней или внешней системной блокировки для таблицы. Это может произойти, когда InnoDB ждет блокировки на уровне таблицы во время выполнения LOCK TABLES. Если это состояние вызывается запросами о внешних блокировках, и Вы не используете многократные mysqld , которые получают доступ к тем же самым таблицам MyISAM, Вы можете отключить внешние системные блокировки с помощью --skip-external-locking. Однако, внешняя блокировка отключена по умолчанию, таким образом, вероятно, что эта опция не будет иметь никакого эффекта. Для SHOW PROFILE это состояние означает, что поток просит блокировку (не ждет ее).

    Для системных таблиц вместо этого используется Locking system tables.

  • update

    Поток готовится начинать обновлять таблицу.

  • Updating

    Поток ищет строки, чтобы обновить и обновляет их.

  • updating main table

    Сервер выполняет первую часть многотабличного обновления. Это обновляет только первую таблицу и сохраняет столбцы и смещения, которые будут использоваться для того, чтобы обновить другие таблицы.

  • updating reference tables

    Сервер выполняет вторую часть многотабличного обновления и обновляет соответствующие строки других таблиц.

  • User lock

    Поток собирается просить или ждет консультативной блокировки, которую требуют через GET_LOCK() . Для SHOW PROFILE это состояние означает, что поток просит блокировку (не ждет ее).

  • User sleep

    Поток вызвал SLEEP().

  • Waiting for commit lock

    FLUSH TABLES WITH READ LOCK ждет передачи блокировки.

  • Waiting for global read lock

    FLUSH TABLES WITH READ LOCK ждет глобальной блокировки чтения или установки глобальной переменной read_only.

  • Waiting for tables

    Поток получил уведомление, что глубинная структура для таблицы изменилась и это должно вновь открыть таблицу, чтобы получить новую структуру. Однако, чтобы вновь открыть таблицу, это должно ждать, пока все другие потоки не закрыли рассматриваемую таблицу.

    Это уведомление имеет место, если другой поток использовал FLUSH TABLES или один из следующих запросов о рассматриваемой таблице: FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE или OPTIMIZE TABLE.

  • Waiting for table flush

    Поток выполняет FLUSH TABLES и ждет всех потоков, чтобы закрыть их таблицы, или поток получил уведомление, что глубинная структура для таблицы изменилась, и это должно вновь открыть таблицу, чтобы получить новую структуру. Однако, чтобы вновь открыть таблицу, это должно ждать, пока все другие потоки не закрыли рассматриваемую таблицу.

    Это уведомление имеет место, если другой поток использовал FLUSH TABLES или один из следующих запросов о рассматриваемой таблице: FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE или OPTIMIZE TABLE.

  • Waiting for lock_type lock

    Сервер ждет, чтобы приобрести блокировку THR_LOCK или блокировку от подсистемы блокировки метаданных, где lock_type указывает на тип блокировки.

    Это состояние указывает на ожидаемую THR_LOCK:

    • Waiting for table level lock

    Эти состояния указывают на ожидание блокировки метаданных:

    • Waiting for event metadata lock

    • Waiting for global read lock
    • Waiting for schema metadata lock
    • Waiting for stored function metadatalock
    • Waiting for stored procedure metadata lock
    • Waiting for table metadata lock
    • Waiting for trigger metadata lock

    Для информации о табличных индикаторах блокировки см. раздел 9.11.1. Для информации о блокировке метаданных см. раздел 9.11.4. Чтобы видеть, какие блокировки блокируют запросы блокировки, используйте таблицы блокировки Performance Schema, описанные в разделе 23.9.12.

  • Waiting on cond

    Состояние, в котором поток ждет условия. Никакая определенная информация не доступна.

  • Writing to net

    Сервер пишет пакет в сеть.

9.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

    Очередь планировщика событий пуста, и он спит.

Поиск

 

Найди своих коллег!

Вы можете направить письмо администратору этой странички, Алексею Паутову. mailto:alexey.v.pautov@mail.ru