MySQL索引优化:从理论到实战

一、索引优化的重要性

索引是数据库性能优化的核心手段之一。在高并发、大数据量的场景下,合理的索引设计可以将查询性能提升数十倍甚至上百倍。阿里巴巴开发手册明确指出:"SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。"

二、MySQL 索引基础

1. 索引类型

MySQL 支持多种索引类型,主要包括:

  • 主键索引:表的主键自动创建的索引,唯一且非空
  • 唯一索引:确保索引列的值唯一
  • 普通索引:最基本的索引类型,没有唯一性限制
  • 覆盖索引:查询的字段都包含在索引中,无需回表查询

正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain 的结果,extra 列会出现:using index。

2. 索引的底层数据结构

MySQL 主要使用 B+Tree 作为索引的底层数据结构,其优势在于:

  • 叶节点具有相同的深度
  • 非叶子节点不存储数据,只存储索引
  • 叶子节点用指针连接,提高区间访问性能
  • 适合范围查询和排序

三、索引设计原则

1. 代码先行,索引后上

"代码先行,索引后上"是阿里巴巴手册推荐的最佳实践。不要在建表后立即创建索引,而应等到主体业务功能开发完毕,把涉及该表的相关 SQL 都拿出来分析之后再建立索引。

2. 联合索引尽量覆盖条件

"联合索引尽量覆盖条件"是索引设计的核心原则。设计一个或两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量包含 SQL 语句里的条件。

3. 区分度高的字段放在最左边

"建组合索引的时候,区分度最高的在最左边。"这是索引设计的黄金法则。

举例:如果用户表有 province(省)、city(市)、sex(性别)字段,且 province 区分度最高(如全国有 34 个省),则应创建联合索引(province, city, sex)。

4. 最左前缀原则

MySQL 索引遵循最左前缀匹配原则。对于联合索引 (a, b, c)

  • WHERE a = 1:可以使用索引
  • WHERE a = 1 AND b = 2:可以使用索引
  • WHERE b = 2:无法使用索引
  • WHERE a = 1 AND c = 3:无法使用索引(b 字段缺失)

重要提示:MySQL 8.0 引入了索引跳跃扫描(Index Skip Scan)特性,可以在某些情况下绕过最左前缀限制,但不能依赖此特性,仍应优先将区分度高的字段放在联合索引的左边。

四、常见索引失效场景及优化

1. 在索引列上做函数操作

sql 复制代码
-- 无效:索引失效
EXPLAIN SELECT * FROM employees WHERE YEAR(hire_time) = 2023;

-- 有效:索引可使用
EXPLAIN SELECT * FROM employees WHERE hire_time >= '2023-01-01' AND hire_time < '2024-01-01';

2. 类型转换

sql 复制代码
-- 无效:索引失效
EXPLAIN SELECT * FROM employees WHERE name = 1000;

-- 有效:索引可使用
EXPLAIN SELECT * FROM employees WHERE name = '1000';

3. 使用 OR 连接条件

sql 复制代码
-- 可能失效:MySQL优化器可能选择不走索引
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' OR name = 'HanMeimei';

-- 优化方案:拆分成两个查询
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE name = 'HanMeimei';

4. 不使用最左前缀

sql 复制代码
-- 无效:无法使用索引
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position = 'dev';

-- 有效:使用最左前缀
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age = 22;

5. 范围查询后跟其他条件

sql 复制代码
-- 无效:age为范围条件,position无法使用索引
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age > 22 AND position = 'dev';

-- 有效:拆分范围查询
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age > 22 AND age <= 30;
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' AND age > 30 AND age <= 40;

五、查询优化实践

1. 分页查询优化

MySQL 的 LIMIT offset, rows 在 offset 很大时效率极低,因为需要先读取 offset+rows 条记录,然后丢弃前 offset 条。

优化方案​:利用主键范围查询

sql 复制代码
-- 原始低效查询
SELECT * FROM employees LIMIT 90000, 5;

-- 优化后查询(主键连续且自增)
SELECT * FROM employees WHERE id > 90000 LIMIT 5;

延迟关联优化​:

sql 复制代码
-- 低效查询
SELECT a.* FROM employees a, (SELECT id FROM employees WHERE condition LIMIT 100000, 20) b WHERE a.id = b.id;

-- 优化后查询
SELECT a.* FROM employees a INNER JOIN (SELECT id FROM employees WHERE condition LIMIT 100000, 20) b ON a.id = b.id;

2. JOIN 关联查询优化

问题​:当被驱动表的关联字段没有索引时,MySQL 会使用 BNL(Block Nested Loop)算法,性能较差。

优化方案​:为关联字段添加索引,并确保小表驱动大表。

sql 复制代码
-- 优化前(被驱动表关联字段无索引)
SELECT * FROM t2 JOIN t1 ON t2.a = t1.a;

-- 优化后(添加索引并小表驱动大表)
ALTER TABLE t1 ADD INDEX idx_a(a);
SELECT * FROM t2 STRAIGHT_JOIN t1 ON t2.a = t1.a;

说明:MySQL 对于被驱动表的关联字段没索引的关联查询,一般使用 BNL 算法。如果有索引,一般选择 NLJ(Nested Loop Join)算法,性能更高。

3. 排序优化

优化目标​:避免 Using filesort,使用 Using index。

sql 复制代码
-- 无效:导致Using filesort
EXPLAIN SELECT * FROM employees ORDER BY position DESC;

-- 有效:使用覆盖索引
EXPLAIN SELECT name, age, position FROM employees ORDER BY position;

MySQL 8.0 引入了降序索引,可以支持 ORDER BY position DESC 直接使用索引,避免 Using filesort。

4. 范围查询优化

sql 复制代码
-- 低效:MySQL可能不使用索引
EXPLAIN SELECT * FROM employees WHERE age >= 1 AND age <= 2000;

-- 优化:拆分成多个小范围查询
EXPLAIN SELECT * FROM employees WHERE age >= 1 AND age <= 1000;
EXPLAIN SELECT * FROM employees WHERE age >= 1001 AND age <= 2000;

六、索引优化最佳实践

1. 索引使用率监控

  • 定期分析慢查询日志,针对慢查询创建索引
  • 使用 SHOW INDEX FROM table_name 查看索引使用情况
  • 通过 EXPLAIN 分析查询是否使用了索引

2. 索引大小控制

  • 避免创建过长的联合索引
  • 索引列应尽量选择小数据类型(如 INT 代替 VARCHAR)
  • 避免在索引中包含大文本字段

3. 索引维护

  • 定期检查索引碎片
  • 删除不使用的索引
  • 避免在频繁更新的表上创建过多索引

4. 阿里巴巴手册推荐实践

  1. 存储方案和底层数据结构的设计:必须获得评审一致通过,并沉淀成为文档
  2. JVM 参数设置XX:+HeapDumpOnOutOfMemoryError,让 JVM 在 OOM 时输出 dump 信息
  3. 生产环境 JVM 配置:Xms 和 Xmx 设置相同大小,避免 GC 后调整堆大小带来的压力
  4. 服务器内部重定向:必须使用 forward;外部重定向必须使用 URL Broker 生成

七、MySQL 5.7 与 8.0 的索引优化差异

  1. 索引跳跃扫描
    • MySQL 5.7:不支持
    • MySQL 8.0:支持,允许在不使用最左前缀的情况下使用索引
  2. 降序索引
    • MySQL 5.7:不支持
    • MySQL 8.0:支持,可创建降序索引 CREATE INDEX idx_position_desc ON employees(position DESC)
  3. EXPLAIN EXTENDED
    • MySQL 5.7:需要使用 EXPLAIN EXTENDEDSHOW WARNINGS
    • MySQL 8.0:已废除 EXPLAIN EXTENDED,只需使用 EXPLAIN
  4. 文件排序
    • MySQL 8.0:max_length_for_sort_data 默认值为 4096 字节,影响排序算法选择

八、总结

索引优化是一项需要持续迭代的工作,而非一劳永逸的解决方案。根据阿里巴巴手册和实战经验,我们总结了以下关键点:

  1. 索引不是越多越好,而是越"准"越好。合理设计索引,避免过度索引。
  2. 遵循最左前缀原则,但 MySQL 8.0 提供了索引跳跃扫描作为补充。
  3. 避免索引失效的常见场景,如在索引列上做函数操作、类型转换等。
  4. 分页查询、JOIN 查询、排序查询是需要特别关注的优化点。
  5. 定期分析慢查询,持续优化索引设计。

"索引是数据库性能的加速器,但不当的索引是性能的拖累。"------阿里巴巴开发手册

记住,索引优化不是一蹴而就的,需要根据实际业务场景和数据分布不断调整和优化。通过合理使用 EXPLAIN 工具,我们可以清晰地看到索引是否被正确使用,从而进行针对性优化。

在实际工作中,建议遵循"先业务,后索引"的原则,先确保业务逻辑正确,再通过 EXPLAIN 分析查询性能,最后进行针对性的索引优化。这样既能保证业务功能正常运行,又能获得最佳的查询性能。

相关推荐
岁岁种桃花儿1 天前
MySQL从入门到精通系列:InnoDB记录存储结构
数据库·mysql
jiunian_cn1 天前
【Redis】hash数据类型相关指令
数据库·redis·哈希算法
冉冰学姐1 天前
SSM在线影评网站平台82ap4(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm框架·在线影评平台·影片分类
Exquisite.1 天前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql
知识分享小能手1 天前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019数据库的操作(2)
数据库·学习·sqlserver
踩坑小念1 天前
秒杀场景下如何处理redis扣除状态不一致问题
数据库·redis·分布式·缓存·秒杀
萧曵 丶1 天前
MySQL 语句书写顺序与执行顺序对比速记表
数据库·mysql
Wiktok1 天前
MySQL的常用数据类型
数据库·mysql
曹牧1 天前
Oracle 表闪回(Flashback Table)
数据库·oracle
J_liaty1 天前
Redis 超详细入门教程:从零基础到实战精通
数据库·redis·缓存