Шпоргалка по MySQL (rev.1)
Шпаргалка по типовым, а также редко используемым операциям в MySQL||MariaDB, позволяющая не заглядывать в объемный справочник, по тривиальным вопросам.
- Базы данных и пользователи
- Создать базу
- Удалить базу
- Создать пользователя
- Выдать пользователю полные права на базу
- Удалить пользователя
- Атомарно создать базу и пользователя для нее
- Вывести список баз, доступных пользователю
- Вывести список таблиц конкретной базы данных
- Вычислить размер баз, доступных пользователю
- Вычислить размер таблиц в базе данных
- Узнать кодировку базы данных
- Сохранить результат выборки в файл
- Работа с дампами
- Работа с JSON
- Отладка запросов
1. Базы данных и пользователи
1.1 Создать базу
CREATE DATABASE `<dbname>` CHARACTER SET utf8_mb4 COLLATE utf8_mb4_general_ci;
1.2 Удалить базу
DROP DATABASE `<dbname>`;
1.3 Создать пользователя
CREATE USER '<username>'@'<localhost>' IDENTIFIED BY '<password>';
1.4 Выдать пользователю полные права на базу
GRANT ALL PRIVILEGES ON <dbname>.* TO '<username>'@'<localhost>';
FLUSH PRIVILEGES;
1.5 Удалить пользователя
DROP USER '<username>'@'<localhost>';
DROP USER
не закрывает ранее открытые сессии пользователя. Однако попытка открыть новую сесию, от ранее удаленного пользователя, завершится ошибкой авторизации. Это штатное поведение, которое нужно иметь в виду.
1.6 Атомарно создать базу и пользователя для нее
BEGIN;
CREATE DATABASE `<dbname>` CHARACTER SET utf8_mb4 COLLATE utf8_mb4_general_ci;
CREATE USER '<username>'@'<localhost>' IDENTIFIED BY '<password>';
GRANT ALL PRIVILEGES ON <dbname>.* TO '<username>'@'<localhost>';
FLUSH PRIVILEGES;
COMMIT;
1.7 Вывести список баз, доступных пользователю
$ mysqlshow -h HOST -u USER -pPASSWORD
1.8 Вывести список таблиц конкретной базы данных
$ mysqlshow -h HOST -u USER -pPASSWORD DATABASE
1.9 Вычислить размер баз, доступных пользователю
SELECT t.Database,
t.Bytes,
ROUND(t.Bytes / 1024, 2) AS 'KiB',
ROUND(t.Bytes / 1024 / 1024, 2) AS 'MiB',
ROUND(t.Bytes / 1024 / 1024 / 1024, 2) AS 'GiB'
FROM (
SELECT table_schema AS 'Database',
SUM(data_length + index_length) AS 'Bytes'
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'performance_schema')
GROUP BY table_schema
) AS t
ORDER BY t.Bytes DESC;
В запросе исключаются служебные базы (
information_schema
,performance_schema
). Если данные по ним нужны — уберите соответствующее WHERE-выражение.
1.10 Вычислить размер таблиц в базе данных
SELECT table_name AS 'Table',
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'MiB'
FROM information_schema.TABLES
WHERE table_schema = '<dbname>';
1.11 Узнать кодировку базы данных
SELECT DEFAULT_CHARACTER_SET_NAME AS 'Charset',
DEFAULT_COLLATION_NAME AS 'Collation'
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = '<dbname>';
1.12 Сохранить результат выборки в файл
SELECT ...
FROM <table>
WHERE ...
INTO OUTFILE '/tmp/my_select_result.txt';
Или в виде csv:
SELECT ...
FROM <table>
WHERE ...
INTO OUTFILE '/tmp/my_select_result.txt'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n';
Или csv с названиями полей:
SELECT 'field1', 'field2', 'field3'
UNION ALL
SELECT field1, field2, field3
FROM <table>
WHERE ...
INTO OUTFILE '/tmp/my_select_result.txt'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n';
Если в запросе на выборку используется сортировка, то названия заголовков станут частью данных, которые будут упорядоченны (т.е. названия csv-столбцов могут оказаться не первой строкой). Чтобы такого не произошло, можно воспользоваться подзапросом:
SELECT *
FROM (
SELECT 'field1', 'field2', 'field3'
UNION ALL
SELECT field1, field2, field3
FROM <table>
ORDER BY field2 ASC
) AS sub
INTO OUTFILE '/tmp/my_select_result.txt';
2 Работа с дампами
Создание текстового дампа (*.sql) — способ, приемлемо работающий для небольших баз (до 10-20 гигабайт, в зависимости ОТ). Для создания дампа баз бОльшего объема, следует использовать специализированные инструменты, работающие с бинарными данными.
2.1 Создать дамп
$ mysqldump -h HOST -u USER -pPASSWORD DATABASE > DATABASE.sql
2.2 Создать сжатый дамп
$ mysqldump -h HOST -u USER -pPASSWORD DATABASE | gzip -9 > DATABASE.sql.gz
2.3 Создать дамп схемы
Создается дамп только со структурой таблиц указанной базы, БЕЗ самих данных.
$ mysqldump --no-data -h HOST -u USER -pPASSWORD DATABASE > DATABASE_schema.sql
2.4 Создать дамп только заданных таблиц
$ mysqldump -h HOST -u USER -pPASSWORD DATABASE TABLE1 TABLE2 > DATABASE_table1_table2.sql
2.5 Создать дамп с указанием текущей даты-времени
$ mysqldump -h HOST -u USER -pPASSWORD DATABASE > `date +DATABASE_%Y%m%d_%H%M%S.sql`
2.6 Создать оптимизированный дамп
$ mysqldump --skip-lock-tables \
--single-transaction \
-h HOST -u USER -pPASSWORD DATABASE \
| gzip -9 > `date +DATABASE_%Y%m%d_%H%M%S.sql.gz`
где:
--skip-lock-tables
— не блокирует таблицы на чтение/запись во время создания дампа;
--single-transaction
— создает дамп в рамках единой транзакции (состояние БД "замораживается" на момент создания дампа), для обеспечения гарантированной консистентности данных.
2.7 Загрузить дамп
$ mysql -h HOST -u USER -pPASSWORD DATABASE < DATABASE.sql
2.8 Загрузить сжатый дамп
$ gunzip < DATABASE.sql.gz | mysql -h HOST -u USER -pPASSWORD DATABASE
или
$ zcat DATABASE.sql.gz | mysql -h HOST -u USER -pPASSWORD DATABASE
3. Работа с JSON
3.1 Перечень функций
->
Получить значение из JSON-объекта (или столбца) по указанному path (эквивалент функции JSON_EXTRACT()
):
SELECT column->'$.key' ...
-- "value"
->>
Получить значение по заданному JSON-path и выполнить деквотирование (удаление кавычек). Эквивалентно комбинации функций JSON_UNQUOTE(JSON_EXTRACT())
:
SELECT column->>"$.key" ...
-- value
JSON_ARRAY([val[, val] ...])
Создать JSON-массив из переданных в качестве аргументов значений:
SELECT JSON_ARRAY(1, 2, 3, "4", 5.0);
-- [1, 2, 3, "4", 5.0]
JSON_ARRAY_APPEND(json_doc, path, val[, path, val] ...)
Добавить элемент в конец JSON-массива:
SET @j = '[1, 2, 3]';
SELECT JSON_ARRAY_APPEND(@j, '$', 4);
-- [1, 2, 3, 4]
SELECT JSON_ARRAY_APPEND(@j, '$[2]', 4);
-- [1, 2, [3, 4]]
JSON_ARRAY_INSERT(json_doc, path, val[, path, val] ...)
Вставить элемент в указанную позицию JSON-массива (при этом все последующие элементы автоматически сдвигаются вправо):
SET @j = '[1, 2, 3]';
SELECT JSON_ARRAY_INSERT(@j, '$[2]', 4);
-- [1, 2, 4, 3]
JSON_CONTAINS(target, candidate[, path])
Проверить, содержится ли в документе искомое ЗНАЧЕНИЕ по указанному JSON-path (возвращает 1 в случае успеха и 0 в противном случае):
SET @j = '{"a": 100, "b": 500}';
SELECT JSON_CONTAINS(@j, '100', '$.a');
-- 1
SELECT JSON_CONTAINS(@j, '500', '$.a');
-- 0
JSON_CONTAINS_PATH(json_doc, one_or_all, path[, path] ...)
Проверить, содержит ли документ искомый "path" внутри себя. Данная проверка имеет два режима работы: "one" — проверяет есть ли в документе хотя бы один из искомых path, режим 'all' — проверяет наличие ВСЕХ искомых path's в документе.
SET @j = '{"a": 100, "b": 500}';
SELECT JSON_CONTAINS_PATH(@j, 'one', '$.a');
-- 1
SELECT JSON_CONTAINS_PATH(@j, 'all', '$.a', '$.b');
-- 1
SELECT JSON_CONTAINS_PATH(@j, 'all', '$.a', '$.b', '$.c');
-- 0
JSON_DEPTH(json_doc)
Возвращает коэффициент максимальной глубины JSON-документа:
SET @j = '{}';
SELECT JSON_DEPTH(@j);
-- 1
SET @j = '{"a": {"b": [{"c": {"d": [1]}}]}}';
SELECT JSON_DEPTH(@j);
-- 7
JSON_EXTRACT(json_doc, path[, path] ...)
Получить значение из JSON-объекта (или столбца) по указанному path (эквивалент функции ->
):
SET @j = '[1, [2, 3]]';
SELECT JSON_EXTRACT(@j, '$[0]');
-- 1
SELECT JSON_EXTRACT(@j, '$[1][*]');
-- [2, 3]
SELECT JSON_EXTRACT(@j, '$[1][1]');
-- 3
-- Для выборки данных из JSON-столбца
SELECT JSON_EXTRACT(json_column, '$.foo') ...
-- "bar"
JSON_INSERT(json_doc, path, val[, path, val] ...)
Вставить данные в документ по указанному path:
SET @j = '{"a": 1, "b": [2, 3]}';
SELECT JSON_INSERT(@j, '$.a', 10, '$.c', '[true, false]');
-- {"a": 1, "b": [2, 3], "c": "[true, false]"}
Пары path, val для существующего в документе path — не изменяется. Для изменения/обновления данных следует использовать функции
JSON_REPLACE()
илиJSON_SET()
.
JSON_KEYS(json_doc[, path])
Получить массив ключей документа, относительно указанного path:
SET @j = '{"a": 1, "b": 2, "c": {"d": 4}}';
SELECT JSON_KEYS(@j);
-- ["a", "b", "c"]
SELECT JSON_KEYS(@j, '$.c');
-- ["d"]
JSON_LENGTH(json_doc[, path])
Получить длину (количество пар "ключ-значение") JSON-документа, относительно указанного path:
SET @j = '{"a": 1, "b": 2, "c": {"d": 4}}';
SELECT JSON_LENGTH(@j);
-- 3
SELECT JSON_LENGTH(@j, '$.c');
-- ["d"]
JSON_MERGE_PATCH(json_doc, json_doc[, json_doc] ...)
Объединить документы JSON, заменив значения повторяющихся ключей:
SELECT JSON_MERGE_PATCH('[1, 2]', '[true, false]');
-- [true, false]
SELECT JSON_MERGE_PATCH('{"name": "x"}', '{"id": 47}');
-- {"id": 47, "name": "x"}
SELECT JSON_MERGE_PATCH('{ "a": 1, "b":2 }', '{ "a": 3, "c":4 }');
-- {"a": 3, "b": 2, "c": 4}
SELECT JSON_MERGE_PATCH(
'{ "a": 1, "b":2 }',
'{ "a": 3, "c":4 }',
'{ "a": 5, "d":6 }'
);
-- {"a": 5, "b": 2, "c": 4, "d": 6}
JSON_MERGE_PRESERVE(json_doc, json_doc[, json_doc] ...)
Объединить документы JSON, сохранив значения повторяющихся ключей:
SELECT JSON_MERGE_PRESERVE('[1, 2]', '[true, false]');
-- [1, 2, true, false]
SELECT JSON_MERGE_PRESERVE('{"name": "x"}', '{"id": 47}');
-- {"id": 47, "name": "x"}
SELECT JSON_MERGE_PRESERVE('{ "a": 1, "b": 2 }', '{ "a": 3, "c": 4 }');
-- {"a": [1, 3], "b": 2, "c": 4}
SELECT JSON_MERGE_PATCH(
'{ "a": 1, "b":2 }',
'{ "a": 3, "c":4 }',
'{ "a": 5, "d":6 }'
);
-- {"a": [1, 3, 5], "b": 2, "c": 4, "d": 6}
JSON_OBJECT([key, val[, key, val] ...])
Создать JSON-объект:
SELECT JSON_OBJECT('id', 123, 'foo', 'bar', 'spam', JSON_OBJECT('egg', 1));
-- {"id": 123, "foo": "bar", "spam": {"egg": 1}}
JSON_PRETTY(json_val)
Распечатать JSON-документ в человекочитаемом формате:
SELECT JSON_PRETTY("[1,3,5]");
/*
[
1,
3,
5
]
*/
SELECT JSON_PRETTY('{"a":"10","b":"15","x":"25"}');
/*
{
"a": "10",
"b": "15",
"x": "25"
}
*/
JSON_QUOTE(string)
Квотировать (обрамить кавычками, экранируя вложенные) JSON-документ:
SELECT JSON_QUOTE('null'), JSON_QUOTE('"null"');
-- "null" | "\"null\""
SELECT JSON_QUOTE('[1, 2, 3]');
-- "[1, 2, 3]"
JSON_REMOVE(json_doc, path[, path] ...)
Удалить данные из JSON-документа по указанному path:
SET @j = '["a", ["b", "c"], "d"]';
SELECT JSON_REMOVE(@j, '$[1]');
-- ["a", "d"]
SET @j = '{"a": 1, "b": 2}';
SELECT JSON_REMOVE(@j, '$.a');
-- {"b": 2}
JSON_REPLACE(json_doc, path, val[, path, val] ...)
Заменить существующие значения в JSON-документе:
JSON_REPLACE() — работает только с существующими значениями, новые (ранее отсутствующие) значения не будут добавлены.
SET @j = '{ "a": 1, "b": [2, 3]}';
SELECT JSON_REPLACE(@j, '$.a', 10, '$.b[1]', 5, '$.c', '[true, false]');
-- {"a": 10, "b": [2, 5]}
JSON_SEARCH(json_doc, one_or_all, search_str[, escape_char[, path] ...])
Возвращает path в документе JSON по искомой строке.
Возвращает NULL, если: любой из аргументов json_doc, search_str или path равен NULL; в документе нет указанного path; или search_str не найден.
SET @j = '["abc", [{"k": "10"}, "def"], {"x":"abc"}, {"y":"bcd"}]';
SELECT JSON_SEARCH(@j, 'one', 'abc');
-- "$[0]"
SELECT JSON_SEARCH(@j, 'all', 'abc');
-- ["$[0]", "$[2].x"]
JSON_SEARCH(@j, 'all', 'ghi')
-- NULL
SELECT JSON_SEARCH(@j, 'all', '10');
-- "$[1][0].k"
SELECT JSON_SEARCH(@j, 'all', '10', NULL, '$');
-- "$[1][0].k"
SELECT JSON_SEARCH(@j, 'all', '10', NULL, '$[*]');
-- "$[1][0].k"
SELECT JSON_SEARCH(@j, 'all', '10', NULL, '$**.k');
-- "$[1][0].k"
SELECT JSON_SEARCH(@j, 'all', '10', NULL, '$[*][0].k');
-- "$[1][0].k"
SELECT JSON_SEARCH(@j, 'all', '10', NULL, '$[1]');
-- "$[1][0].k"
SELECT JSON_SEARCH(@j, 'all', '10', NULL, '$[1][0]');
-- "$[1][0].k"
SELECT JSON_SEARCH(@j, 'all', 'abc', NULL, '$[2]');
-- "$[2].x"
SELECT JSON_SEARCH(@j, 'all', '%a%');
-- ["$[0]", "$[2].x"]
SELECT JSON_SEARCH(@j, 'all', '%b%');
-- ["$[0]", "$[2].x", "$[3].y"]
SELECT JSON_SEARCH(@j, 'all', '%b%', '', '$[1]');
-- NULL
SELECT JSON_SEARCH(@j, 'all', '%b%', '', '$[3]');
-- "$[3].y"
JSON_SET(json_doc, path, val[, path, val] ...)
Вставить данные в JSON-документ:
SET @j = '{ "a": 1, "b": [2, 3]}';
SELECT JSON_SET(@j, '$.a', 10, '$.c', '[true, false]');
-- {"a": 10, "b": [2, 3], "c": "[true, false]"}
SELECT JSON_INSERT(@j, '$.a', 10, '$.c', '[true, false]');
-- {"a": 1, "b": [2, 3], "c": "[true, false]"}
SELECT JSON_REPLACE(@j, '$.a', 10, '$.c', '[true, false]');
-- {"a": 10, "b": [2, 3]}
Функции
JSON_SET()
,JSON_INSERT()
, иJSON_REPLACE()
связаны:JSON_SET()
— заменяет существующие и добавляет несуществущие значенияJSON_INSERT()
— вставляет новые значения, без изменения существующихJSON_REPLACE()
— заменяет только существующие значения (новые не добавляет)
SELECT JSON_SET(@j, '$.a', 10, '$.c', '[true, false]');
-- {"a": 10, "b": [2, 3], "c": "[true, false]"}
SELECT JSON_INSERT(@j, '$.a', 10, '$.c', '[true, false]');
-- {"a": 1, "b": [2, 3], "c": "[true, false]"}
SELECT JSON_REPLACE(@j, '$.a', 10, '$.c', '[true, false]');
-- {"a": 10, "b": [2, 3]}
JSON_STORAGE_SIZE(json_val)
Функция возвращает объем (в байтах) данных, необходимых для хранения документа JSON:
CREATE TABLE jtable (jcol JSON);
INSERT INTO jtable VALUES ('{"a": 1000, "b": "wxyz", "c": "[1, 3, 5, 7]"}');
SELECT JSON_STORAGE_SIZE(jcol) FROM jtable;
-- 47
JSON_TYPE(json_val)
Возвращает строку (в кодировке utf8mb4
), указывающую на тип значения JSON (объект, массив или скаляр):
SET @j = '{"a": [10, true], "b": null}';
SELECT JSON_TYPE(@j);
-- OBJECT
SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a'));
-- ARRAY
SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a[0]'));
-- INTEGER
SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a[1]'));
-- BOOLEAN
SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.b'));
-- NULL
JSON_UNQUOTE(json_val)
Функция деквотирует JSON-значение и возвращает результат в виде utf8mb4-строки:
SET @j = '"abc"';
SELECT @j, JSON_UNQUOTE(@j);
-- abc
JSON_VALID(val)
Проверяет, является ли переданное значение валидным JSON, возвращает 1 или 0 соответственно:
SELECT JSON_VALID('{"a": 1}');
-- 1
SELECT JSON_VALID('hello'), JSON_VALID('"hello"');
-- 0 | 1
3.2 Создание таблицы с JSON-столбцом
CREATE TABLE <table> (<json_column> JSON);
3.3 Вставка
Для представления json-объекта и json-массива, можно использовать как строковое представление, обрамленное в одинарные кавычки, так и специализированные функции —
JSON_OBJECT
иJSON_ARRAY
соответственно.
INSERT INTO <table> (<json_column>)
VALUES '{"foo": "bar", "pi": 3.14, "second_level": {"key": [1, 2, 3]}}';
или:
INSERT INTO <table> (<json_column>)
VALUES JSON_OBJECT(
"foo", "bar",
"pi", 3.14,
"second_level", JSON_OBJECT(
"key", JSON_ARRAY(1, 2, 3)
)
);
3.4 Выборка
Для доступа к значению json-обекта по ключу можно использовать как короткий синтаксис —
->
, так и функциюJSON_EXTRACT
.
SELECT <field>
FROM <table>
WHERE <json_column>->"$.<first_level_key>" = <some_value>;
или:
SELECT <field>
FROM <table>
WHERE JSON_EXTRACT(<json_column>, "$.<first_level_key>") = <some_value>;
3.5 Обновление/замена
UPDATE <table>
SET <json_column> = JSON_SET(<json_column>, "$.<first_level_key>", <new_value>)
WHERE <statement>;
3.6 Удаление
UPDATE <table>
SET <json_column> = JSON_REMOVE(<json_column>, "$.<first_level_key>")
WHERE <statement>;
3.7 Сравнение и сортировка
JSON-значения могут сравниваться с помощью операторов: =, <, <=, >, >=, <>, != и <=>. Однако следующие операторы и функции НЕ работают с JSON-значениями: BETWEEN, IN(), GREATEST(), LEAST(), ORDER BY. Поэтому, JSON-значения в данных операторах и функциях должны быть предварительно конвретированы в примитивы с помощью функции CAST().
...
ORDER BY CAST(JSON_EXTRACT(jdoc, '$.id') AS UNSIGNED)
4. Отладка запросов
Нижеописанные средства анализа, вкупе со средствами логирования, позволяют достаточно быстро и эффективно находить проблемные места в коде клиентского приложения, при работе с СУБД.
4.1 Логирование запросов
Включенное логирования дает информацию о том, какие именно запросы, и в каком количестве, генерирует тот или иной клиент (клиентское приложение). Особенно актуально, если в приложении для работы с БД используется ORM или иная, схожая по концепции, абстракция.
Просмотр лога — первый этап при отладке запросов. Им же может все и закончиться, так как откровенная дичь, как правило, заметна сразу. По виду бажного запроса, можно быстро найти соответствующий ему код в приложении, и исправить его.
Включение логирования запросов в таблицу:
SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';
Или в файл:
SET GLOBAL log_output = 'FILE';
Назание файла для логирования запросов можно задать либо в конфигурации сервера (my.conf:
general_log_file = <filepath>
), либо интерактивно черезSET GLOBAL general_log_file='<filepath>'
. По умолчанию логи записываются в файл:/var/log/mysql/mysql.log
.
Способ включения табличных логов является предпочтительным, так как не требует доступа к файловой системе, и позволяет делать выборки целевых запросов с фильтрацией по нескольким критериям, без более сложного парсинга файлов. Также не стоит забывать о возможности перенаправить результат любого запроса в файл.
Структура таблицы mysql.general_log
:
Field | Type | Null | Key | Default | Extra |
---|---|---|---|---|---|
event_time | timestamp(6) | NO | current_timestamp(6) | on update current_timestamp(6) | |
user_host | mediumtext | NO | NULL | ||
thread_id | bigint(21) unsigned | NO | NULL | ||
server_id | int(10) unsigned | NO | NULL | ||
command_type | varchar(64) | NO | NULL | ||
argument | mediumtext | NO | NULL |
Выборка запросов к конкретной базе:
SELECT * FROM mysql.general_log;
SELECT * FROM mysql.general_log WHERE argument LIKE 'databasename%';
Выборка запросов в интересующем временном интервале:
SELECT * FROM mysql.general_log
WHERE event_time > (NOW() - INTERVAL 10 SECOND)
AND argument LIKE 'databasename%'
...
Если логов слишком много, и запрос на выборку начинает тормозить, — можно периодически чистить таблицу:
TRUNCATE TABLE mysql.general_log;
По завершению работ, логи следует отключить:
SET GLOBAL general_log = 'OFF';
4.2 Анализ запросов
Чтобы проанализировать запрос на семантику, и узнать как этот запрос интерпретирует внутри себя СУБД, достаточно просмотреть план запроса, применив оператор EXPLAIN.
EXPLAIN работает с запросами: SELECT, UPDATE, INSERT, DELETE, REPLACE и предоставляет информацию о типе запроса, используемых в запросе таблицах, используемых индексах, ключах, количестве прочитанных и отфильтрованных строк и т.д.
Кратко про колонки в выводе команды EXPLAIN:
Колонка | Краткое описание |
---|---|
id | Идентификатор запроса. |
select_type | Тип запроса SELECT: SIMPLE, SUBQUERY, DERIVED, UNION, UNION RESULT, MATERIALIZED. |
table | Название таблицы. |
partitions | Название используемой партиции (если не используется - null). |
type | ALL, index, range, index_subquery, unique_subquery, index_merge, ref_or_null, fulltext, ref, eq_ref, const, system, NULL |
possible_keys | Показывает, какие индексы можно, потенциально, использовать для запроса. |
key | Указывает на ключ (индекс), который оптимизатор MySQL решил использовать фактически. |
key_len | Показывает длину выбранного ключа (индекса) в байтах. |
ref | Показывает, какие столбцы или константы сравниваются с указанным в key индексом. |
rows | Показывает примерное количество строк, которые будут прочитаны. |
filtered | Показывает, какую долю (т.е. %) от общего количества просмотренных строк вернет движок СУБД. |
Extra | Различная доп. инфа: предупреждения, подсказки и проч. |
Описание всех возможных значений в выхлопе EXPLAIN выходит за рамки данной шпаргалки. Тот случай, когда лучше прочитать офф. документацию к вашей версии СУБД.
Здесь, можно ограничиться лишь общим подходом:
хорошо, когда в выводе EXPLAIN используются ожидаемые таблицы, индексы, кеш, количество прочитанных и отфильтрованных строк — не зашкаливающее, а в значении Extra нет негативных сообщений или предупреждений;
плохо, когда в выводе EXPLAIN большое количество rows, запрос строится не от той таблицы (актуально при неправильно сконструированном JOIN или подзапросах), не используются индексы, кеш, перебираются все записи, а в Extra содержатся ворнинги.
4.3 Профилирование запросов
Профилирование позволяет оценить сложность и время выполнения запроса. Для этого применяется выражение EXPLAIN ANALYZE
. Применяться оно к тем же операторам, что и EXPLAIN.
Схематично, вывод команды можно представить в виде дерева, каждый узел которого оформлен строкой, вида:
-> [операция] (используемые ключи) (оценка стоимости) (фактические время выполнения операции)
Пример вывода EXPLAIN ANALIZE:
-> Nested loop inner join (cost=1.05 rows=1) (actual time=0.152..0.152 rows=0 loops=1)
-> Nested loop inner join (cost=0.70 rows=1) (actual time=0.123..0.123 rows=0 loops=1)
-> Index scan on Drivers using Drivers_car_id_index (cost=0.35 rows=1) (actual time=0.094..0.094 rows=0 loops=1)
-> Index lookup on Orders using Orders_Drivers_id_fk (driver_id=Drivers.id) (cost=0.35 rows=1) (never executed)
-> Single-row index lookup on Clients using PRIMARY (id=Orders.client_id) (cost=0.35 rows=1) (never executed)
Каждая вложенная строка означает дополнительное действие/цикл.
cost
— некая внутренняя оценка (стоимость) того, насколько "дорого" для СУБД выполнять то или иное действие (больше — хуже).
actual time
— фактическое время получения первой строки и фактическое время получения всех строк, в формате actual time={время получения первой строки}..{время получения всех строк}
.
rows
— фактическое количество прочитанных/обработанных строк.
loops
— количество циклов, которые будут выполнены для соединения с внешней таблицей (выше по дереву). Если не потребовалось ни одной итерации цикла, будет отображено never executed
.