要优化索引,得先搞懂它的“底层语言”——B+树。以InnoDB为例,B+树的结构像棵“倒挂的树”:非叶子节点存索引键和子节点指针,叶子节点按顺序存索引键和对应数据。如果是聚簇索引(比如主键),叶子节点直接存完整行数据;如果是非聚簇索引(普通索引),叶子节点存聚簇索引的键(比如主键id)。

举个直观例子:查id=100的用户,InnoDB从根节点二分查找,到叶子节点直接取行数据——这是聚簇索引的流程。查name=’张三’(非聚簇索引),得先通过name找到id=100,再回聚簇索引查行数据——这步“回表”是很多慢查询的根源。
我之前帮项目把UUID主键改成自增id,插入性能提升40%——因为自增主键顺序插入不会导致B+树分裂,而UUID无序会频繁调整索引结构。
选对索引类型:不是所有场景都用B+树
很多人以为“索引=B+树”,其实不同类型对应不同场景。我整理了张对比表,帮你快速选对:
索引类型 | 底层结构 | 适用场景 | 实战案例 |
---|---|---|---|
B+树索引 | B+树 | 范围查询(age>30)、排序(order by create_time) | MySQL InnoDB默认索引 |
哈希索引 | 哈希表 | 等值查询(id=1) | Redis键值存储 |
全文索引 | 倒排索引 | 文本搜索(content like ‘%数据库%’) | Elasticsearch文本检索 |
空间索引 | R树 | 地理数据(location near (116.4,39.9)) | PostgreSQL PostGIS扩展 |
比如哈希索引,Redis用它做键值查询,get key速度O(1),但range查询没法用——哈希值无序。全文索引解决like ‘%关键词%’的慢查询,但精确匹配用B+树更快。
索引设计的避坑要点
索引不是越多越好,这3个坑最容易踩:
坑1:给低基数字段加索引
低基数字段指取值范围小的字段(比如性别、状态),selectivity(选择性)极低——性别字段selectivity=1/2,优化器会认为“走索引查50%数据,不如全表扫描”。我之前遇到过开发给“性别”加索引,结果查询全表扫描,删掉后速度快了2倍。
坑2:加过长字段做索引
给varchar(500)的“备注”加索引,索引文件会大得离谱——100万条数据占500MB,查询时要读更多数据页。正确做法是用前缀索引:index(remark(10))
,取前10个字符做索引,减少大小。但要注意前缀的选择性,比如“备注”前10个字符重复率高,索引效果会打折扣。
坑3:给频繁更新的字段加索引
更新带索引的字段,数据库要同时更新索引树。比如“积分”字段每笔交易都更新,如果加索引,写入性能会暴跌。我优化过电商项目,删掉“积分”索引后,订单插入速度提升35%。
慢查询中的索引诊断技巧
慢查询的核心是“找没用到索引的原因”,explain命令是诊断神器。重点看4个字段:
- type:连接类型,从好到差:system>const>eq_ref>ref>range>index>all。all是全表扫描,range是范围索引。
- key:实际用到的索引名,NULL表示没用到。
- rows:估计扫描行数,数值越小越好。
- Extra:Using index(覆盖索引,好)、Using filesort(需要排序,坏)、Using temporary(临时表,坏)。
举个例子,执行:
explain select * from user where age>30 and name like '张%';
如果type是range,key是age_name_idx,rows是1000,说明用到联合索引;如果type是all,key是NULL,就得加索引。
再比如查询select * from order where create_time>'2025-01-01' order by amount desc limit 100
,Extra显示Using filesort,说明排序没用到索引——加联合索引(age, name)就能解决,因为可以按age范围扫描,再按name排序。
索引维护的实用策略
索引建完要定期维护,否则会成“性能包袱”:
策略1:定期清理碎片
InnoDB频繁删改会产生碎片——删除一行后,页有空隙,插入数据填不满导致页分裂。碎片多了,查询要读更多页。
查碎片用:
show table status like 'order';
看Data_free字段,占数据量10%以上就整理。MySQL 8.0用:
alter table order engine=InnoDB; -- 重建表整理碎片
optimize table会锁表,建议低峰期用。
策略2:更新统计信息
优化器靠统计信息选执行计划,过时会选错。比如插入100万行,统计信息旧,优化器可能认为“扫100行”,实际扫100万行。
更新用:
analyze table order; -- MySQL更新统计信息
analyze verbose order; -- PostgreSQL更新
策略3:删除无用索引
无用索引增加写入开销和磁盘占用。用Percona的pt-index-usage工具分析慢查询日志:
pt-index-usage slow.log --database=test
工具会输出“从未使用的索引”,直接删。我之前清理了12个无用索引,写入性能提升28%。
高并发场景下的索引优化
高并发下,索引“效率”比“数量”重要。分享2个立竿见影的技巧:
技巧1:用覆盖索引避免回表
覆盖索引指“查询字段都在索引里”,不用回表。比如电商订单查询:
select order_id, user_id, amount from order where create_time between '2025-08-01' and '2025-08-31';
建联合索引(create_time, order_id, user_id, amount),查询直接从索引取数据,不用回表,IO次数减少50%。我优化过的项目,订单列表查询从1.2秒降到0.15秒。
技巧2:用延迟关联减少回表次数
如果必须回表,用延迟关联——先查索引里的主键,再回表取数据。比如:
-- 原查询(回表100次,扫描大量数据)
select * from order where create_time>'2025-01-01' order by amount desc limit 100;
-- 延迟关联(先查主键,再回表100次)
select o.* from (
select order_id from order where create_time>'2025-01-01' order by amount desc limit 100
) as t join order o on t.order_id=o.order_id;
子查询只查order_id(在索引里),再回表查完整数据,减少回表的数据量——原查询可能回表100万行,延迟关联只回表100行。
索引优化的核心是“匹配场景”——先懂原理,再选对类型,避开坑,最后维护好。很多时候不是你不会优化,是没搞清楚“索引到底怎么工作”。把这些技巧落地,你的数据库性能会有质的提升。
原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/303