1. 为什么我们需要深入理解MySQL索引
记得刚入行时处理过一个线上事故:某核心报表查询突然从200ms飙升至15秒,整个页面卡死。排查后发现是开发同学在时间字段上漏加了索引,随着数据量突破百万级,全表扫描直接拖垮了性能。这个惨痛教训让我意识到——索引不是简单的"加速工具",而是直接影响系统生死的关键设计。
2. 索引的本质与工作原理
2.1 存储引擎如何实现索引
InnoDB采用B+树作为索引结构不是偶然。相比二叉树,B+树的矮胖特性使得3-4层就能存储亿级数据。我曾用SHOW INDEX FROM table查看过一个2亿行表的索引统计,其高度仅为4层,这意味着最多4次磁盘IO就能定位到数据。
B+树的两个关键设计:
- 叶子节点形成双向链表:范围查询时无需回溯父节点
- 非叶子节点只存键值:单个节点能容纳更多索引项
2.2 聚簇索引的物理存储奥秘
聚簇索引的叶子节点直接包含完整行数据,这导致:
- 主键查询极快(只需1次索引查找)
- 主键长度影响所有二级索引(二级索引存储的是主键值)
- 插入顺序影响存储密度(随机主键会导致页分裂)
实测案例:使用自增ID vs UUID作为主键时,相同数据量下后者索引大小增加23%,写入TPS下降18%
3. 索引类型的实战选择策略
3.1 二级索引的隐藏成本
创建INDEX idx_name (name)时,实际存储的是(name, primary_key)的组合。这意味着:
-- 需要回表查询 SELECT * FROM users WHERE name = '张三' -- 覆盖索引避免回表 SELECT id FROM users WHERE name = '张三'3.2 最左前缀原则的边界情况
联合索引(a,b,c)能生效的典型场景:
WHERE a=1 AND b=2 AND c>3 -- 用到a,b,c WHERE a=1 ORDER BY b,c -- 排序优化 WHERE a=1 AND b IN (2,3) -- 用到a,b容易踩坑的场景:
WHERE b=2 AND c=3 -- 无法使用索引 WHERE a=1 AND c=3 -- 只用a列4. 索引优化全链路实战
4.1 设计阶段的黄金法则
- 区分度公式:
COUNT(DISTINCT col)/COUNT(*)大于0.1才考虑建索引 - 宽度权衡:用
city代替address作为索引 - 避免过度索引:每个额外索引增加约5%的写入开销
4.2 执行计划深度解读
关键指标解析:
type:从优到劣 system > const > ref > range > index > ALLrows:预估扫描行数(与实际偏差过大时需要analyze table)Extra:Using filesort和Using temporary是危险信号
4.3 索引失效的隐蔽陷阱
- 隐式类型转换:
-- phone是varchar类型 WHERE phone = 13800138000 -- 失效 WHERE phone = '13800138000' -- 有效- 函数操作:
-- 不会使用create_time索引 WHERE DATE(create_time) = '2023-01-01' -- 改用范围查询 WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59'5. 高级索引优化技巧
5.1 索引跳跃扫描优化
MySQL 8.0的新特性,对于联合索引(gender, age):
-- 即使gender未指定也能部分使用索引 SELECT * FROM users WHERE age > 20原理:自动枚举gender的所有取值(相当于自动改写为WHERE gender IN('M','F') AND age>20)
5.2 不可见索引的灰度发布
-- 创建不可见索引(优化器忽略) CREATE INDEX idx_name ON users(name) INVISIBLE -- 通过hint强制使用测试 SELECT /*+ USE_INDEX(users idx_name) */ * FROM users... -- 确认效果后改为可见 ALTER INDEX idx_name VISIBLE6. 生产环境索引管理
6.1 在线DDL的风险控制
-- 查看DDL进度(MySQL 5.7+) SELECT * FROM performance_schema.events_stages_current WHERE EVENT_NAME LIKE '%alter%'推荐工具:
- pt-online-schema-change
- gh-ost
6.2 索引监控与维护
关键SQL:
-- 查找冗余索引 SELECT * FROM sys.schema_redundant_indexes -- 索引使用统计 SELECT * FROM sys.schema_index_statistics WHERE table_schema NOT IN ('mysql','sys')定期维护策略:
# 每周凌晨低峰期执行 mysqlcheck -o --all-databases7. 真实案例复盘
7.1 电商订单查询优化
原始查询(执行时间2.8s):
SELECT * FROM orders WHERE user_id=100 AND status='paid' ORDER BY create_time DESC LIMIT 10优化步骤:
- 建立
(user_id, status, create_time)联合索引 - 改写为覆盖索引查询:
SELECT * FROM orders WHERE id IN ( SELECT id FROM orders WHERE user_id=100 AND status='paid' ORDER BY create_time DESC LIMIT 10 )最终优化至23ms,QPS提升120倍
7.2 时间序列数据的分区索引
对于日志表按天分区后:
-- 传统方式(低效) ALTER TABLE logs ADD INDEX idx_ip (ip_addr) -- 分区优化方式 ALTER TABLE logs ADD INDEX idx_ip (ip_addr) LOCAL分区索引大小减少70%,维护时间缩短85%