Глава 9. Оптимизация

Эта глава объясняет, как оптимизировать работу MySQL и обеспечивает примеры. Оптимизация вовлекает конфигурирование, настройку и определение эксплуатационных качеств на нескольких уровнях. В зависимости от Вашей роли (разработчик, DBA или комбинация обоих), Вы могли бы оптимизировать на уровне отдельных запросов SQL, всех приложений, единственного сервера базы данных или многих сетевых серверов базы данных. Иногда Вы можете запланировать заранее работу в то время, как в других случаях Вы могли бы расследовать конфигурацию или кодировать проблему после того, как проблема происходит. Оптимизация центрального процессора и использования памяти может также улучшить масштабируемость, позволяя базе данных обработать большие нагрузки без замедления.

9.1. Краткий обзор оптимизации

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

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

Оптимизация на уровне базы данных

Наиболее важным фактором в создании приложения базы данных является базовая конструкция:

Оптимизация на уровне аппаратных средств

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

Балансирование мобильности и работы

Чтобы использовать ориентируемый на работу на расширения 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

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

9.2.1.2. Как MySQL оптимизирует WHERE

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

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

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

Некоторые примеры запросов, которые очень быстры:

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

Определение условия диапазона для единственной части индекса:

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

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

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

Раздел 9.2.1.3.1 описывает, как оптимизация выполнена, чтобы объединить или устранить интервалы для условий диапазона на единственной части индекса. Аналогичные шаги выполнены для условий диапазона на многих частях индекса.

9.2.1.3.3. Оптимизация диапазона равенства сравнений

Рассмотрите эти выражения, где col_name индексированный столбец:

col_name IN(val1, ...,
valN)

col_name = val1 OR
... OR col_name = valN

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

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

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

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

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

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

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

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

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

9.2.1.3.5. Оптимизация диапазона выражений конструктора строки

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

SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' ));

Ранее для просмотра диапазона было необходимо написать запрос как:

SELECT ... FROM t1 WHERE (col_1 = 'a' AND col_2 = 'b') OR
           (col_1 = 'c' AND col_2 = 'd');

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

См. раздел 9.2.1.20 .

9.2.1.4. Оптимизация слияния индекса

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

В выводе EXPLAIN метод слияния появляется как index_merge в столбце type. В этом случае столбец key содержит список используемых индексов, а key_len содержит список самых длинных ключевых частей для тех индексов.

Примеры:

SELECT * FROM tbl_name
         WHERE key1 = 10 OR
         key2 = 20;
SELECT * FROM tbl_name
         WHERE (key1 = 10 OR
         key2 = 20) AND
         non_key=30;
SELECT * FROM t1, t2 WHERE (t1.key1 IN (1,2) OR
         t1.key2 LIKE 'value%')
         AND t2.key1=t1.some_col;
SELECT * FROM t1, t2 WHERE t1.key1=1 AND
         (t2.key1=t1.some_col OR
         t2.key2=t1.some_col2);

У метода слияния есть несколько алгоритмов доступа (отмечено в поле Extra вывода EXPLAIN ):

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

У алгоритма оптимизации Слияния есть следующие известные недостатки:

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

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

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

Примеры:

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 , и каждое условие одно из следующего:

Примеры:

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

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

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

Условие механизма включено по умолчанию. Чтобы отключить это при запуске сервера, установите optimizer_switch. Например, в файле my.cnf используйте эти строки:

[mysqld]
optimizer_switch=engine_condition_pushdown=off

Во время выполнения включите условие:

SET optimizer_switch='engine_condition_pushdown=off';

Ограничения. Условие механизма подвергается следующим ограничениям:

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

Различия в поведении оптимизатора для использования расширенных индексов могут также быть замечены с SHOW STATUS :

FLUSH TABLE t1;
FLUSH STATUS;
SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01';
SHOW STATUS LIKE 'handler_read%'

Предыдущие запросы включают FLUSH TABLE и FLUSH STATUS для сброса табличного кэша и очистки счетчиков состояния.

Без индексного расширения SHOW STATUS приводит к этому результату:

+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 0     |
| Handler_read_key      | 1     |
| Handler_read_last     | 0     |
| Handler_read_next     | 5     |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 0     |
+-----------------------+-------+

С индексным расширением SHOW STATUS приводит к этому результату. Handler_read_next уменьшается от 5 до 1, указывая на более эффективное использование индексирования:

+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 0     |
| Handler_read_key      | 1     |
| Handler_read_last     | 0     |
| Handler_read_next     | 1     |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 0     |
+-----------------------+-------+

Флаг use_index_extensions в optimizer_switch разрешает управление, принимает ли оптимизатор столбцы первичного ключа во внимание, определяя, как использовать вторичные индексы таблицы InnoDB. По умолчанию use_index_extensions включен. Чтолыб проверить, улучшит ли работу отключение использования расширения, надо использовать этот запрос:

SET optimizer_switch = 'use_index_extensions=off';

Использование расширения подвергается обычным пределам на число ключевых частей в индексировании (16) и максимальную длину ключа (3072 байт).

9.2.1.8. Оптимизация IS NULL

MySQL может выполнить ту же самую оптимизацию на col_name IS NULL, которую может использовать для col_name = constant_value. Например, MySQL может использовать индекс и диапазоны, чтобы искать NULL с IS NULL.

Примеры:

SELECT * FROM tbl_name WHERE
         key_col IS NULL;
SELECT * FROM tbl_name WHERE
         key_col <=> NULL;
SELECT * FROM tbl_name
         WHERE key_col=const1 OR
         key_col=const2 OR
         key_col IS NULL;

Если WHERE включает col_name IS NULL для столбца, который объявлен как NOT NULL, то выражение оптимизировано. Эта оптимизация не происходит в случаях, когда столбец мог бы произвести NULL так или иначе, например, если это прибывает из таблицы на правой стороне LEFT JOIN.

MySQL может также оптимизировать комбинацию col_name = expr OR col_name IS NULL, форма, которая распространена в решенных подзапросах. EXPLAIN показывает ref_or_null, когда эта оптимизация используется.

Эта оптимизация может обработать IS NULL для любой ключевой части.

Некоторые примеры запросов, которые оптимизированы, предполагая, что есть индексирование на столбцах a и b таблицы t2:

SELECT * FROM t1 WHERE t1.a=expr OR t1.a IS NULL;
SELECT * FROM t1, t2 WHERE t1.a=t2.a OR t2.a IS NULL;
SELECT * FROM t1, t2 WHERE (t1.a=t2.a OR t2.a IS NULL) AND t2.b=t1.b;
SELECT * FROM t1, t2 WHERE t1.a=t2.a AND (t2.b=t1.b OR t2.b IS NULL);
SELECT * FROM t1, t2 WHERE (t1.a=t2.a AND t2.a IS NULL AND ...) OR
         (t1.a=t2.a AND t2.a IS NULL AND ...);

ref_or_null выполняет чтение на ссылочном ключе, а затем отдельный поиск строк со значением ключа NULL.

Оптимизация может обработать только один уровень IS NULL. В следующем запросе MySQL использует ключевые поиски только по выражению (t1.a=t2.a AND t2.a IS NULL) и не в состоянии использовать ключевую часть на b:

SELECT * FROM t1, t2 WHERE (t1.a=t2.a AND t2.a IS NULL) OR
         (t1.b=t2.b AND t2.b IS NULL);

9.2.1.9. Оптимизация LEFT JOIN и RIGHT JOIN

MySQL осуществляет A LEFT JOIN B join_condition следующим образом:

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

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

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

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

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

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

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

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

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

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

9.2.1.10. Алгоритмы соединения вложенной петли

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

Алгоритм соединения вложенной петли

Простой алгоритм nested-loop join (NLJ) читает строки из первой таблицы в петле по одной, передавая каждую строку к вложенной петле, которая обрабатывает следующую таблицу в соединении. Этот процесс повторен так много раз, пока там остаются таблицы, к которым присоединятся.

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

Table   Join Type
t1      range
t2      ref
t3      ALL

Если простой алгоритм NLJ используется, соединение обработано так:

for each row in t1 matching range {
  for each row in t2 matching reference key {
    for each row in t3 {
      if row satisfies join conditions,
         send to client
    }
  }
}

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

Алгоритм Block Nested-Loop Join

Алгоритм Block Nested-Loop (BNL) присоединяется к буферизации использования алгоритма чтения строк во внешних петлях, чтобы уменьшить число раз, которое таблицы во внутренних петлях должны быть считаны. Например, если 10 строк считаны в буфер, и буфер передают к следующей внутренней петле, каждое чтение строки во внутренней петле может быть сравнено со всеми 10 строками в буфере.

MySQL использует буферизацию соединения при этих условиях:

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

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

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

(S * C)/join_buffer_size + 1

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

9.2.1.11. Оптимизация Nested Join

Синтаксис для выражений разрешает вложенные соединения. Следующее обсуждение обращается к синтаксису соединения, описанному в разделе 14.2.9.2.

Синтаксис table_factor расширен по сравнению со стандартом SQL. Последний принимает только table_reference , но не список их в паре круглых скобок. Это консервативное расширение, если мы рассматриваем каждую запятую в списке элементов table_reference как эквивалент внутреннему соединению. Например:

SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
         ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)

эквивалентно вот этому:

SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
         ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)

В MySQL CROSS JOIN эквивалентно INNER JOIN (они могут заменить друг друга). В стандартном SQL они не эквивалентны. INNER JOIN используется с ON, иначе используется CROSS JOIN.

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

t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
   ON t1.a=t2.a

преобразовывается в выражение:

(t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3
    ON t2.b=t3.b OR t2.b IS NULL

Все же эти два выражения не эквивалентны. Чтобы видеть это, предположите, что таблицы t1, t2 и t3 имеют следующее состояние:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

и:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

9.2.1.12. Упрощение Outer Join

Табличные выражения в FROM упрощены во многих случаях.

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

(T1, ...) RIGHT JOIN (T2,...) ON P(T1,...,T2,...) =
(T2, ...) LEFT JOIN (T1,...) ON P(T1,...,T2,...)

Все внутренние выражения соединения формы T1 INNER JOIN T2 ON P(T1,T2) заменены списком T1,T2, P(T1,T2) будучи присоединенным как соединенное к WHERE (или к условию соединения встраивания, если есть).

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

Предположите, что у нас есть запрос формы:

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

с R(T2) очень сужающим число соответствия строк от таблицы T2. Если бы мы выполняли запрос, как это, то у оптимизатор не было бы никакого другого выбора, кроме доступа к таблице T1 до T2, что может привести к очень неэффективному плану выполнения.

MySQL преобразовывает такой запрос в запрос без outer join, если условие WHERE отклонено null. Условие называют отклоненным нулем для outer join, если оно оценивается к FALSE или UNKNOWN для любой NULL-дополненной строки для работы.

Таким образом, для этого внешнего соединения:

T1 LEFT JOIN T2 ON T1.A=T2.A

Такие условия отклонены нулем:

T2.B IS NOT NULL,
T2.B > 3,
T2.C <= T1.C,
T2.B < 2 OR T2.C > 1

Такие условия не отклонены нулем:

T2.B IS NULL,
T1.B < 3 OR T2.B IS NOT NULL,
T1.B < 3 OR T2.B > 3

Общие правила для того, чтобы проверить, отклонено ли условие нулем для outer join просты. Условие отклонено нулем в следующих случаях:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

9.2.1.13. Оптимизация мультидиапазонного чтения

Чтение строк, используя просмотр диапазона на вторичном индексе, может привести ко многим случайным дисковым доступам к базовой таблице, когда таблица является большой и не сохранена в кэше механизма хранения. С оптимизацией Disk-Sweep Multi-Range Read (MRR) MySQL пытается сократить количество случайного дискового доступа для просмотров диапазона первым только просмотром индекса и сбором ключей для соответствующих строк. Тогда ключи сортированы, и строки получены из базовой таблицы, используя порядок первичного ключа. Побуждение для Disk-sweep MRR должно сократить количество случайных дисковых доступов и вместо этого достигнуть более последовательного просмотра данных базовой таблицы.

Оптимизация Multi-Range Read обеспечивает эту выгоду:

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

Следующие примеры иллюстрируют, когда оптимизация 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:

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

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

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

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

Если индексирование не используется для ORDER BY, но LIMIT также присутствует, оптимизатор может быть в состоянии избегать использования файла слияния и сортировать строки в памяти. См. раздел 9.2.1.19.

9.2.1.16. Оптимизация GROUP BY

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

Самые важные предварительные условия для того, чтобы использовать индекс для GROUP BY это все ссылочные признаки столбцов GROUP BY от того же самого индекса, и что ключи индекса хранятся в порядке (например, это индекс BTREE, но не HASH). Может ли использование временных таблиц быть заменено индексным доступом, также зависит от того, на которой части индексирования используются в запросе условия, определенные для этих частей и выбранных совокупных функций.

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

В MySQL GROUP BY используется для того, чтобы сортировать, таким образом, сервер может также применить ORDER BY к группировке. Однако, доверие неявному GROUP BY устарело, см. раздел 9.2.1.15.

9.2.1.16.1. Свободный индексный просмотр

Самый эффективный способ обработать GROUP BY, когда индексирование используется, чтобы непосредственно получить группирующиеся столбцы. С этим методом доступа MySQL использует свойство некоторых типов индекса, что ключи упорядочены (например, BTREE). Это свойство включает использование групп поиска в индексировании, не имея необходимость рассматривать все ключи в индексировании, которые удовлетворяют все выражения WHERE. Этот метод доступа рассматривает только фракцию ключей в индексировании, таким образом, это называют свободный просмотр индекса. Когда нет WHERE, свободный просмотр читает столько ключей, сколько имеется групп, что может быть намного меньшим числом, чем все ключи. Если WHERE содержит предикаты диапазона (см. обсуждение range в разделе 9.8.1), свободный просмотр ищет первый ключ каждой группы, которая удовлетворяет условиям диапазона, и снова читает наименее возможное число ключей. Это возможно при следующих условиях:

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

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

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

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

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

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

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) у оптимизатора есть этот выбор:

Для NOT IN (или <>ALL) у оптимизатора есть этот выбор:

Для полученных таблиц (подзапросы в FROM) и ссылок представления у оптимизатора есть этот выбор:

Следующее обсуждение предоставляет больше информации об этих стратегиях оптимизации.

Ограничение UPDATE и DELETE, которые используют подзапрос, чтобы изменить единственную таблицу, это когда оптимизатор не использует полусоединение или оптимизацию подзапроса материализации. Как обходное решение, попытайтесь переписать их как многотабличный UPDATE и DELETE, которые используют соединение, а не подзапрос.

9.2.1.18.1. Оптимизация подзапросов с преобразованиями полусоединения

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

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

SELECT class.class_num, class.class_name
       FROM class INNER JOIN roster
       WHERE class.class_num = roster.class_num;

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

Будем считать, что class_num это primary key в таблице class, подавление дубликатов могло быть достигнуто при использовании SELECT DISTINCT, но это неэффективно, чтобы произвести все строки соответствия сначала только, чтобы устранить дубликаты позже.

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

SELECT class_num, class_name FROM class
       WHERE class_num IN (SELECT class_num FROM roster);

Здесь, оптимизатор может признать, что IN требует, чтобы подзапрос возвратил только один случай каждого классификационного индекса от таблицы roster. В этом случае запрос может быть выполнен как semi-join, то есть, работа, которая возвращает только один случай каждой строки в class, который является соответствующим строкам в roster.

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

В MySQL подзапрос должен удовлетворить эти критерии, которые будут обработаны как полусоединение:

Подзапрос может быть коррелированый или некоррелированый. DISTINCT разрешен, как LIMIT, если также используется и ORDER BY.

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

Каждая из этих стратегий может быть включена с использованием переменной optimizer_switch . Флаг semijoin управляет, используются ли полусоединения. Если это установлено в on, то флаги firstmatch, loosescan, duplicateweedout и materialization включают более гладкое управление разрешенными стратегиями полусоединения. Эти флаги on по умолчанию. См. раздел 9.9.2.

Если стратегия duplicateweedout выключена, она не используется, если все другие применимые стратегии также не отключены.

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

Оптимизатор минимизирует различия в обработке представлений и подзапросов в FROM. Это затрагивает запросы с STRAIGHT_JOIN и представления с подзапросом IN, которые могут быть преобразованы в полусоединение. Следующий запрос иллюстрирует это, потому что изменение в обработке причина изменений в преобразовании, и таким образом применена иная стратегия выполнения:

CREATE VIEW v AS SELECT * FROM t1 WHERE a IN (SELECT b FROM t2);
SELECT STRAIGHT_JOIN * FROM t3 JOIN v ON t3.x = v.a;

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

Использование стратегий полусоединения обозначено в выводе EXPLAIN так:

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

Следующие примеры иллюстрируют как требование для эквивалентности оценки предиката 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);

Следующие ограничения относятся к использованию материализации подзапроса:

Применение 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.

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

Рассмотрите следующий 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 значение не NULL, но подзапрос не производит строку таким образом, что outer_expr = inner_expr. outer_expr IN (SELECT ...) оценивается следующим образом:

В этой ситуации поиск строк с outer_expr = inner_expr больше не действителен. Необходимо искать такие строки, но если ни одна не найдена, также искать строки, где inner_expr = NULL. Примерно говоря, подзапрос может быть преобразован во что-то вроде этого:

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
(outer_expr=inner_expr OR
inner_expr IS NULL))

Потребность оценить дополнительно IS NULL причина того, почему MySQL имеет метод доступа ref_or_null:

mysql> EXPLAIN SELECT outer_expr IN
    ->         (SELECT t2.maybe_null_key FROM t2, t3 WHERE ...)
    ->         FROM t1;
*************************** 1. row ***************************
 id: 1
  select_type: PRIMARY
table: t1
...
*************************** 2. row ***************************
 id: 2
  select_type: DEPENDENT SUBQUERY
table: t2
 type: ref_or_null
possible_keys: maybe_null_key
key: maybe_null_key
key_len: 5
ref: func
 rows: 2
Extra: Using where; Using index
...

У определенных для подзапроса методов доступа unique_subquery и index_subquery также есть версия or NULL .

Дополнительное условие OR ... IS NULL делает выполнение запроса немного более сложным (и некоторая оптимизация в пределах подзапроса становится неподходящей), но вообще это терпимо.

Ситуация намного хуже, когда outer_expr может быть NULL. Согласно интерпретации SQL NULL как неизвестная величина, NULL IN (SELECT inner_expr ...) должен оцениться как:

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

По существу должны быть различные способы выполнить подзапрос в зависимости от значения outer_expr.

Оптимизатор предпочитает согласие SQL скорости, таким образом, это составляет возможность, что outer_expr может быть NULL.

Если outer_expr = NULL, чтобы оценить следующее выражение, необходимо выполнить SELECT, чтобы определить, производит ли это какие-либо строки:

NULL IN (SELECT inner_expr FROM ...
     WHERE subquery_where)

Необходимо выполнить оригинал SELECT без любых продвинутых вниз равенств, упомянутых ранее.

С другой стороны, когда outer_expr не NULL, абсолютно важно, что это сравнение:

outer_expr IN (SELECT inner_expr
FROM ... WHERE subquery_where)

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

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
       outer_expr=inner_expr)

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

outer_expr IN (SELECT inner_expr
FROM ... WHERE subquery_where)

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

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
       trigcond(outer_expr=inner_expr))

Более широко, если сравнение подзапроса основано на нескольких парах внешних и внутренних выражений, преобразование берет это сравнение:

(oe_1, ..., oe_N) IN
(SELECT ie_1, ..., ie_N
        FROM ... WHERE subquery_where)

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

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
       trigcond(oe_1=ie_1) AND
       ... AND trigcond(oe_N=ie_N))

trigcond(X) это специальная функция, которая оценивается к следующим значениям:

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

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

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

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

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

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

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

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

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

Флаг subquery_materialization_cost_based включает управление выбором между материализацией подзапроса и преобразованием подзапроса IN-to-EXISTS. См. раздел 9.9.2.

9.2.1.19. Оптимизация запроса LIMIT

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

MySQL иногда оптимизирует запрос, у которого есть LIMIT row_count, но нет HAVING:

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

Один фактор, который затрагивает план выполнения, LIMIT, так что ORDER BY с или без LIMIT может возвратить строки в различных порядках. Рассмотрите этот запрос, который отсортирован по столбцу category, но недетерминирован относительно столбцов id и rating:

mysql> SELECT * FROM ratings ORDER BY category;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
|  1 | 1        | 4.5    |
|  5 | 1        | 3.2    |
|  3 | 2        | 3.7    |
|  4 | 2        | 3.5    |
|  6 | 2        | 3.5    |
|  2 | 3        | 5.0    |
|  7 | 3        | 2.7    |
+----+----------+--------+

Включение LIMIT может затронуть порядок строк в пределах каждого значения category. Например, это допустимый результат запроса:

mysql> SELECT * FROM ratings ORDER BY category LIMIT 5;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
|  1 | 1        | 4.5    |
|  5 | 1        | 3.2    |
|  4 | 2        | 3.5    |
|  3 | 2        | 3.7    |
|  6 | 2        | 3.5    |
+----+----------+--------+

В каждом случае строки отсортированы по столбцу ORDER BY, который является всем, что требуется стандартом SQL.

Если важно гарантировать тот же самый порядок строк и без LIMIT, включайте дополнительные столбцы в ORDER BY, чтобы сделать детерминированный порядок. Например, если значения id уникальны, Вы можете сделать строки для данного category, сортируя их по id:

mysql> SELECT * FROM ratings ORDER BY category, id;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
|  1 | 1        | 4.5    |
|  5 | 1        | 3.2    |
|  3 | 2        | 3.7    |
|  4 | 2        | 3.5    |
|  6 | 2        | 3.5    |
|  2 | 3        | 5.0    |
|  7 | 3        | 2.7    |
+----+----------+--------+

mysql> SELECT * FROM ratings ORDER BY category, id LIMIT 5;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
|  1 | 1        | 4.5    |
|  5 | 1        | 3.2    |
|  3 | 2        | 3.7    |
|  4 | 2        | 3.5    |
|  6 | 2        | 3.5    |
+----+----------+--------+

Оптимизатор действительно обрабатывает запросы (и подзапросы) следующей формы:

SELECT ... FROM single_table ...
       ORDER BY non_index_column
       [DESC] LIMIT [M,]N;

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

SELECT col1, ... FROM t1 ... ORDER BY name LIMIT 10;
SELECT col1, ... FROM t1 ... ORDER BY RAND() LIMIT 15;

У буфера сортировки есть размер sort_buffer_size . Если элементы для N строк являются достаточно небольшими, чтобы поместиться в буфер (M+N строк, если M указан), сервер может избегать использования файла слияния и выполнить сортировку полностью в памяти, обрабатывая буфер вида как приоритетную очередь:

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

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

Оптимизатор рассматривает баланс между этими факторами для особых значений N и размера строки.

9.2.1.20. Оптимизация выражения конструктора строки

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

SELECT * FROM t1 WHERE (column1,column2) = (1,1);
SELECT * FROM t1 WHERE column1 = 1 AND column2 = 1;

Кроме того, оптимизатор обрабатывает оба выражения одинаково.

Оптимизатор, менее вероятно, будет использовать доступ к индексу, если столбцы конструктора строки не покрывают префикс индекса. Рассмотрите следующую таблицу, у которой есть первичный ключ на (c1, c2, c3):

CREATE TABLE t1 (c1 INT, c2 INT, c3 INT, c4 CHAR(100),
       PRIMARY KEY(c1,c2,c3));

В этом запросе WHERE использует все столбцы в индексировании. Однако, конструктор самой строки не покрывает префикс индекса, так что в итоге оптимизатор использует только c1 (key_len=4, размер c1):

mysql> EXPLAIN SELECT * FROM t1
    ->         WHERE c1=1 AND (c2,c3) > (1,1)\G
*************************** 1. row ***************************
 id: 1
  select_type: SIMPLE
table: t1
   partitions: NULL
 type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
 rows: 3
 filtered: 100.00
Extra: Using where

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

(c2,c3) > (1,1)
c2 > 1 OR ((c2 = 1) AND (c3 > 1))

Перезапись запроса, чтобы использовать выражение неконструктора приводит к оптимизатору, использующему все три столбца в индексе (key_len=12):

mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 1 AND (c2 > 1 OR
    ->         ((c2 = 1) AND (c3 > 1)))\G
*************************** 1. row ***************************
 id: 1
  select_type: SIMPLE
table: t1
   partitions: NULL
 type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 12
ref: NULL
 rows: 3
 filtered: 100.00
Extra: Using where

Таким образом, для лучших результатов, избегите смешивать конструктор строки с выражениями AND/ OR.

При определенных условиях оптимизатор может применить метод доступа диапазона для IN(), у которых есть параметры конструктора строки. См. раздел 9.2.1.3.5 .

9.2.1.21. Как избежать полного сканирования таблицы

Вывод EXPLAIN показывает ALL в столбце type, когда MySQL использует полный просмотр таблицы, чтобы решить запрос. Это обычно происходит при следующих условиях:

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

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

Этот раздел объясняет, как ускорить запросы изменения данных: INSERT, UPDATE и DELETE. Традиционные приложения OLTP и современные веб-приложения, как правило, делают много маленьких операций изменения данных, где параллелизм жизненно важен. Анализ данных, как правило, выполняет операции изменения данных, которые затрагивают много строк сразу, где основные соображения это ввод/вывод, чтобы написать большие объемы данных и сохранить индекс современным. Для вставки и обновления больших объемов данных (известный в промышленности как ETL, от extract-transform-load), иногда Вы используете другие запросы SQL или внешние команды, которые имитируют эффекты INSERT, UPDATE и DELETE.

9.2.2.1. Скорость INSERT

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

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

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

Размер таблицы замедляет вставку индексов на log N для индексов B-tree.

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

9.2.2.2. Скорость UPDATE

Запрос обновления оптимизирован как SELECT. Скорость записи зависит от обновляемого объема данных и числа индексов, которые обновлены. Индексы, которые не изменены, не становятся обновленными.

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

Для таблицы MyISAM, которая использует динамический формат строки, обновление строку к большей полной длине может разделить строку. Если Вы часто делаете это, очень важно использовать OPTIMIZE TABLE. См. раздел 14.7.2.4.

9.2.2.3. Скорость DELETE

Время, требуемое, чтобы удалить отдельные строки в MyISAM, точно пропорционально числу индексов. Чтобы удалить строки более быстро, Вы можете увеличить размер ключевого кэша, увеличивая key_buffer_size. См. раздел 6.1.1.

Чтобы удалить все строки из MyISAM, TRUNCATE TABLE tbl_name быстрей, чем DELETE FROM tbl_name. Усечение не безопасно для транзакции; ошибка происходит в ходе активной транзакции или активной табличной блокировки. См. раздел 14.1.30.

9.2.3. Оптимизация привилегий базы данных

Чем более сложна Ваша установка привилегии, тем больше издержек относится ко всем запросам SQL. Упрощение привилегий, установленных GRANT, позволяет MySQL уменьшить издержки проверки разрешения, когда клиенты выполняют запросы. Например, если Вы не предоставляете привилегий на уровне столбца или на уровне таблицы, сервер никогда не должен проверять содержание таблиц tables_priv и columns_priv. Точно так же, если Вы не устанавливаете границ ресурса для каких-либо учетных записей, сервер не должен выполнять подсчет ресурса. Если Вы имеете очень высокую обрабатывающую запрос загрузку, рассмотрите использование упрощенной структуры полномочий, чтобы уменьшить проверку разрешений.

9.2.4. Оптимизация запросов INFORMATION_SCHEMA

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

Поведение сравнения для имен базы данных и имен таблиц в INFORMATION_SCHEMA могут отличаться от того, что Вы ожидаете. Для деталей см. раздел 11.1.8.7.

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

CHARACTER_SETS
COLLATIONS
COLLATION_CHARACTER_SET_APPLICABILITY
COLUMNS
KEY_COLUMN_USAGE
SCHEMATA
STATISTICS
TABLES
TABLE_CONSTRAINTS
VIEWS

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

Некоторые таблицы INFORMATION_SCHEMA содержат столбцы, которые обеспечивают табличную статистику:

STATISTICS.CARDINALITY
TABLES.AUTO_INCREMENT
TABLES.AVG_ROW_LENGTH
TABLES.CHECKSUM
TABLES.CHECK_TIME
TABLES.CREATE_TIME
TABLES.DATA_FREE
TABLES.DATA_LENGTH
TABLES.INDEX_LENGTH
TABLES.MAX_DATA_LENGTH
TABLES.TABLE_ROWS
TABLES.UPDATE_TIME

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

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

Чтобы определить, как установлена information_schema_stats, рассмотрите обмены:

Установите глобальное значение information_schema_stats, чтобы определить значение по умолчанию, используемое первоначально всеми сеансами. Отдельные сеансы могут установить сеансовое значение information_schema_stats, чтобы переопределить глобальное значение как надо.

Для таблиц INFORMATION_SCHEMA, осуществленных как представления о таблицах словаря данных, индексы на основных таблицах разрешают оптимизатору создать эффективные планы выполнения запроса. Чтобы видеть выбор, сделанный оптимизатором, надо использовать EXPLAIN. Чтобы также видеть запрос, используемый сервером, чтобы выполнить запрос INFORMATION_SCHEMA, используйте SHOW WARNINGS сразу после EXPLAIN.

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

mysql> SELECT COLLATION_NAME
    ->        FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY
    ->        WHERE CHARACTER_SET_NAME = 'utf8mb4';
+----------------------+
| COLLATION_NAME       |
+----------------------+
| utf8mb4_general_ci   |
| utf8mb4_bin          |
| utf8mb4_unicode_ci   |
| utf8mb4_icelandic_ci |
| utf8mb4_latvian_ci   |
| utf8mb4_romanian_ci  |
| utf8mb4_slovenian_ci |
...

Как сервер обрабатывает этот запрос? Чтобы узнать, надо использовать EXPLAIN:

mysql> EXPLAIN SELECT COLLATION_NAME
    ->         FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY
    ->         WHERE CHARACTER_SET_NAME = 'utf8mb4'\G
*************************** 1. row ***************************
 id: 1
  select_type: SIMPLE
table: cs
   partitions: NULL
 type: const
possible_keys: PRIMARY,name
key: name
key_len: 194
ref: const
 rows: 1
 filtered: 100.00
Extra: Using index
*************************** 2. row ***************************
 id: 1
  select_type: SIMPLE
table: col
   partitions: NULL
 type: ref
possible_keys: character_set_id
key: character_set_id
key_len: 8
ref: const
 rows: 68
 filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.01 sec)

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

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `mysql`.`col`.`name` AS `COLLATION_NAME`
 from `mysql`.`character_sets` `cs`
 join `mysql`.`collations` `col`
 where ((`mysql`.`col`.`character_set_id` = '45') and
 ('utf8mb4' = 'utf8mb4'))

Как обозначено SHOW WARNINGS , сервер обрабатывает запрос на COLLATION_CHARACTER_SET_APPLICABILITY как запрос на таблицах character_sets и collations словаря данных в системной базе данных mysql.

9.2.5. Оптимизация запросов Performance Schema

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

Большинство таблиц Performance Schema имеют индекс. Таблицы, которые не имеют, обычно содержат немного строк или вряд ли будут часто запрашиваться. Индексы Performance Schema предоставляют оптимизатору доступ к планам выполнения, кроме полного сканирования таблицы. Они также улучшают работу для связанных объектов, таких как представления схемы sys, которые используют те таблицы.

Чтобы видеть, имеет ли данная таблица индексы и каковы они, надо использовать SHOW INDEX или SHOW CREATE TABLE:

mysql> SHOW INDEX FROM performance_schema.accounts\G
*************************** 1. row ***************************
Table: accounts
   Non_unique: 0
 Key_name: ACCOUNT
 Seq_in_index: 1
  Column_name: USER
Collation: NULL
  Cardinality: NULL
 Sub_part: NULL
 Packed: NULL
 Null: YES
   Index_type: HASH
Comment:
Index_comment:
Visible: YES
*************************** 2. row ***************************
Table: accounts
   Non_unique: 0
 Key_name: ACCOUNT
 Seq_in_index: 2
  Column_name: HOST
Collation: NULL
  Cardinality: NULL
 Sub_part: NULL
 Packed: NULL
 Null: YES
   Index_type: HASH
Comment:
Index_comment:
Visible: YES

mysql> SHOW CREATE TABLE performance_schema.rwlock_instances\G
*************************** 1. row ***************************
 Table: rwlock_instances
Create Table: CREATE TABLE `rwlock_instances` (
  `NAME` varchar(128) NOT NULL,
  `OBJECT_INSTANCE_BEGIN` bigint(20) unsigned NOT NULL,
  `WRITE_LOCKED_BY_THREAD_ID` bigint(20) unsigned DEFAULT NULL,
  `READ_LOCKED_BY_COUNT` int(10) unsigned NOT NULL,
  PRIMARY KEY (`OBJECT_INSTANCE_BEGIN`),
  KEY `NAME` (`NAME`),
  KEY `WRITE_LOCKED_BY_THREAD_ID` (`WRITE_LOCKED_BY_THREAD_ID`))
  ENGINE=PERFORMANCE_SCHEMA DEFAULT CHARSET=utf8

Чтобы видеть план выполнения для запроса к Performance Schema и использует ли это любой индекс, стоит использовать EXPLAIN:

mysql> EXPLAIN SELECT * FROM performance_schema.accounts
    ->         WHERE (USER,HOST) = ('root','localhost')\G
*************************** 1. row ***************************
 id: 1
  select_type: SIMPLE
table: accounts
   partitions: NULL
 type: const
possible_keys: ACCOUNT
key: ACCOUNT
key_len: 278
ref: const,const
 rows: 1
 filtered: 100.00
Extra: NULL

Вывод EXPLAIN указывает, что оптимизатор использует индекс ACCOUNT таблицы accounts, который включает столбцы USER и HOST.

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

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

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

См. раздел 9.3.8.

9.2.6. Другие подсказки по оптимизации

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

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 применяет индексы для этих операций:

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

9.3.2. Используя первичные ключи

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

Если Ваша таблица является большой и важной, но не имеет очевидного столбца или набора столбцов, чтобы использовать в качестве первичного ключа, Вы могли бы создать отдельный столбец со значениями auto-increment, чтобы использовать в качестве первичного ключа. Эти уникальные ID могут служить указателями на соответствующие строки в других таблицах, когда Вы присоединяетесь к таблицам, используя внешние ключи.

9.3.3. Используя внешние ключи

Если у таблицы есть много столбцов, и Вы запрашиваете много различных комбинаций столбцов, могло бы быть эффективно разделить менее часто используемые данные на отдельные таблицы с несколькими столбцами и связать их с основной таблицей, дублируя числовой столбец ID от основной таблицы. Таким путем у каждой маленькой таблицы может быть первичный ключ для быстрых поисков данных, и Вы можете запросить только набор столбцов, в котором Вы нуждаетесь при использовании работы соединения. В зависимости от того, как распределены данные, запросы могли бы выполнить меньше ввода/вывода и занять меньше кэш-памяти, потому что соответствующие столбцы упакованы вместе на диске. Чтобы максимизировать работу, запросы пытаются читать так мало блоков данных, насколько возможно с диска, таблицы только с несколькими столбцами могут приспособить больше строк в каждом блоке данных.

9.3.4. Столбец индекса

Наиболее распространенный тип индекса вовлекает единственный столбец, храня копии значений от того столбца в структуре данных, позволяя быстрые поиски для строк с соответствующими значениями столбцов. Структура данных B-tree позволяет индексированию быстро находить определенное значение, ряд значений или диапазона значений, соответствуя таким операторам, как =, >, BETWEEN, IN и т.д. в WHERE.

Максимальное количество индексов на таблицу и максимум длины индекса определены механизмом хранения. См. главы 16 и 17. Все механизмы хранения поддерживают, по крайней мере, 16 индексов на таблицу и общая длина по крайней мере 256 байтов. У большинства механизмов хранения есть более высокие пределы.

См. раздел 14.1.12.

Префиксы индексов

С col_name(N) в определении индекса для строкового столбца, Вы можете создать индексирование, которое использует только первые N символов столбца. Индексация только префикса значений столбцов таким образом может сделать индексный файл намного меньшим. Когда Вы индексируете столбец BLOB или TEXT, Вы должны определить длину префикса для индексирования. Например:

CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));

Префиксы могут составить до 767 байтов для InnoDB, которые используют формат строки REDUNDANT или COMPACT. Предел длины поднят до 3072 байтов для InnoDB, которые используют формат строки DYNAMIC или COMPRESSED. Для MyISAM размер префикса составляет 1000 байт.

Пределы измерены в байтах, тогда как длина префикса в CREATE TABLE, ALTER TABLE и CREATE INDEX интерпретируется как число символов для недвоичных строковых типов (CHAR, VARCHAR, TEXT) и число байтов для двоичных строковых типов (BINARY, VARBINARY, BLOB). Примите это во внимание, определяя длину префикса для недвоичного строкового столбца, который использует многобайтовый набор символов.

См. раздел 14.1.12.

Индексы FULLTEXT

Индексы FULLTEXT используются для полнотекстовых поисков. Только InnoDB и MyISAM поддерживают индексы FULLTEXT и только для столбцов CHAR, VARCHAR и TEXT. Индексация всегда имеет место по всему столбцу, префикс не используется. См. раздел 13.9.

Оптимизация применена к определенным видам запросов FULLTEXT. Запросы с этими характеристиками особенно эффективны:

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

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

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

MEMORY применяет по умолчанию индексы HASH, но также и BTREE.

9.3.5. Многостолбцовые индексы

MySQL может создать сводные индексы (то есть, индекс на многих столбцах). Индексирование может состоять из 16 столбцов. Для определенных типов данных Вы можете индексировать префикс столбца (см. раздел 9.3.4).

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

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

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

SELECT * FROM tbl_name
         WHERE hash_col=MD5(CONCAT(val1,
         val2)) AND
         col1=val1 AND
         col2=val2;

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

CREATE TABLE test (id INT NOT NULL, last_name CHAR(30) NOT NULL,
                   first_name CHAR(30) NOT NULL, PRIMARY KEY (id),
                   INDEX name (last_name, first_name));

Индекс name создан на столбцах last_name и first_name. Индексирование может использоваться для поисков в запросах, которые определяют значения в известном диапазоне для комбинаций last_name и first_name. Это может также использоваться для запросов, которые определяют только last_name, потому что этот столбец префикс индексирования (как описано позже в этом разделе). Поэтому индекс name используется для поисков в следующих запросах:

SELECT * FROM test WHERE last_name='Widenius';
SELECT * FROM test WHERE last_name='Widenius' AND first_name='Michael';
SELECT * FROM test WHERE last_name='Widenius' AND (first_name='Michael' OR
         first_name='Monty');
SELECT * FROM test WHERE last_name='Widenius' AND first_name >='M' AND
         first_name < 'N';

Однако, name не используется для поисков в следующих запросах:

SELECT * FROM test WHERE first_name='Michael';
SELECT * FROM test WHERE last_name='Widenius' OR first_name='Michael';

Предположите, что Вы создаете такой SELECT:

SELECT * FROM tbl_name
         WHERE col1=val1 AND
         col2=val2;

Если многостолбцовый индекс существует на col1 и col2, соответствующие строки могут быть принесены непосредственно. Если отдельный одностолбцовый индекс существует на col1 и col2, оптимизатор пытается использовать оптимизацию Index Merge (см. раздел 9.2.1.4) или пытается найти самые строгие индексы, решая, который индекс исключает больше строк и используя этот индекс, чтобы принести строки.

Если у таблицы есть многостолбцовый индекс, любой префикс может использоваться оптимизатором, чтобы искать строки. Например, если у Вас есть индекс на три столбца (col1, col2, col3), Вы имеете возможности поиска на (col1), (col1, col2) и (col1, col2, col3).

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

SELECT * FROM tbl_name WHERE col1=val1;
SELECT * FROM tbl_name WHERE col1=val1 AND
         col2=val2;
SELECT * FROM tbl_name WHERE col2=val2;
SELECT * FROM tbl_name WHERE col2=val2 AND
         col3=val3;

Если индексирование существует на (col1, col2, col3), только первые два запроса используют индексирование. Третий и четвертый запросы действительно вовлекают индексированные столбцы, но (col2) и (col2, col3) не префиксы для (col1, col2, col3).

9.3.6. Подтверждение использования индекса

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

9.3.7. Набор индексной статистики InnoDB и MyISAM

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

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

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

Если Вы склонны использовать много соединений то используйте <=> виесто =, значения NULL не являются особенными в сравнениях и один NULL равен другим. В этом случае nulls_equal соответствующий метод статистики.

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

Восстановить табличную статистику MyISAM можно любым из следующих методов:

Некоторые протесты относительно использования innodb_stats_method и myisam_stats_method:

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-индекс имеет несколько иные характеристики:

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)

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

9.3.10. Невидимый индекс

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

Индексы видимы по умолчанию. Чтобы управлять невидимостью явно для нового индекса, используют VISIBLE или INVISIBLE как часть индексного определения для CREATE TABLE, CREATE INDEX или ALTER TABLE:

CREATE TABLE t1 (i INT, j INT, k INT, INDEX i_idx (i) INVISIBLE)
       ENGINE = InnoDB;
CREATE INDEX j_idx ON t1 (j) INVISIBLE;
ALTER TABLE t1 ADD INDEX k_idx (k) INVISIBLE;

Чтобы изменить невидимость существующего индекса используют VISIBLE или INVISIBLE в ALTER TABLE ... ALTER INDEX:

ALTER TABLE t1 ALTER INDEX i_idx INVISIBLE;
ALTER TABLE t1 ALTER INDEX i_idx VISIBLE;

Информация о том, видимо ли индексирование или нет, доступна из таблицы INFORMATION_SCHEMA.STATISTICS или вывода SHOW INDEX :

mysql> SELECT INDEX_NAME, IS_VISIBLE
    ->        FROM INFORMATION_SCHEMA.STATISTICS
    ->        WHERE TABLE_SCHEMA = 'db1' AND TABLE_NAME = 't1';
+------------+------------+
| INDEX_NAME | IS_VISIBLE |
+------------+------------+
| i_idx      | YES        |
| j_idx      |  NO        |
| k_idx      |  NO        |
+------------+------------+

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

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

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

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

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

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

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

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

ALTER TABLE t2 ADD PRIMARY KEY (i);

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

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

9.4. Оптимизация структуры базы данных

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

9.4.1. Оптимизация размера данных

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

MySQL поддерживает много различных механизмов хранения (табличные типы) и форматов строк. Для каждой таблицы Вы можете решить, который метод хранения и индексации использовать. Выбор надлежащего формата таблицы для Вашего приложения может дать Вам большой прирост производительности. См. главы 16 и 17.

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

Столбцы таблицы

Формат строки

Индексы

Joins

Нормализация

9.4.2. Оптимизация типов данных MySQL

9.4.2.1. Оптимизация для числовых данных

9.4.2.2. Оптимизация для типов символа и строки

9.4.2.3. Оптимизация для BLOB

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, когда это не является соответствующим.

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

9.4.3. Оптимизация для многих таблиц

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

9.4.3.1. Как MySQL открывает и закрывает таблицы

Когда Вы выполняете mysqladmin status, Вы должны видеть что-то вроде этого:

Uptime: 426 Running threads: 1 Questions: 11082
Reloads: 1 Open tables: 12

Open tables = 12 может быть несколько озадачивающим, если у Вас есть только шесть таблиц.

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

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

table_open_cache относится к max_connections. Например, для 200 параллельных рабочих соединений, определите табличный размер кэша, по крайней мере, в 200 * N, где N это максимальное количество таблиц, участвующих в любом из запросов, который Вы выполняете. Вы должны также зарезервировать некоторые дополнительные описатели файла для временных таблиц и файлов.

Удостоверьтесь, что Ваша операционная система может обработать число открытых описателей файла, подразумеваемых table_open_cache . Если table_open_cache установлен слишком высоко, MySQL может исчерпать описатели файла и отказаться от соединений или быть не в состоянии выполнить запросы, и быть очень ненадежным.

Вы должны также принять во внимание факт, что MyISAM нуждается в двух описателях файла для каждой уникальной открытой таблицы. Для разделенной таблицы MyISAM два описателя файла требуются для каждого раздела открытой таблицы. Отметьте что, когда MyISAM открывает разделенную таблицу, это открывает каждый раздел этой таблицы, используется ли данный раздел фактически. См. MyISAM and partition file descriptor usage. Вы можете увеличить число описателей файла, доступных MySQL, используя опцию --open-files-limit при запуске mysqld . См. раздел B.5.2.17.

Кэш открытых таблиц сохранен на уровне table_open_cache . Сервер автоматически меняет размер кэша при запуске. Чтобы установить размер явно, установите table_open_cache при запуске. Отметьте, что MySQL может временно открыть больше таблиц, чем это значение, чтобы выполнить запросы.

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

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

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

Если Вы открываете таблицу с HANDLER tbl_name OPEN, специализированный табличный объект выделен для потока. Этот табличный объект не использован совместно другими потоками и не закрыт до требований потока HANDLER tbl_name CLOSE или завершения потока. Когда это происходит, таблица отложена в табличном кэше (если кэш не полон). См. раздел 14.2.4.

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

mysql> SHOW GLOBAL STATUS LIKE 'Opened_tables';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Opened_tables | 2741  |
+---------------+-------+

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

9.4.3.2. Недостатки составления многих таблиц в той же самой базе данных

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

9.4.4. Внутренние временные таблицы в MySQL

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

9.5. Оптимизация таблиц InnoDB

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

9.5.1. Оптимизация расположения хранения для таблиц InnoDB

9.5.2. Оптимизирующее операционное управление InnoDB

Чтобы оптимизировать обработку транзакций, найдите идеальный баланс между издержками транзакционных особенностей и рабочей нагрузкой Вашего сервера. Например, приложение могло бы столкнуться с исполнительными проблемами, если оно передает тысячи раз в секунду и совсем другими проблемами, если оно передает только каждые 2-3 часа.

9.5.3. Оптимизация транзакций только для чтения

InnoDB может избежать издержек, связанных с transaction ID (поле TRX_ID) для транзакций, которые только для чтения. Операционный ID необходим для транзакции, которая могла бы выполнить записи или блокирующие чтения, например, SELECT ... FOR UPDATE. Устранение ненужных операционных ID уменьшает размер внутренних структур данных, с которыми консультируются каждый раз, когда запрос изменения запроса или данных создает представление чтения.

InnoDB обнаруживает транзакции только для чтения когда:

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

9.5.5. Оптовые данные для таблиц InnoDB

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

9.5.6. Оптимизация запросов InnoDB

Чтобы настроить запросы для InnoDB, создайте соответствующий набор индексов на каждой таблице. См. раздел 9.3.1.

9.5.7. Оптимизация InnoDB DDL

9.5.8. Оптимизация дискового ввода/вывода InnoDB

Если Вы следуете за лучшими методами для проектирования баз данных и настройки операций SQL, но Вашу базу данных все еще замедляет тяжелая дисковая деятельность ввода/вывода, исследуйте эти низкоуровневые методы, связанные с дисковым вводом/выводом. Если в Unix команда top или Windows Task Manager показывают, что процент использования центрального процессора с Вашей рабочей нагрузкой составляет меньше 70%, Ваша рабочая нагрузка является, вероятно, связанной с диском.

9.5.9. Оптимизация переменных конфигурации InnoDB

Различные настройки работают лучше всего на серверах с легкими и предсказуемыми загрузками.

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

Основные шаги конфигурации, которые Вы можете выполнить, включают:

9.5.10. Оптимизация InnoDB для систем со многими таблицами

9.6. Оптимизация для таблиц MyISAM

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

9.6.1. Оптимизация запросов MyISAM

Некоторые общие советы для того, чтобы ускорить запросы на таблицах MyISAM:

9.6.2. Оптовые загрузки данных для MyISAM

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

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

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

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

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

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

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

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

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

EXPLAIN может также использоваться, чтобы получить информацию о столбцах в таблице. EXPLAIN tbl_name синоним with DESCRIBE tbl_name и SHOW COLUMNS FROM tbl_name. См. разделы information, see 14.8.1 и 14.7.5.5.

9.8.2. Выходной формат EXPLAIN

EXPLAIN предоставляет информацию о плане выполнения относительно SELECT.

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

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

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

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

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

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

СтолбецИмя JSON Значение
id select_id Идентификатор SELECT
select_typeНетТип SELECT
table table_nameТаблица для выходной строки
partitionspartitions Соответствующее разделение
type access_typeТип соединения
possible_keyspossible_keys Возможные индексы, чтобы выбрать
key keyИндекс, который фактически выбран
key_len key_lengthДлина выбранного ключа
ref refСтолбцы по сравнению с индексом
rows rowsОценка строк, которые будут исследованы
filtered filteredПроцент строк, которые фильтруются по табличному условию
Extra НетДополнительная информация

Свойства JSON, которые являются NULL, не выведены на экран в JSON-формате вывода EXPLAIN.

Типы соединения EXPLAIN

type EXPLAIN описывает, как присоединяются к таблицам. В формате JSON они найдены как значения свойства access_type. Следующий список описывает типы соединения, упорядоченные от лучшего типа до худшего:

Дополнительная информация EXPLAIN

Столбец Extra в выводе EXPLAIN содержит дополнительную информацию о том, как MySQL решает запрос. Следующий список объясняет значения, которые могут появиться в этом столбце. Каждый элемент также указывает для формата JSON, который выводит на экран свойство Extra. Для некоторых из них есть определенное свойство. Другие выводят на экран как текст свойства message.

Если Вы хотите сделать свои запросы с такой скоростью, как возможныо, см. в Extra значения Using filesort и Using temporary или для формата JSON свойства using_filesort и using_temporary_table, равные true.

Выходная интерпретация EXPLAIN

Вы можете получить хороший признак того, насколько хорошо соединение, принимая произведение значений в столбце rows вывода EXPLAIN. Это должно сказать Вам примерно, сколько строк MySQL должен исследовать, чтобы выполнить запрос. Если Вы ограничиваете запросы через max_join_size, это также используется, чтобы определить, который мультитабличный SELECT выполнять. См. раздел 6.1.1.

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

Предположите, что Вы имеете SELECT, показанный здесь, и что Вы планируете исследовать это с использованием EXPLAIN:

EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, tt.ProjectReference,
        tt.EstimatedShipDate, tt.ActualShipDate, tt.ClientID,
        tt.ServiceCodes, tt.RepetitiveID, tt.CurrentProcess,
        tt.CurrentDPPerson, tt.RecordVolume, tt.DPPrinted, et.COUNTRY,
        et_1.COUNTRY, do.CUSTNAME
        FROM tt, et, et AS et_1, do WHERE tt.SubmitTime IS NULL AND
        tt.ActualPC = et.EMPLOYID AND tt.AssignedPC = et_1.EMPLOYID AND
        tt.ClientID = do.CUSTNMBR;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

mysql> ANALYZE TABLE tt;

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

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

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

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

9.8.3. Формат вывода EXPLAIN EXTENDED

Когда EXPLAIN используется с EXTENDED, вывод включает столбец filtered. Этот столбец указывает на предполагаемый процент строк таблицы, которые будут фильтроваться по табличному условию. Кроме того, запрос производит дополнительную информацию, которая может быть просмотрена через SHOW WARNINGS после EXPLAIN. Значение Message в выводе SHOW WARNINGS выводит на экран, как оптимизатор квалифицирует имена таблиц и имена столбцов в SELECT, на что SELECT похож после применения правил перезаписи и оптимизации, и возможно другие примечания о процессе оптимизации.

С MySQL 5.7.3 EXPLAIN изменено так, что эффект EXTENDED всегда включается. Параметр EXTENDED все еще признан, но лишний и устарел. Это будет удалено из EXPLAIN в будущем выпуске MySQL.

Вот пример расширенного вывода:

mysql> EXPLAIN EXTENDED SELECT t1.a, t1.a IN
    ->         (SELECT t2.a FROM t2) FROM t1\G
*************************** 1. row ***************************
 id: 1
  select_type: PRIMARY
table: t1
 type: index
possible_keys: NULL
key: PRIMARY
key_len: 4
ref: NULL
 rows: 4
 filtered: 100.00
Extra: Using index
*************************** 2. row ***************************
 id: 2
  select_type: SUBQUERY
table: t2
 type: index
possible_keys: a
key: a
key_len: 5
ref: NULL
 rows: 3
 filtered: 100.00
Extra: Using index
2 rows in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `test`.`t1`.`a` AS `a`,
 <in_optimizer>(`test`.`t1`.`a`,`test`.`t1`.`a` in
 ( <materialize> (/* select#2 */ select `test`.`t2`.`a`
 from `test`.`t2` where 1 having 1 ),
 <primary_index_lookup>(`test`.`t1`.`a` in
 <temporary table> on <auto_key>
 where ((`test`.`t1`.`a` = `materialized-subquery`.`a`))))) AS `t1.a
 IN (SELECT t2.a FROM t2)` from `test`.`t1`
1 row in set (0.00 sec)

EXPLAIN EXTENDED может использоваться с SELECT, DELETE, INSERT, REPLACE и UPDATE. Однако, следующий SHOW WARNINGS выводит на экран непустой результат только для SELECT.

Поскольку запрос, выведенный на экран SHOW WARNINGS, может содержать специальные маркеры, чтобы предоставить информацию о перезаписи запроса или действиях оптимизатора, запрос не обязательно допустимый SQL и не предназначен, чтобы быть выполненным. Вывод может также включать строки с Message, который обеспечивает дополнительные не-SQL примечания о мерах, предпринятых оптимизатором.

Следующий список описывает специальные маркеры, которые могут появиться в выводе EXTENDED и SHOW WARNINGS:

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

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

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

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 /*+ BNL(t1) BKA(t2) */ ...

Но не это:

SELECT /*+ BNL(t1) */ /* BKA(t2) */ ...

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

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

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

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

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

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

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

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

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

Примеры:

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 эти правила приоритета применяются:

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

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

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

Примеры:

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] ...])

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

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

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

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

QB_NAME(name)

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

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

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

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

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

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

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

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

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

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

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

9.9.4. Индексные подсказки

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

Индексные подсказки определены после имени таблицы. Для общего синтаксиса для того, чтобы определить таблицы в SELECT см. раздел 14.2.9.2. Синтаксис для того, чтобы обратиться к отдельной таблице, включая индексные подсказки, похож на это:

tbl_name [[AS] alias]
[index_hint_list]
index_hint_list:
index_hint [, index_hint] ...
index_hint:
USE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
     | IGNORE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
     | FORCE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
index_list:
index_name [, index_name] ...

USE INDEX (index_list) говорит MySQL использовать только один из названных индексов, чтобы найти строки в таблице. Альтернативный синтаксис IGNORE INDEX (index_list) говорит MySQL не использовать некоторый индекс или индексы. Эти подсказки полезны, если EXPLAIN показывает, что MySQL использует не тот индекс.

FORCE INDEX похожа на USE INDEX (index_list) с тем дополнением, что сканирование таблицы очень дорого. Другими словами, сканирование таблицы используется, только если нет никакого способа использовать один из названных индексов, чтобы найти строки в таблице.

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

index_name не должно быть полным именем индекса. Это может быть однозначный префикс имени. Если префикс неоднозначен, ошибка происходит.

Примеры:

SELECT * FROM table1 USE INDEX (col1_index,col2_index)
         WHERE col1=1 AND col2=2 AND col3=3;
SELECT * FROM table1 IGNORE INDEX (col3_index)
         WHERE col1=1 AND col2=2 AND col3=3;

Синтаксис для индексных подсказок имеет следующие характеристики:

Если индексная подсказка не включает 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 индексные подсказки работают так:

9.9.5. Модель стоимости оптимизатора

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

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

Модель стоимости

Конфигурируемые оптимизатором модели стоимости работают так:

База данных модели стоимости

Образцовая база данных стоимости оптимизатора состоит из двух таблиц в базе данных mysql, которые содержат информацию о смете для операций, которые происходят во время выполнения запроса:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Чтобы управлять размером ключевого кэша, используйте переменную key_buffer_size. Если эта переменная установлена равной 0, никакой ключевой кэш не используется. Ключевой кэш также не используется, если значение key_buffer_size является слишком маленьким, чтобы выделить минимальное число буферов блоков.

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

Индексный блок это непрерывный модуль доступа к индексным файлам MyISAM. Обычно размер индексного блока равен размеру узлов индексного B-дерева. Индексы представлены на диске, используя структуру данных B-дерева. Узлы у основания дерева это узлы листа. Узлы выше узлов листа это узлы нелиста.

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

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

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

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

InnoDB тоже использует LRU, чтобы управлять его буферным пулом. См. раздел 16.6.3.1.

9.10.2.1. Совместно используемый ключевой доступ

Потоки могут обращаться к кэшу ключей одновременно, согласно следующим условиям:

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

9.10.2.2. Многократные ключевые кэши

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

Где есть многократные ключевые кэши, сервер должен знать, который кэш использовать, обрабатывая запросы для данной таблицы MyISAM. По умолчанию все индексы MyISAM кэшируются в ключевом кэше значения по умолчанию. Чтобы назначить индекс к определенному ключевому кэшу, используйте CACHE INDEX (см. раздел 14.7.6.2). Например, следующий запрос назначает индекс от таблиц t1, t2 и t3 к ключевому кэшу hot_cache:

mysql> CACHE INDEX t1, t2, t3 IN hot_cache;
+---------+--------------------+----------+----------+
| Table   | Op                 | Msg_type | Msg_text |
+---------+--------------------+----------+----------+
| test.t1 | assign_to_keycache | status   | OK       |
| test.t2 | assign_to_keycache | status   | OK       |
| test.t3 | assign_to_keycache | status   | OK       |
+---------+--------------------+----------+----------+

Ключевой кэш, упомянутый в CACHE INDEX, может быть создан, устанавливая его размер с SET GLOBAL или при использовании опций запуска сервера. Например:

mysql> SET GLOBAL keycache1.key_buffer_size=128*1024;

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

mysql> SET GLOBAL keycache1.key_buffer_size=0;

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

mysql> SET GLOBAL key_buffer_size = 0;

mysql> SHOW VARIABLES LIKE 'key_buffer_size';
+-----------------+---------+
| Variable_name   | Value   |
+-----------------+---------+
| key_buffer_size | 8384512 |
+-----------------+---------+

Ключевые переменные кэша это структурированные системные переменные, у которых есть имя и компоненты. Для keycache1.key_buffer_size keycache1 это имя переменной кэша и key_buffer_size компонент кэша. См. раздел 6.1.6.1.

По умолчанию индексы назначены на основной (по умолчанию) ключевой кэш, создаваемый при запуске сервера. Когда ключевой кэш разрушен, все индексы, назначенные на него, повторно назначены на ключевой кэш по умолчанию.

Для занятого сервера Вы можете использовать стратегию, которая вовлекает три ключевых кэша:

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

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.

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

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

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

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

9.10.3.2. Опции SELECT кэша запроса

Два запроса, связанные с кэшем, могут быть определены в SELECT:

Примеры:

SELECT SQL_CACHE id, name FROM customer;
SELECT SQL_NO_CACHE id, name FROM customer;

9.10.3.3. Конфигурация кэша запроса

have_query_cache указывает, доступен ли кэш запроса:

mysql> SHOW VARIABLES LIKE 'have_query_cache';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| have_query_cache | YES   |
+------------------+-------+

Используя стандартный MySQL, это значение всегда YES, даже если кэширование запроса отключено.

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

Чтобы установить размер кэша запроса, установите query_cache_size . Установка этого к 0 отключает кэш запроса, что делает установку query_cache_type=0 . По умолчанию кэш запроса отключен. Это достигнуто, используя размер значения по умолчанию 1M со значением по умолчанию для query_cache_type 0.

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

Используя Windows Configuration Wizard, чтобы установить или сконфигурировать MySQL, значение по умолчанию для query_cache_size будет сконфигурировано автоматически для Вас, основываясь на различных доступных типах конфигурации. Используя Windows Configuration Wizard, кэш запроса может быть включен (то есть, установлен в ненулевое значение), из-за выбранной конфигурации. Кэшем запроса также управляет установка query_cache_type . Проверьте значения этих переменных в my.ini.

Когда Вы устанавливаете query_cache_size к ненулевому значению, имейте в виду, что кэш запроса нуждается в минимальном размере приблизительно 40 КБ, чтобы выделить его структуры. Точный размер зависит от системной архитектуры. Если Вы устанавливаете слишком маленькое значение, Вы получите предупреждение, как в этом примере:

mysql> SET GLOBAL query_cache_size = 40000;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Warning
   Code: 1282
Message: Query cache failed to set size 39936;
 new query cache size is 0

mysql> SET GLOBAL query_cache_size = 41984;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW VARIABLES LIKE 'query_cache_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| query_cache_size | 41984 |
+------------------+-------+

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

mysql> SET GLOBAL query_cache_size = 1000000;
Query OK, 0 rows affected (0.04 sec)

mysql> SHOW VARIABLES LIKE 'query_cache_size';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| query_cache_size | 999424 |
+------------------+--------+
1 row in set (0.00 sec)

query_cache_size выравнивается к самому близкому 1024-байтовому блоку. Значение может поэтому отличаться от значения, которое Вы назначаете.

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

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

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

mysql> SET SESSION query_cache_type = OFF;

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

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

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

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

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

9.10.3.4. Состояние кэша запроса и обслуживание

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

mysql> SHOW VARIABLES LIKE 'have_query_cache';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| have_query_cache | YES   |
+------------------+-------+

Вы можете дефрагментировать кэш запроса, чтобы лучше использовать его память с FLUSH QUERY CACHE. Запрос не удаляет запросы из кэша.

RESET QUERY CACHE удаляет все результаты запроса из кэша. FLUSH TABLES также делает это.

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

mysql> SHOW STATUS LIKE 'Qcache%';
+-------------------------+--------+
| Variable_name           | Value  |
+-------------------------+--------+
| Qcache_free_blocks      |     36 |
| Qcache_free_memory      | 138488 |
| Qcache_hits             | 79570  |
| Qcache_inserts          | 27087  |
| Qcache_lowmem_prunes    | 3114   |
| Qcache_not_cached       | 22989  |
| Qcache_queries_in_cache |   415  |
| Qcache_total_blocks     |   912  |
+-------------------------+--------+

Описания каждой из этих переменных даны в разделе 6.1.7. Некоторое использование для них описано здесь.

Общее количество запросов SELECT дано этой формулой:

  Com_select
+ Qcache_hits
+ queries with errors found by parser

Com_select дано этой формулой:

  Qcache_inserts
+ Qcache_not_cached
+ queries with errors found during the column-privileges check

Кэш запроса использует блоки переменной длины, таким образом, Qcache_total_blocks и Qcache_free_blocks может указать на фрагментацию кэш-памяти. После FLUSH QUERY CACHE только единственный свободный блок остается.

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

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

9.10.4. Кэширование готовых запросов и сохраненных программ

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

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

Когда сервер использует кэшируемую внутреннюю структуру запроса, он должен заботиться, что структура современна. Изменения метаданных могут произойти для объекта, используемого запросом, вызывая несоответствие между текущим определением объекта и определением как представлено во внутренней структуре запроса. Изменения метаданных происходят для запросов DDL, таких как те, которые создают, удаляют, изменяют, переименовывают или усекают таблицы, или которые анализируют, оптимизируют или ремонтируют таблицы. Табличные изменения контента (например, с INSERT или UPDATE) не изменяют метаданные и не делают SELECT.

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

PREPARE s1 FROM 'SELECT * FROM t1';

SELECT * расширяется во внутренней структуре до списка столбцов в таблице. Если набор столбцов в таблице изменен с ALTER TABLE, готовый запрос терет актуальность. Если сервер не обнаруживает это изменение в следующий раз, когда клиент выполняет s1, готовый запрос возвратит неправильные результаты.

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

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

Сервер также обнаруживает изменения метаданных для объектов в выражениях. Они могли бы использоваться в запросах, определенных для сохраненных программ, например, DECLARE CURSOR, или запросы управления потоками, например, IF, CASE и RETURN.

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

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

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

Перепарсинг является автоматическим.

Для готовых запросов Com_stmt_reprepare отслеживает число переприготовлений.

9.11. Оптимизации операций блокировки

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

9.11.1. Внутренние методы блокировки

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

Блокировка на уровне строки

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

Чтобы избежать тупиков, с многократными параллельными операциями записи на InnoDB, приобретите необходимые блокировки в начале транзакции, запросив SELECT ... FOR UPDATE для каждой группы строк, которая будет изменена, даже если запросы изменения данных прибывают позже в транзакцию. Если транзакции изменяют или блокируют больше, чем одну таблицу, делают применимые запросы в том же самом порядке в пределах каждой транзакции. Тупики затрагивают работу вместо того, чтобы представить серьезную ошибку, потому что InnoDB автоматически обнаруживает условия тупика по умолчанию и откатывает до прежнего уровня одну из затронутых транзакций.

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

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

Блокировка на уровне таблицы

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

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

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

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

  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;

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

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

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

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

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

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

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

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

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

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

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

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

С внешней отключенной блокировкой, чтобы использовать myisamchk, Вы должны или остановить сервер, в то время как myisamchk выполняется или иначе блокировать и сбросить таблицы прежде, чем выполнить myisamchk. См. System Factors. Чтобы избежать этого требования, используйте CHECK TABLE и REPAIR TABLE для MyISAM.

Для mysqld внешней блокировкой управляет значение skip_external_locking . Когда эта переменная включена, внешняя блокировка отключена и наоборот. Внешняя блокировка отключена по умолчанию.

Использованием внешней блокировки можно управлять при запуске сервера при использовании опции --external-locking или --skip-external-locking.

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

Самый легкий способ удовлетворить эти условия состоит в том, чтобы всегда использовать --external-locking с --delay-key-write=OFF и --query-cache-size=0 . Это не сделано по умолчанию, потому что во многих установках полезно иметь смесь предыдущих опций.

9.12. Оптимизация MySQL Server

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

9.12.1. Оптимизация дискового I/O

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

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 работает следующим образом:

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

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 использует память. Где применимо, на соответствующие системные переменные ссылаются. Некоторые элементы определены для механизмов хранения.

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, не выпускает ее.

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

Чтобы открыть заблокированные узлы, сбросьте кэш узла командой 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)

Чтобы определить эксплуатационные качества, рассмотрите следующие факторы:

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

9.13.1. Измерение скорости выражений и функций

Чтобы измерить скорость определенного выражения MySQL или функции, вызовите BENCHMARK(), используя mysql . Ее синтаксис: BENCHMARK(loop_count,expression) . Возвращаемое значение всегда ноль, но mysql печатает строку, выводящую на экран приблизительно, сколько времени запрос взял. Например:

mysql> SELECT BENCHMARK(1000000,1+1);
+------------------------+
| BENCHMARK(1000000,1+1) |
+------------------------+
| 0                      |
+------------------------+
1 row in set (0.32 sec)

Этот результат был получен на системе Pentium II 400MHz. Это показывает, что MySQL может выполнить 1000000 простых сложений за 0.32 секунды на этой системе.

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

9.13.2. Используя Ваши собственные точки отсчета

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

Свободный эталонный набор Open Source Database Benchmark, можно скачать с http://osdb.sourceforge.net/.

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

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

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

9.13.3. Определение эксплуатационных качеств с performance_schema

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

9.14. Информация о потоке исследования

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

Доступ к threads не требует mutex и оказывает минимальное влияние на работу сервера. INFORMATION_SCHEMA.PROCESSLIST и SHOW PROCESSLIST имеют отрицательные исполнительные последствия, потому что они требуют mutex. threads также показывает информацию о фоновых потоках, которую не дают INFORMATION_SCHEMA.PROCESSLIST и SHOW PROCESSLIST . Это означает, что threads может использоваться, чтобы контролировать деятельность, которую другие источники информации дать не могут.

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

Каждый вход списка процесса содержит несколько сведений:

Следующие разделы перечисляют возможные значения Command и State по категориям. Значение для некоторых из этих значений самоочевидно. Для других обеспечено дополнительное описание.

9.14.1. Значения команды потока

У потока может быть любое из следующих значений Command:

9.14.2. Общие состояния потока

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

9.14.3. Состояния потока кэша запроса

Эти состояния потока связаны с кэшем запроса (см. раздел 9.10.3).

9.14.4. Ведущие состояния потока репликации

Следующий список показывает наиболее распространенные состояния, которые Вы можете видеть в столбце State для потока Binlog Dump ведущего устройства. Если Вы не видите потоки Binlog Dump на главном сервере, это означает, что репликация не запущена, то есть, что никакие ведомые устройства в настоящее время не соединяются.

9.14.5. Ведомые состояния потока ввода/вывода

Следующий список показывает наиболее распространенные состояния, которые Вы видите в столбце State для ведомого потока ввода/вывода сервера. Это состояние также появляется в столбце Slave_IO_State , выведенный на экран SHOW SLAVE STATUS, таким образом, Вы можете получить хорошее представление того, что происходит при использовании запроса.

9.14.6. Ведомое устройство ответа состояния потока SQL

Следующий список показывает наиболее распространенные состояния, которые Вы можете видеть в столбце State для потока SQL ведомого сервера:

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

9.14.7. Ведомые состояния соединения потока

Эти состояния потока происходят на ведомом устройстве, но связаны с потоками соединения, а не с потоками SQL или вводом/выводом.

9.14.8. Состояния потока планировщика событий

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