索引是MySQL性能优化的核心手段之一,但很多开发者对索引的理解停留在"加了就能提速"的层面。我在处理过的数百个数据库性能案例中发现,90%的索引使用问题都源于对基本原理的误解。让我们从存储引擎层面来理解索引的本质。
InnoDB引擎采用B+树结构存储索引数据,每个索引都是一棵独立的B+树。当创建INDEX(col)时,引擎会:
这种结构带来两个关键特性:
注意:B+树的高度通常不超过4层,这意味着即使亿级数据量,最多也只需要4次I/O就能定位到记录
每个索引都会带来三方面成本:
| 成本类型 | 说明 | 量化示例 |
|---|---|---|
| 存储成本 | 额外占用磁盘空间 | 1GB表添加索引可能增加200MB空间 |
| 写入成本 | 每次INSERT/UPDATE/DELETE需维护索引树 | 单索引会使写入性能降低10-20% |
| 优化器成本 | 查询优化器需要评估更多执行计划 | 每增加一个索引,优化时间增加0.5-2ms |
我在电商系统实测发现:当表的索引超过5个时,写操作吞吐量会下降40%以上。这就是为什么"索引不是越多越好"。
区分度计算公式:
code复制区分度 = COUNT(DISTINCT column) / COUNT(*)
实践建议:
sql复制-- 快速计算字段区分度
SELECT
COUNT(DISTINCT status)/COUNT(*) AS status_selectivity,
COUNT(DISTINCT user_id)/COUNT(*) AS user_selectivity
FROM orders;
对于TEXT/BLOB/VARCHAR(2000+)等大字段:
| 方案 | 实现方式 | 适用场景 |
|---|---|---|
| 前缀索引 | INDEX(column(20)) |
前N个字符具有区分度 |
| 哈希索引 | 新增哈希值列并建索引 | 精确匹配场景 |
| 倒排索引 | 使用专业全文检索方案 | 文本搜索场景 |
踩坑提醒:前缀索引会导致
ORDER BY、GROUP BY无法使用索引覆盖
对于订单流水、日志记录等写多读少的表:
sql复制-- 反例(需要回表)
SELECT * FROM order_log WHERE user_id=100 ORDER BY id DESC LIMIT 10;
-- 正例(先查主键再关联)
SELECT a.* FROM order_log a
JOIN (SELECT id FROM order_log WHERE user_id=100 ORDER BY id DESC LIMIT 10) b
ON a.id=b.id;
对于热查询字段应建立复合索引,遵循"最左前缀原则":
sql复制-- 优化前(低效)
INDEX(status), INDEX(create_time)
-- 优化后(高效)
INDEX(status, create_time)
实测案例:用户中心表的WHERE status=1 AND create_time>'2023-01-01'查询,优化后响应时间从120ms降至8ms。
对于GROUP BY+ORDER BY场景:
sql复制-- 需要索引
SELECT department, COUNT(*)
FROM employees
GROUP BY department
ORDER BY join_date DESC;
-- 最优索引
INDEX(department, join_date)
使用以下方法确定多列索引顺序:
EXPLAIN查看现有查询模式sys.schema_index_statistics分析索引使用频率sql复制-- 分析索引使用情况
SELECT * FROM sys.schema_index_statistics
WHERE table_schema='your_db';
定期执行检查脚本:
sql复制SELECT
table_name,
index_name,
ROUND(stat_value * @@innodb_page_size/1024/1024,2) AS size_mb,
stat_description
FROM mysql.innodb_index_stats
WHERE database_name = 'your_db';
关键指标阈值:
重建索引的正确姿势:
sql复制-- 在线重建(MySQL 5.7+)
ALTER TABLE orders ALTER INDEX idx_user_id INVISIBLE;
ALTER TABLE orders ALTER INDEX idx_user_id VISIBLE;
-- 传统方式(锁表)
ALTER TABLE orders DROP INDEX idx_user_id;
ALTER TABLE orders ADD INDEX idx_user_id(user_id);
建议在业务低峰期执行,大表采用pt-online-schema-change工具。
sql复制-- user_id是varchar类型时(无法使用索引)
SELECT * FROM users WHERE user_id = 100;
-- 解决方案
SELECT * FROM users WHERE user_id = '100';
sql复制-- 错误用法(索引失效)
SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01';
-- 正确用法(范围查询)
SELECT * FROM orders
WHERE create_time >= '2023-01-01'
AND create_time < '2023-01-02';
sql复制-- 低效写法
SELECT * FROM products
WHERE category='electronics' OR price>1000;
-- 高效改写
SELECT * FROM products WHERE category='electronics'
UNION ALL
SELECT * FROM products WHERE price>1000
AND (category<>'electronics' OR category IS NULL);
在最近一次系统优化中,通过修正这三大类问题,某核心接口的数据库响应时间从平均800ms降至90ms。