MySQL索引是数据库性能优化的核心手段之一,它通过特定的数据结构(通常是B+Tree)来加速数据检索。根据索引列的数量,我们可以将索引分为两大类:
在实际项目中,我们为stu表创建了一个典型的联合索引示例:
sql复制CREATE INDEX stu_name_age_phone ON stu(name, age, phone);
这个索引会按照name、age、phone的顺序建立B+Tree结构。理解这种排序机制对正确使用索引至关重要——索引列的顺序决定了数据在索引树中的排列方式,也直接影响查询优化器能否有效利用这个索引。
注意:索引虽然能加速查询,但会降低写入性能。每次INSERT、UPDATE、DELETE操作都需要同步更新索引。经验表明,单表的索引数量控制在5个以内为佳,超过这个数量就需要谨慎评估必要性。
最左前缀原则是联合索引使用的黄金法则:查询必须从索引的最左列开始,并且不能跳过中间的列。这个原则源于B+Tree的存储结构特性。
让我们通过两个查询示例来观察key_len的变化:
sql复制EXPLAIN SELECT * FROM stu WHERE name = '杨志明' AND age = 24 AND phone = '15996069196'\G
结果中的key_len为290,表示三个字段的索引都被充分利用。
sql复制EXPLAIN SELECT * FROM stu WHERE name = '杨志明' AND phone = '15996069196'\G
此时key_len降为202,说明只有name字段的索引生效,phone字段由于跳过了age而失效。
底层原理:B+Tree是按照索引定义的列顺序构建的。当跳过中间列时,后续列的排序信息无法被利用,优化器只能使用前面连续列的部分索引。
范围查询(>、<、BETWEEN等)会对其右侧的索引列使用造成影响:
sql复制EXPLAIN SELECT * FROM stu WHERE name = '杨志明' AND age > 23 AND phone = '15996069196'\G
这里key_len为207,表示phone字段的索引未完全生效。
但有趣的是,使用>=时:
sql复制EXPLAIN SELECT * FROM stu WHERE name = '杨志明' AND age >= 23 AND phone = '15996069196'\G
key_len恢复为290,所有字段索引都有效。
经验总结:
在索引列上使用函数会使索引失效:
sql复制EXPLAIN SELECT * FROM stu WHERE SUBSTRING(major,10,2) = '软件'\G
type显示为ALL,表示全表扫描。
解决方案:
当查询条件与列类型不匹配时会发生隐式转换:
sql复制EXPLAIN SELECT * FROM stu WHERE name = '杨志明' AND age = 24 AND phone = 15996069196\G
phone被当作数字处理,导致索引失效(key_len=207)。
最佳实践:
LIKE查询只有特定形式能使用索引:
sql复制EXPLAIN SELECT * FROM stu WHERE major LIKE '%工程'\G
type为ALL,全表扫描。
优化方案:
OR条件需要特别注意:
sql复制EXPLAIN SELECT * FROM stu WHERE id = 101 OR gender = 0\G
即使id有索引,只要OR条件中有任意无索引列,整个查询就会退化为全表扫描。
解决方案:
MySQL提供了索引提示语法来影响优化器选择:
sql复制SELECT * FROM stu USE INDEX(stu_phone) WHERE phone = '15996069196'
sql复制SELECT * FROM stu FORCE INDEX(stu_phone) WHERE phone = '15996069196'
sql复制SELECT * FROM stu IGNORE INDEX(stu_phone) WHERE phone = '15996069196'
使用场景:
覆盖索引是指查询所需的所有列都包含在索引中,无需回表:
sql复制EXPLAIN SELECT id, name FROM stu USE INDEX(stu_name) WHERE name = '杨志明'\G
Extra显示"Using index",表示使用了覆盖索引。
优势:
设计建议:
对于长字符串字段,可以使用前缀索引节省空间:
sql复制CREATE INDEX stu_phone_5 ON stu(phone(5));
适用条件:
注意事项:
sql复制SELECT COUNT(DISTINCT column)/COUNT(*) FROM table;
最常用原则:为高频查询条件、排序字段建立索引
短小精悍原则:
ERD排序法:Equal-Reference-Date,即等值条件字段在前,范围查询字段在后
CRUD分析法:根据业务查询模式设计索引顺序
三星索引原则:
典型示例:
sql复制-- 适合 WHERE a=? AND b=? ORDER BY c 的查询
CREATE INDEX idx_a_b_c ON table(a,b,c);
sql复制SELECT * FROM sys.schema_unused_indexes;
sql复制ALTER TABLE table_name ENGINE=InnoDB;
sql复制SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage;
维护建议:
场景:商品表需要支持多条件筛选:
sql复制SELECT * FROM products
WHERE category_id=5
AND price BETWEEN 100 AND 500
AND status=1
ORDER BY create_time DESC
LIMIT 20;
优化方案:
sql复制CREATE INDEX idx_category_status_price_time ON products(category_id, status, price, create_time);
优化效果:
场景:分页查询好友动态:
sql复制SELECT * FROM feeds
WHERE user_id IN (SELECT friend_id FROM relations WHERE user_id=123)
AND create_time > '2023-01-01'
ORDER BY create_time DESC
LIMIT 10;
优化方案:
sql复制CREATE INDEX idx_user_time ON feeds(user_id, create_time);
特殊技巧:
场景:按时间范围查询日志:
sql复制SELECT * FROM access_log
WHERE create_time BETWEEN '2023-06-01' AND '2023-06-02'
AND status_code=200;
优化方案:
sql复制CREATE INDEX idx_time_status ON access_log(create_time, status_code);
分区考虑:
理解EXPLAIN输出对索引优化至关重要:
优化器选择索引的依据:
sql复制ANALYZE TABLE table_name;
sql复制SELECT * FROM table FORCE INDEX(index1) WHERE ...;
SELECT * FROM table FORCE INDEX(index2) WHERE ...;
MySQL 8.0+支持JSON字段索引:
sql复制CREATE INDEX idx_json ON table((CAST(json_column->>'$.key' AS CHAR(30))));
使用限制:
针对文本搜索的优化:
sql复制CREATE FULLTEXT INDEX idx_content ON articles(content);
搜索语法:
sql复制SELECT * FROM articles
WHERE MATCH(content) AGAINST('+mysql -oracle' IN BOOLEAN MODE);
优化技巧:
地理位置查询优化:
sql复制CREATE SPATIAL INDEX idx_location ON shops(location);
查询示例:
sql复制SELECT * FROM shops
WHERE ST_Distance_Sphere(location, POINT(116.404, 39.915)) < 1000;
支持索引列的降序存储:
sql复制CREATE INDEX idx_desc ON table(column1 DESC, column2 ASC);
适用场景:
临时禁用索引而不删除:
sql复制ALTER TABLE table ALTER INDEX idx_name INVISIBLE;
使用场景:
直接为表达式创建索引:
sql复制CREATE INDEX idx_func ON table((YEAR(create_time)));
注意事项:
InnoDB的MVCC机制需要考虑索引:
优化建议:
索引影响锁定范围:
案例分析:
sql复制-- 使用索引列作为条件可以减少锁定范围
UPDATE accounts SET balance=balance-100 WHERE user_id=123;
索引设计影响死锁概率:
最佳实践:
在实际项目中,我经常发现开发人员会过度依赖索引提示(USE INDEX)。虽然它能解决某些特定场景的问题,但长期来看,更好的做法是优化索引设计本身,让优化器能够自动选择最优执行计划。索引提示应该作为临时解决方案,而不是长期依赖的手段。
另一个常见误区是试图为所有可能的查询条件创建索引。这种做法不仅增加维护成本,还可能因为索引过多导致优化器选择困难。我建议采用"最小够用"原则,先为核心查询路径创建必要的索引,再通过监控逐步优化。