MySQL суроо-талаптарын кантип тездетүү керек: EXPLAIN жана индекстер (2015)
Кыскача: Жай суроо-талапты EXPLAIN менен текшериңиз. Эгер type: ALL (толук таблицаны сканерлөө) же rows: 100000+ болсо — индекс керек. CREATE INDEX idx_name ON table(column) кошуп, EXPLAIN'ди кайра иштетиңиз. type: ref же range болуп калышы керек. Биздин мисалда: 8 секунд → 40 мс.
Реалдуу мисал: эмне болду
2015-жылы бир кардарыбыздын онлайн-дүкөнү жайлай баштады. Заказдар тизмеси жүклөнүп жатканда 7–10 секунд күтүү болчу. 50,000 заказ болгон маалымат базасы.
-- Проблемалуу суроо-талап
SELECT o.*, u.name as customer_name, u.phone
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending'
AND o.created_at >= '2015-09-01'
ORDER BY o.created_at DESC
LIMIT 20;
-- Убакыт: 7.8 секунд
Шаг 1: EXPLAIN менен диагностика
EXPLAIN
SELECT o.*, u.name, u.phone
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending'
AND o.created_at >= '2015-09-01'
ORDER BY o.created_at DESC
LIMIT 20;
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
| 1 | SIMPLE | o | ALL | NULL | NULL | NULL | NULL | 48923 | Using where; Using filesort |
| 1 | SIMPLE | u | ALL | NULL | NULL | NULL | NULL | 12400 | |
+----+-------------+-------+------+---------------+------+---------+------+--------+-----------------------------+
Эмне билдирет:
type: ALL— толук таблицаны сканерлөө (эң жаман)rows: 48923— 49 миң жолду карайт, 20 гана кайтаратUsing filesort— ORDER BY үчүн дискте сорттооkey: NULL— эч кандай индекс колдонулган жок
Шаг 2: Туура индекс кошуу
-- Суроо-талаптагы WHERE шарттарын талдоо:
-- WHERE o.status = 'pending' → status боюнча
-- AND o.created_at >= '2015-09-01' → created_at боюнча
-- ORDER BY o.created_at DESC → ошол эле created_at
-- Составной индекс: алгач тең тиңдеген мамыча, анан диапазондук
CREATE INDEX idx_status_created ON orders(status, created_at);
-- users таблицасы: id колонкасы (JOIN'да колдонулат)
-- PRIMARY KEY болушу керек — текшерүү:
SHOW CREATE TABLE users;
-- Эгер PRIMARY KEY(id) бар болсо — кошумча индекс керек эмес
Шаг 3: EXPLAIN кайра текшерүү
EXPLAIN
SELECT o.*, u.name, u.phone
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending'
AND o.created_at >= '2015-09-01'
ORDER BY o.created_at DESC
LIMIT 20;
+----+-------------+-------+-------+-------------------+-------------------+---------+--------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | rows | Extra |
+----+-------------+-------+-------+-------------------+-------------------+---------+--------+------+-------+
| 1 | SIMPLE | o | range | idx_status_created| idx_status_created| 207 | 243 | |
| 1 | SIMPLE | u | eq_ref| PRIMARY | PRIMARY | 4 | 1 | |
+----+-------------+-------+-------+-------------------+-------------------+---------+--------+------+-------+
Жакшырды:
type: range— индекс диапазонду колдонот (жакшы)rows: 243— 243 жол гана (48,923 эмес)Using filesortжок — ORDER BY индекс аркылуу
Убакыт: 7.8 секунд → 38 мс (200 эсе тез!)
Slow Query Log: жай суроо-талаптарды табуу
# /etc/mysql/my.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1 # 1 секунддан жай суроо-талаптарды жаз
log_queries_not_using_indexes = 1
sudo service mysql restart
# Slow log'ду карап чыгуу
tail -f /var/log/mysql/slow.log
# mysqldumpslow менен суммарлоо
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# -s t: убакыт боюнча сортировка
# -t 10: эң жай 10 суроо-талап
Кеңири таралган индекс каталары
Ката 1: Функция ичинде колонка
-- ЖОЙ: индекс иштебейт
WHERE YEAR(created_at) = 2015
-- ТУУРА: диапазон колдон
WHERE created_at >= '2015-01-01' AND created_at < '2016-01-01'
Ката 2: OR шарты
-- ЖОЙ: OR'дагы индекс иштебеши мүмкүн
WHERE status = 'pending' OR user_id = 42
-- ТУУРА: UNION колдон
SELECT * FROM orders WHERE status = 'pending'
UNION
SELECT * FROM orders WHERE user_id = 42
Ката 3: LIKE менен башында %
-- ЖОЙ: индекс иштебейт
WHERE name LIKE '%алматы%'
-- ТУУРА: FULLTEXT INDEX колдон
ALTER TABLE articles ADD FULLTEXT INDEX ft_content(title, body);
SELECT * FROM articles WHERE MATCH(title, body) AGAINST('алматы' IN BOOLEAN MODE);
INDEX EXPLAIN типтерин түшүнүү
| type | Маанисы | Баасы |
|---|---|---|
ALL |
Толук таблица сканери | ЭН ЖАМАН |
index |
Толук индекс сканери | Жаман |
range |
Индекс диапазону | Жакшы |
ref |
Тиңдеме боюнча индекс | Жакшы |
eq_ref |
Уникалдуу индекс (JOIN) | Өтө жакшы |
const |
Бир жол (PRIMARY KEY) | ЭН ЖАХШы |
Индекс орнотулгандан кийинки мониторинг
-- Кайсы индекстер колдонулат, кайсысы колдонулбайт
SELECT
table_name,
index_name,
stat_name,
stat_value
FROM mysql.innodb_index_stats
WHERE database_name = 'myshop'
ORDER BY table_name, index_name;
-- Колдонулбаган индекстерди табуу (MySQL 5.6+)
-- performance_schema.table_io_waits_summary_by_index_usage
SELECT object_schema, object_name, index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
ORDER BY object_schema, object_name;
Колдонулбаган индекс — бекер жер жана INSERT/UPDATE убактысын жайлатат. Бар болгон индекстерди да аудиттен өткөрүү керек.