MySQL索引优化为什么是数据库性能的基石
MySQL查询性能问题的根源,80%以上与索引使用不当有关——全表扫描、索引失效、冗余索引、执行计划偏差。盲目加索引不仅不能解决问题,还会拖慢写入性能、浪费存储空间。索引优化需要从执行计划分析入手,精准定位问题,再针对性调整索引策略。这篇文章覆盖从EXPLAIN解读到慢查询诊断的完整流程。
EXPLAIN执行计划:读懂MySQL的查询路径
EXPLAIN是索引优化的起点。它展示MySQL优化器选择的查询执行计划,包括是否使用索引、扫描行数、连接方式等关键信息:
-- 查看执行计划
EXPLAIN SELECT o.order_id, o.total, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.created_at >= '2026-07-01'
AND o.status = 'PAID'
ORDER BY o.total DESC
LIMIT 20;
EXPLAIN输出关键字段解读:
| 字段 | 含义 | 重点关注 |
|---|---|---|
| type | 访问类型 | 从优到劣:const > eq_ref > ref > range > index > ALL |
| key | 实际使用的索引 | NULL表示未使用索引 |
| rows | 预估扫描行数 | 越少越好 |
| filtered | 过滤百分比 | 100%最佳,低于10%需关注 |
| Extra | 附加信息 | Using filesort/Using temporary需警惕 |
type字段等级详解:
- const:主键/唯一索引等值查询,最多1行
- eq_ref:JOIN时被驱动表使用主键/唯一索引
- ref:非唯一索引等值查询
- range
:索引范围扫描(BETWEEN, >, <等)
- index:全索引扫描(比ALL好,但仍是全量扫描)
- ALL:全表扫描,必须优化
索引失效的六种常见场景
索引存在但查询未使用,这是最常见的性能问题。以下是六种典型失效场景及修复方案:
场景1:对索引列使用函数或表达式
-- 索引失效:对created_at使用了函数
SELECT * FROM orders WHERE DATE(created_at) = '2026-07-25';
-- 修复:改写为范围查询
SELECT * FROM orders
WHERE created_at >= '2026-07-25 00:00:00'
AND created_at < '2026-07-26 00:00:00';
场景2:隐式类型转换
-- 索引失效:customer_id是INT类型,但传入字符串
SELECT * FROM orders WHERE customer_id = '12345';
-- 修复:使用正确类型
SELECT * FROM orders WHERE customer_id = 12345;
场景3:LIKE前缀通配符
-- 索引失效:以通配符开头
SELECT * FROM products WHERE name LIKE '%手机';
-- 修复:使用前缀匹配,或考虑全文索引
SELECT * FROM products WHERE name LIKE '华为%';
场景4:OR条件导致索引合并失败
-- 可能失效:OR条件中一个列无索引
SELECT * FROM orders WHERE customer_id = 100 OR status = 'PENDING';
-- 修复1:为status列添加索引
ALTER TABLE orders ADD INDEX idx_status (status);
-- 修复2:改写为UNION
SELECT * FROM orders WHERE customer_id = 100
UNION
SELECT * FROM orders WHERE status = 'PENDING';
场景5:联合索引最左前缀原则违反
-- 联合索引:idx_status_created (status, created_at)
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
-- 生效:使用最左前缀
SELECT * FROM orders WHERE status = 'PAID'; ✅
SELECT * FROM orders WHERE status = 'PAID' AND created_at > '2026-07-01'; ✅
-- 失效:跳过最左列
SELECT * FROM orders WHERE created_at > '2026-07-01'; ❌
场景6:NOT条件
-- 索引可能失效:NOT/EQUALS不等于
SELECT * FROM orders WHERE status != 'CANCELLED';
-- 修复:改写为IN
SELECT * FROM orders WHERE status IN ('PENDING', 'PAID', 'SHIPPED');
联合索引的设计原则
联合索引(Composite Index)是索引优化的核心技能。设计原则直接影响查询性能:
原则1:等值条件列在前,范围条件列在后
-- 查询:WHERE status = 'PAID' AND created_at > '2026-07-01'
-- 索引设计:status在前(等值),created_at在后(范围)
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
-- 这样MySQL可以先通过status精确定位,再在范围内使用created_at的索引排序
-- 如果created_at在前,status的等值过滤就无法利用索引
原则2:覆盖索引避免回表
-- 需要回表:索引只包含status和created_at,查询还需要total列
SELECT order_id, total FROM orders
WHERE status = 'PAID' AND created_at > '2026-07-01';
-- 覆盖索引:把查询需要的列都包含在索引中
ALTER TABLE orders ADD INDEX idx_cover_paid (
status, created_at, order_id, total
);
-- EXPLAIN中Extra显示Using index表示覆盖索引生效,无需回表
原则3:避免冗余索引
-- 冗余:如果已有(status, created_at),单独的(status)索引是冗余的
ALTER TABLE orders ADD INDEX idx_status (status); -- 冗余!
-- 检查冗余索引
SELECT s.table, s.index_name, GROUP_CONCAT(s.column_name ORDER BY s.seq_in_index) AS columns
FROM information_schema.statistics s
WHERE s.table_schema = 'your_db' AND s.table = 'orders'
GROUP BY s.table, s.index_name;
-- 使用sys.schema_unused_indexes查看未使用的索引
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = 'your_db';
慢查询诊断全流程
慢查询日志是发现性能问题的第一道防线:
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒的查询记入日志
SET GLOBAL log_queries_not_using_indexes = 'ON'; -- 未用索引的查询也记录
-- 查看慢查询日志位置
SHOW VARIABLES LIKE 'slow_query_log_file';
-- 使用mysqldumpslow分析慢查询
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log
# -s t:按查询时间排序
# -t 10:显示前10条
诊断流程:
- 从慢查询日志中提取TOP N慢查询
- 对每条慢查询执行EXPLAIN分析
- 检查type是否为ALL或index
- 检查Extra是否有Using filesort或Using temporary
- 检查rows预估扫描行数是否远大于实际结果集
- 根据诊断结果添加或调整索引
-- 实战诊断:发现Using filesort
EXPLAIN SELECT * FROM orders
WHERE customer_id = 100
ORDER BY created_at DESC LIMIT 10;
-- 如果显示:Using where; Using filesort
-- 原因:索引(customer_id)无法支持created_at排序
-- 优化:添加联合索引
ALTER TABLE orders ADD INDEX idx_customer_created (customer_id, created_at);
-- 优化后EXPLAIN应显示:Using where; Backward index scan
-- Backward index scan表示MySQL利用索引的降序扫描替代filesort
索引监控与持续优化
索引优化不是一次性工作,需要持续监控:
-- 查看索引使用统计(MySQL 8.0+)
SELECT
index_name,
rows_examined,
rows_returned,
ROUND(rows_examined / NULLIF(rows_returned, 0), 2) AS selectivity
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'your_db'
ORDER BY rows_examined DESC;
-- 查看索引大小
SELECT
index_name,
ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) AS size_mb
FROM mysql.innodb_index_stats
WHERE database_name = 'your_db'
AND stat_name = 'size'
ORDER BY stat_value DESC;
MySQL索引优化的完整路径:先用EXPLAIN读懂执行计划,再用六种失效场景逐一排查,按联合索引三原则设计索引,通过慢查询日志持续监控。索引不是越多越好,精准的索引设计远比堆砌索引有效。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-suo-yin-you-hua-shi-zhan-zhi-xing-ji-hua-fen-xi-yu/