2. • не извлекает ли приложение больше данных, чем
нужно
• не анализирует ли сервер MySQL больше строк, чем
это необходимо
Типичные ошибки:
• Выборка ненужных строк
• Выборка всех столбцов из соединения нескольких
таблиц
• Выборка всех столбцов
3. SELECT *
FROM tag
JOIN tag_post ON tag_post.tag_id=tag.id
JOIN post ON tag_post.post_id=post.id
WHERE tag.tag=’mysql’;
SELECT * FROM tag WHERE tag=’mysql’;
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);
4. Соединение в приложении может оказаться
эффективнее в следующих случаях:
• Организован кэш и вы повторно используете ранее
запрошенные данные
• Часто используются таблицы типа MyISAM
• Данные распределены по нескольким серверам
• Вместо соединения с большой таблицей используется
список IN()
• В соединении несколько раз встречается одна и та же
таблица
5.
6. SHOW FULL PROCESSLIST;
Sleep
Поток ожидает поступления нового запроса от клиента.
Query
Поток либо занят выполнением запроса, либо отправляет клиенту результаты.
Locked
Поток ожидает предоставления табличной блокировки на уровне сервера.
Analyzing и Statistics
Поток проверяет статистику, собранную подсистемой хранения, и
оптимизирует запрос.
Copying to tmp table [on disk]
Поток обрабатывает запрос и копирует результаты во временную таблицу.
Sorting result
Поток занят сортировкой результирующего набора.
Sending data
Пересылает данные между различными стадиями обработки запроса или
генерирует результирующий набор или возвращает результаты клиенту.
7. SELECT SQL_NO_CACHE COUNT(*) FROM sakila.film_actor;
mysql> SHOW STATUS LIKE ‘last_query_cost’;
COUNT(*)
5462
Variable_name Value
Last_query_cost 1040.599000
8. • Некорректная статистика.
• Принятая метрика стоимости не всегда эквивалентна истинной
стоимости выполнения запроса.
• Представление MySQL о том, что такое «оптимально», может
расходиться с вашим представлением.
• MySQL не берет в расчет другие одновременно выполняющиеся
запросы.
• MySQL не всегда выполняет оптимизацию по стоимости. Иногда он
просто следует правилам.
• Оптимизатор не учитывает стоимость операций, которые ему
неподконтрольны, например выполнение хранимых или
определенных пользователем функций.
• Не всегда оптимизатор способен рассмотреть все возможные планы
выполнения, поэтому оптимальный план он может просто не увидеть.
9. Изменение порядка соединения
Таблицы не обязательно соединять именно в том порядке, который указан в
запросе.
Применение алгебраических правил эквивалентности
MySQL применяет алгебраические преобразования для упрощения
выражений и приведения их к каноническому виду. Она умеет так-же вычислять
константные выражения, исключая заведомо невыполнимые и всегда
выполняющиеся условия.
(5=5 AND a>5) -> a>5
(a < b AND b=c) AND a=5 -> b>5 AND b=c AND a=5
Оптимизации COUNT(), MIN() и MAX()
Наличие индексов и сведений о возможности хранения NULL-значений в
столбцах часто позволяет вообще не вычислять эти выражения. Если применена
такая оптимизация, то в плане, выведенном командой EXPLAIN, будет
присутствовать фраза «Select tables optimized away» (некоторые таблицы
исключены при оптимизации).
10. Вычисление и свертка константных выражений
Если MySQL обнаруживает, что выражение можно свернуть в константу, то
делает это на стадии оптимизации.
EXPLAIN SELECT film.film_id, film_actor.actor_id
FROM sakila.film
INNER JOIN sakila.film_actor USING(film_id)
WHERE film.film_id = 1;
| id | select_type | table | type | key | ref | rows |
| 1 | SIMPLE | film | const | PRIMARY | const | 1 |
| 1 | SIMPLE | film_actor | ref | idx_fk_film_id | const | 10 |
Покрывающие индексы
Если индекс содержит все необходимые запросу столбцы, то MySQL может
воспользоваться им, вообще не читая данные таблицы.
Оптимизация подзапросов
MySQL умеет преобразовывать некоторые виды подзапросов в более
эффективные эквивалентные формы, сводя их к поиску по индексу.
11. Раннее завершение
MySQL может прекратить обработку запроса (или какой-то шаг обработки), как
только поймет, что этот запрос или шаг полностью выполнен.
EXPLAIN SELECT film.film_id FROM sakila.film WHERE film_id = -1;
| id |...| Extra |
| 1 |...| Impossible WHERE noticed after reading const tables |
SELECT film.film_id
FROM sakila.film
LEFT OUTER JOIN sakila.film_actor USING(film_id)
WHERE film_actor.film_id IS NULL;
12. Распространение равенства
MySQL распознает ситуации, когда в некотором запросе два столбца должны
быть равны, – например, в условии JOIN, и распространяет условие WHERE на
эквивалентные столбцы.
SELECT film.film_id
FROM sakila.film
INNER JOIN sakila.film_actor USING(film_id)
WHERE film.film_id > 500;
... WHERE film.film_id > 500 AND film_actor.film_id > 500
Сравнение по списку IN( )
Во многих СУБД оператор IN() – не более чем синоним нескольких условий
OR, поскольку логически они эквивалентны. Но не в MySQL, здесь
перечисленные в списке IN()значения сортируются, и для работы с ним
применяется быстрый двоичный поиск.
13. SELECT tbl1.col1, tbl2.col2
FROM tbl1 INNER JOIN tbl2 USING(col3)
WHERE tbl1.col1 IN(5,6);
outer_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = outer_iter.next
while outer_row
inner_iter = iterator over tbl2 where col3 = outer_row.col3
inner_row = inner_iter.next
while inner_row
output [ outer_row.col1, inner_row.col2 ]
inner_row = inner_iter.next
end
outer_row = outer_iter.next
end
14. SELECT tbl1.col1, tbl2.col2
FROM tbl1 LEFT OUTER JOIN tbl2 USING(col3)
WHERE tbl1.col1 IN(5,6);
outer_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = outer_iter.next
while outer_row
inner_iter = iterator over tbl2 where col3 = outer_row.col3
inner_row = inner_iter.next
if inner_row
while inner_row
output [ outer_row.col1, inner_row.col2 ]
inner_row = inner_iter.next
end
else
output [ outer_row.col1, NULL ]
end
outer_row = outer_iter.next
end
15.
16.
17. • Двухпроходный (старый)
Читает указатели на строки и столбцы, упомянутые во фразе
ORDER BY, сортирует их, затем проходит по отсортированному
списку и снова читает исходные строки, чтобы вывести
результат.
• Однопроходный (новый)
Читает все необходимые запросу столбцы, сортирует строки по
столбцам, упомянутым во фразе ORDER BY, проходит по
отсортированному списку и выводит заданные столбцы.
Следовательно, в буфер сортировки поместится меньше строк и
надо будет выполнить больше циклов слияния.
18. SELECT * FROM sakila.film
WHERE film_id IN(
SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);
19. SELECT * FROM sakila.film
WHERE film_id IN(
SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);
SELECT GROUP_CONCAT(film_id)
FROM sakila.film_actor
WHERE actor_id = 1;
-- Result:
1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,
970,980
SELECT * FROM sakila.film
WHERE film_id
IN(1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,9
70,980);
20. SELECT * FROM sakila.film
WHERE film_id IN(
SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);
SELECT * FROM sakila.film
WHERE EXISTS (
SELECT * FROM sakila.film_actor WHERE actor_id = 1
AND film_actor.film_id = film.film_id);
22. SELECT film_id, actor_id FROM sakila.film_actor
WHERE actor_id = 1 OR film_id = 1;
SELECT film_id, actor_id FROM sakila.film_actor
WHERE actor_id = 1
UNION ALL
SELECT film_id, actor_id FROM sakila.film_actor
WHERE film_id = 1 AND actor_id <> 1;
27. SELECT MIN(actor_id) FROM sakila.actor
WHERE first_name = ‘PENELOPE’;
SELECT actor_id FROM sakila.actor USE INDEX(PRIMARY)
WHERE first_name = ‘PENELOPE’ LIMIT 1;
28. UPDATE tbl AS outer_tbl
SET cnt = (
SELECT count(*) FROM tbl AS inner_tbl
WHERE inner_tbl.type = outer_tbl.type
);
29. UPDATE tbl AS outer_tbl
SET cnt = (
SELECT count(*) FROM tbl AS inner_tbl
WHERE inner_tbl.type = outer_tbl.type
);
ERROR 1093 (HY000): You can’t specify target table
‘outer_tbl’ for update in FROM clause
30. UPDATE tbl AS outer_tbl
SET cnt = (
SELECT count(*) FROM tbl AS inner_tbl
WHERE inner_tbl.type = outer_tbl.type
);
ERROR 1093 (HY000): You can’t specify target table
‘outer_tbl’ for update in FROM clause
UPDATE tbl
INNER JOIN(
SELECT type, count(*) AS cnt
FROM tbl
GROUP BY type
) AS der USING(type)
SET tbl.cnt = der.cnt;
31. COUNT() – это особая функция, которая решает две очень
разные задачи: подсчитывает значения и строки.
Значение – это выражение, отличное от NULL.
COUNT(*) просто подсчитывает количество строк в
результирующем наборе.
Если вы хотите знать, сколько строк в результирующем наборе,
всегда употребляйте COUNT(*).
МИФЫ:
Для таблиц типа MyISAM запросы, содержащие функцию
COUNT(), выполняются очень быстро.
33. SELECT COUNT(*) FROM world.City WHERE ID > 5;
SELECT (SELECT COUNT(*) FROM world.City) - COUNT(*)
FROM world.City WHERE ID <= 5;
34. SELECT COUNT(*) FROM world.City WHERE ID > 5;
SELECT (SELECT COUNT(*) FROM world.City) - COUNT(*)
FROM world.City WHERE ID <= 5;
SELECT COUNT(color= ‘blue’ OR color= ‘red’) FROM items;
SELECT COUNT(*) FROM items
WHERE color= ‘blue’ AND color= ‘red’;
35. SELECT COUNT(*) FROM world.City WHERE ID > 5;
SELECT (SELECT COUNT(*) FROM world.City) - COUNT(*)
FROM world.City WHERE ID <= 5;
SELECT COUNT(color= ‘blue’ OR color= ‘red’) FROM items;
SELECT COUNT(*) FROM items
WHERE color= ‘blue’ AND color= ‘red’;
SELECT SUM(IF(color = ‘blue’, 1, 0)) AS blue,
SUM(IF(color = ‘red’, 1, 0)) -> AS red FROM items;
36. SELECT COUNT(*) FROM world.City WHERE ID > 5;
SELECT (SELECT COUNT(*) FROM world.City) - COUNT(*)
FROM world.City WHERE ID <= 5;
SELECT COUNT(color= ‘blue’ OR color= ‘red’) FROM items;
SELECT COUNT(*) FROM items
WHERE color= ‘blue’ AND color= ‘red’;
SELECT SUM(IF(color = ‘blue’, 1, 0)) AS blue,
SUM(IF(color = ‘red’, 1, 0)) -> AS red FROM items;
SELECT COUNT(color = ‘blue’ OR NULL) AS blue, COUNT(color = ‘red’
OR NULL) AS red FROM items;
37. SELECT COUNT(*) FROM world.City WHERE ID > 5;
SELECT (SELECT COUNT(*) FROM world.City) - COUNT(*)
FROM world.City WHERE ID <= 5;
SELECT COUNT(color= ‘blue’ OR color= ‘red’) FROM items;
SELECT COUNT(*) FROM items
WHERE color= ‘blue’ AND color= ‘red’;
SELECT SUM(IF(color = ‘blue’, 1, 0)) AS blue,
SUM(IF(color = ‘red’, 1, 0)) -> AS red FROM items;
SELECT COUNT(color = ‘blue’ OR NULL) AS blue, COUNT(color = ‘red’
OR NULL) AS red FROM items;
38. • Стройте индексы по столбцам, используемым во фразах ON
или USING.
• При добавлении индексов учитывайте порядок соединения.
В общем случае следует индексировать только вторую
таблицу в порядке соединения, если, конечно, индекс не
нужен для каких-то других целей.
• Старайтесь, чтобы в выражениях GROUP BY и ORDER BY
встречались столбцы только из одной таблицы, тогда у MySQL
появится возможность воспользоваться для этой операции
индексом.
• Будьте внимательны при переходе на новую версию MySQL,
поскольку в разные моменты изменялись синтаксис
соединения, приоритеты операторов и другие аспекты
поведения.
40. SELECT actor.first_name, actor.last_name, COUNT(*)
FROM sakila.film_actor
INNER JOIN sakila.actor USING(actor_id)
GROUP BY actor.first_name, actor.last_name;
SELECT actor.first_name, actor.last_name, COUNT(*)
FROM sakila.film_actor
INNER JOIN sakila.actor USING(actor_id)
GROUP BY film_actor.actor_id;
41. SELECT actor.first_name, actor.last_name, COUNT(*)
FROM sakila.film_actor
INNER JOIN sakila.actor USING(actor_id)
GROUP BY actor.first_name, actor.last_name;
SELECT MIN(actor.first_name), MAX(actor.last_name), COUNT(*)
FROM sakila.film_actor
INNER JOIN sakila.actor USING(actor_id)
GROUP BY film_actor.actor_id;
42. SELECT actor.first_name, actor.last_name, COUNT(*)
FROM sakila.film_actor
INNER JOIN sakila.actor USING(actor_id)
GROUP BY actor.first_name, actor.last_name;
SELECT actor.first_name, actor.last_name, c.cnt
FROM sakila.actor
INNER JOIN (
SELECT actor_id, COUNT(*) AS cnt
FROM sakila.film_actor
GROUP BY actor_id
) AS c USING(actor_id) ;
44. SELECT film_id, description
FROM sakila.film
ORDER BY title
LIMIT 50, 5;
SELECT film.film_id, film.description
FROM sakila.film
INNER JOIN (
SELECT film_id FROM sakila.film
ORDER BY title LIMIT 50, 5
) AS lim USING(film_id);
45. SELECT film_id, description
FROM sakila.film
ORDER BY title
LIMIT 50, 5;
SELECT film.film_id, film.description
FROM sakila.film
INNER JOIN (
SELECT film_id FROM sakila.film
ORDER BY title LIMIT 50, 5
) AS lim USING(film_id);
SELECT film_id, description FROM sakila.film
WHERE position BETWEEN 50 AND 54 ORDER BY position;
47. Кэш запросов
• Перед началом обработки запроса на чтение нужно
проверить, есть ли он в кэше
• Если запрос допускает кэширование, но еще не помещен в
кэш, то нужно потратить некоторое время на запись в кэш
сгенерированных результатов
• Наконец, при обработке любого запроса на запись
необходимо сделать недействительными все записи в кэше,
в которых встречается измененная таблица
50. Объединенные таблицы и
секционирование
Позволяют:
• Отделить статические данные от изменяющихся
• Воспользоваться физической близостью взаимосвязанных
данных для оптимизации запросов
• Проектировать таблицы так, чтобы запрос обращался к
возможно меньшему объему данных
• Упростить обслуживание очень больших наборов данных (в
этом вопросе объединенные таблицы обладают некоторыми
преимуществами по сравнению с секционированными)
51. Объединенные таблицы
CREATE TABLE t1(a INT NOT NULL PRIMARY KEY)
ENGINE=MyISAM;
CREATE TABLE t2(a INT NOT NULL PRIMARY KEY)
ENGINE=MyISAM;
INSERT INTO t1(a) VALUES(1),(2);
INSERT INTO t2(a) VALUES(1),(2);
CREATE TABLE mrg(a INT NOT NULL PRIMARY KEY)
ENGINE=MERGE UNION=(t1, t2) INSERT_METHOD=LAST;
SELECT a FROM mrg;
| a |
| 1 |
| 1 |
| 2 |
| 2 |
52. Объединенные таблицы
• Для объединенной таблицы необходимо больше открытых файловых
дескрипторов, чем для обычной таблицы, содержащей те же данные.
• Команда CREATE, которая создает объединенную таблицу, не
проверяет, совместимы ли ее составляющие. Если определения
объединяемых таблиц слегка различаются, то MySQL может создать
объединенную таблицу, которой впоследствии не сумеет
воспользоваться.
• Запросы, обращенные к объединенной таблице, переадресуются к
каждой из составляющих таблиц. В результате поиск единственной
строки может оказаться медленнее по сравнению с поиском в одной
таблице.
• Сканирование выполняется для объединенной таблицы так же
быстро, как для обычной
• Поиск по уникальному и первичному ключу прекращается, как только
искомая строка найдена.
• Составляющие таблицы читаются в порядке, указанном в команде
CREATE TABLE.
55. Секционирование
LIST
По точному списку значений
PARTITION BY LIST(store_id) (
PARTITION pNorth VALUES IN (3,5,6,9,17),
PARTITION pEast VALUES IN (1,2,10,11,19,20)
)
57. Секционирование
• Все секции должны управляться одной и той же подсистемой
хранения. Например, нельзя сжать только часть секций, хотя для
составляющих объединенной таблицы это допустимо.
• Любой уникальный индекс над секционированной таблицей
должен содержать столбцы, на которые ссылается функция
секционирования.
• Хотя сервер MySQL может обойтись без доступа к каждой секции
при обработке запроса к секционированной таблице, он тем
менее ставит блокировки на все секции.
• Существует ряд ограничений на функции и выражения, которые
можно использовать в механизме секционирования.
• Некоторые подсистемы хранения вообще не поддерживают
секционирование.
• Внешние ключи не работают.