Подходы к оптимизации производительности MySQL Григорий Рубцов MySQL AB / Sun Microsystems О чем доклад? • Имеем приложение, использующее MySQL. • Нагрузка выросла, производительность упала. • Что можно с этим сделать на уровне MySQL? План доклада (1/2) I. Диагностика. Насколько все плохо? II. Помогут ли индексы? III. Запрос №1: ORDER BY + LIMIT IV. Безобидные подзапросы План доклада (2/2) V. Где хранить? MyISAM, InnoDB, MEMORY, Cluster… VI. Кэш запросов MySQL VII. Структура БД – её надо менять?! VIII. Выводы Приобретение MySQL компанией Sun • • • Сделка завершилась в 2008 году Sun и MySQL совместными усилиями сделают продукты и услуги ближе к заказчику. – Корпоративная поддержка 24x7x365 – Больше поддерживаемых платформ – Профессиональные услуги и обучение Обе компании твердо стоят на позициях Open Source Миссия Sun/MySQL: Сделать доступную каждому высококлассную СУБД. I. Диагностика (1/2) • Внешние признаки – медленная работа приложения на сложных операциях (поиск, сортировка данных) – ошибка базы данных “too many connections” – mysql активно использует процессор (десятки процентов в top) I. Диагностика (2/2) • Преждевременная оптимизация – корень всех зол • Тони Хоар • Внутренние признаки – SHOW FULL PROCESSLIST; показывает много процессов в состоянии locked или просто много процессов – много медленных запросов в логе (требует параметров my.cnf) • log-slow-queries • long_query_time=1 Архитектура MySQL-сервера II. Индексы (B-tree) Root Node (non-leaf) Pointers to child leaf nodes 20 1 Keys 2-19 19 21 40 Keys 22-39 39 60 41 Leaf Nodes Keys 42-59 59 Pointers to data pages Key Values Actual Data Pages 61 Keys 62-79 79 II. Составные индексы • KEY(`фамилия`, `имя`) – аналог телефонной книги, нельзя искать по имени (сначала надо найти фамилию, а затем имя) • KEY(A,B,C) – позволяет: • WHERE A=10 AND B>10; • WHERE A=10 AND B=7 AND C=12; • WHERE A=10 AND B=7 ORDER BY C – не позволяет • WHERE B=10; • WHERE A=10 AND B>10 AND C>10 – Операция сравнения последняя при использовании индекса II. Типы индексов • • • • • B-TREE (MyISAM, MEMORY, ndbcluster) B+TREE (Innodb) HASH (MEMORY, ndbcluster) FULLTEXT (MyISAM) SPATIAL (MyISAM) • mysql> SHOW INDEX FROM brevno_sessions\G • • • • • • • • • • • • *************************** 1. row *************************** Table: brevno_sessions Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 244002 Sub_part: NULL Packed: NULL Null: Index_type: BTREE III. ORDER BY + LIMIT (1/8) • Запрос №1 среди медленных в рунете: – Выборка из большой таблицы – Условие WHERE или ORDER BY не может использовать ключи – Сортируется много записей, а выбирается несколько (например, LIMIT 20 ) III. ORDER BY + LIMIT (2/8) • Пример 1: – SELECT * FROM banners WHERE active AND btype IN (1, 3, 7) AND clicks < clickslim AND shows < showslim AND now() < date_end AND now() > date_start ORDER BY rand()*weight() LIMIT 1; III. ORDER BY + LIMIT (3/8) •Пример 2: SELECT DISTINCT * FROM wp_posts LEFT JOIN wp_post2cat ON (wp_posts.ID = wp_post2cat.post_id) LEFT JOIN wp_categories ON (wp_post2cat.category_id = wp_categories.cat_ID) WHERE 1=1 AND (category_id = '1') AND post_date_gmt <= '2008-09-12 00:15:59' AND (post_status = 'publish') AND post_status != 'attachment' GROUP BY wp_posts.ID ORDER BY post_date DESC LIMIT 3, 3; III. ORDER BY + LIMIT (4/8) • EXPLAIN SELECT … id: 1 select_type: SIMPLE table: wp_posts type: ref possible_keys: PRIMARY,post_status,post_date_gmt key: post_status key_len: 1 ref: const rows: 3450 Extra: Using where; Using temporary; Using filesort ....................... III. ORDER BY + LIMIT (5/8) • Как выполняется запрос? – Используется индекс post_status=‘publish’ – Перебор почти всей таблицы для проверки остальных условий – Временная таблица (в памяти, если меньше tmp_table_size), содержащая все поля таблиц, участвующих в JOIN – Cортировка всей временной таблицы (filesort, без использования индексов) – LIMIT 3,3 – оставляем записи с 4 по 6 III. ORDER BY + LIMIT (6/8) • Решение: разбить запрос •Сортировать только значения id •Наложить ограничение LIMIT •Получить значения остальных полей • Схематическое решение: • SELECT * FROM large_table WHERE id IN • (SELECT id FROM large_table WHERE условие • AND условие AND условие ORDER BY порядок LIMIT M,N) ORDER BY порядок; • IN + LIMIT будет только в MySQL 6.0 III. ORDER BY + LIMIT (7/8) • • • • • • • • • • • Пример полного решения CREATE TEMPORARY TABLE tmp_postid ENGINE=MEMORY SELECT wp_posts.ID FROM wp_posts LEFT JOIN wp_post2cat ON (wp_posts.ID = wp_post2cat.post_id) WHERE (category_id = '1') AND post_date_gmt <= '2008-09-12 00:15:59‘ AND (post_status = 'publish') AND post_status != 'attachment‘ ORDER BY post_date DESC LIMIT 3, 3; SELECT * FROM tmp_postid t JOIN wp_posts USING(ID) LEFT JOIN wp_post2cat ON (wp_posts.ID = wp_post2cat.post_id) ORDER BY post_date DESC; DROP TEMPORARY TABLE tmp_postid; III. ORDER BY + LIMIT (8/8) • Особенности решения – Сортировать приходится дважды • Можно обойти, сохраняя порядок в первом запросе • SET @order:=0; SELECT id, @order:=@order+1 AS ord .. – SELECT превращается в INSERT+SELECT • Временную таблицу можно создавать в другой базе данных, чтобы избежать репликации или использовать SET SQL_LOG_BIN:=0; • При небольшом количестве записей список id можно собирать в языке программирования или в переменной MySQL IV. Безобидные подзапросы • Три типа подзапросов – В контексте выбираемых полей (обычно зависимый) SELECT clients.id, (SELECT count(*) FROM contracts WHERE contracts.client=clients.id) FROM clients; – В контексте FROM (независимый подзапрос) SELECT * FROM clients,(SELECT a FROM b) AS stats; – В условии WHERE (зависимый или независимый) • SELECT * FROM clients WHERE EXISTS(SELECT 1 FROM contracts WHERE contracts.client=clients.id) IV. Оптимизация подзапросов • Мощный встроенный механизм оптимизации появится в MySQL 6.0 • http://forge.mysql.com/wiki/Subquery_Works • Зависимые подзапросы, если возможно, преобразовывать в JOIN • Некоторые независимые подзапросы обрабатываются как зависимые V. Механизмы хранения • MyISAM • InnoDB • MEMORY • ndbcluster V. MyISAM • Индекс и данные в отдельных файлах – table.MYI файл ссылается на конкретные позиции в файле table.MYD • Блокировки на уровне таблиц – SELECT может устанавливать блокировку READ LOCAL, разрешая запись в конец таблицы SET GLOBAL concurrent_insert=x • x=0, блокировка READ • x=1, блокировка READ LOCAL, если нет щелей • x=2, блокипрвка READ LOCAL в любом случае V. MyISAM Key Cache SHOW VARIABLES LIKE 'key_buffer_size'; SHOW STATUS LIKE 'key_blocks%'; V. Индивидуальный кэш индексов • Установка – SET GLOBAL hot_cache.key_buffer_size=128*1024; • Загрузка индексов таблиц в кэш 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 | +---------+--------------------+----------+----------+ V. InnoDB • Поддержка транзакций (ACID) • Блокировки на уровне строк • innodb_flush_log_at_trx_commit – 0 – сбрасывать логи на диск на каждой контрольной точке (примерно раз в секунду) – 1 – сбрасывать логи при завешении каждой транзакции – 2 – сбрасывать логи при завершении каждой транзакции, но fsync выполнять только на контрольной точке V. Innodb buffer pool Переменная innodb_buffer_pool_size V. MEMORY •+ • Хранение таблиц в памяти (до max_heap_table_size на таблицу, но не больше 4 Гб) • Быстрый доступ к данным • Два типа индексов (HASH и BTREE) •• Данные не сохраняются при остановке сервера • Нельзя использовать BLOB/TEXT • Все строки фиксированной длины, VARCHAR(4000) будет работать как CHAR(4000) V. MySQL Cluster VI. Кэш запросов MySQL VI. Кэш запросов MySQL • Не зависит от механизма хранения • Только буквально идентичные запросы • Подзапросы не кэшируются отдельно • Функции NOW(), RAND(), и др., пользовательские переменные и хранимые функции отводят запрос от кэша • Включить кэш: – query_cache_type=1 (значение 2 включает кэш по требованию) – query_cache_size=128M VI. Кэш запросов MySQL • Любое изменение таблицы, удаляет из кэша все запросы, использующие данную таблицу. Пример: • UPDATE articles • SET viewcount=viewcount+1 • WHERE id=542; • • Переносите счетчики в отдельные таблицы! • Использованием кэша можно управлять: – SELECT SQL_NO_CACHE … – SELECT SQL_CACHE … VII. Менять структуру данных?! Учитывая вашу репутацию, я бы не обратился к вам, если бы у меня был выбор. - Из фильма «Револьвер» - - • Очень серьезное изменение кода приложения • У скольких присутствующих в зале, приложение использует только инвариантные процедуры? VII. (Де)?нормализация • Нормализация – исключение избыточности в данных • Денормализация – внесение избыточной информации Inventory sID sLoc sPostal pID1 pName1 pQty1 pID2 pName2 pQty2 1 Holtsville 00501 1 bed 15 2 chair 4 2 3 4 Waukesha Waukesha Ketchikan 53146 53146 99950 1 2 2 bed chair chair 4 8 24 3 4 4 table sofa sofa 6 4 10 1NF Inventory_1NF sPostal pID sID sLoc pName pQty 1 Holtsville 00501 1 bed 15 1 Holtsville 00501 2 chair 4 2 2 Waukesha Waukesha 53146 53146 1 3 bed table 4 6 3 3 4 Waukesha Waukesha Ketchikan 53146 53146 99950 2 4 2 chair sofa chair 8 4 24 4 Ketchikan 99950 4 sofa 10 VII. Третья нормальная форма • Нет функциональной зависимости колонок, каждая зависит только от первичногно ключа. Part_3NF Part_1NF sID 1 1 pID 1 2 pName bed chair pQty 15 4 2 2 1 3 bed table 4 6 3 2 chair 8 3 4 4 4 2 4 sofa chair sofa 4 24 10 3NF 3NF sID 1 1 2 pID 1 2 1 Qty 15 4 4 2 3 3 3 2 4 6 8 4 4 4 2 4 24 10 PartName_3NF pID pName 1 bed 2 chair 3 table 4 sofa VII. Денормализация • Выполните полную нормализацию перед тем, как начинать денормализацию • Избыточные поля в той же таблице – ЗА • Не потребуется JOIN – Против • Триггер не может изменять собственную таблицу • UPDATE сбрасывает кэш запросов VIII. Выводы. Оптимизация запросов • Что делает запрос и как он будет выполняться? (EXPLAIN поможет) • Какие индексы он будет использовать? • Как написать этот же запрос проще? • Безобидны ли подзапросы? • Будет ли запрос кэшироваться? • Если ничего не помогает – оптимизируйте структуру таблиц. Заключение • Лучше день потерять, • потом за пять минут долететь. • • Спасибо за внимание! Пишите: rgbeast@sqlinfo.ru, http://sqlinfo.ru/forum/