Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL索引指南

MySQL数据库索引完全指南

作者:ANYOLY

索引是数据库中用来提高数据检索效率的数据结构,它类似于书籍的目录,可以帮助用户快速找到所需的数据,而不必扫描整个数据集,这篇文章主要介绍了MySQL数据库索引的相关资料,需要的朋友可以参考下

第一部分:索引基础

一、索引的本质

1.1 什么是索引?

索引(Index) 是一种数据结构,用于帮助数据库高效地查找数据。

类比理解

图书馆找书的方式:

没有索引 = 逐本翻找(全表扫描)
有索引   = 通过目录快速定位(索引查找)

书籍目录:
- 章节目录:按顺序组织(B+树)
- 关键词索引:按字母排序(索引)
- 页码:快速定位到具体位置(主键)

1.2 索引解决的核心问题

问题场景

-- 100万条用户数据
SELECT * FROM users WHERE name = '张三';

无索引

有索引

性能对比

数据量      无索引耗时    有索引耗时    提升倍数
1万       10ms          1ms           10倍
10万      100ms         1ms           100倍
100万     1000ms        1ms           1000倍
1000万    10000ms       2ms           5000倍

1.3 索引的核心思想

  1. 空间换时间:额外存储索引数据,换取查询速度
  2. 有序结构:数据按特定规则排列,支持快速查找
  3. 减少IO:减少磁盘访问次数(数据库性能瓶颈)

二、索引的底层数据结构

2.1 为什么选择B+树?

常见数据结构对比

数据结构查找时间优点缺点适用场景
数组O(n)简单查询慢小数据量
有序数组O(log n)查询快插入/删除慢静态数据
链表O(n)插入快查询慢不适合索引
二叉搜索树O(log n) ~ O(n)平衡可能退化内存结构
AVL树O(log n)严格平衡旋转成本高内存结构
红黑树O(log n)平衡树高较高内存结构
哈希表O(1)查询极快不支持范围查询等值查询
B树O(log n)多路查找非叶子节点存数据文件系统
B+树O(log n)范围查询快结构复杂数据库索引

为什么不用其他结构?

1. 哈希表

优点:O(1) 查找
缺点:
  ❌ 不支持范围查询(WHERE age > 20)
  ❌ 不支持排序(ORDER BY)
  ❌ 不支持模糊查询(LIKE 'zhang%')
  ❌ 哈希冲突问题
  
使用场景:Memory引擎的HASH索引

2. 二叉搜索树/红黑树

缺点:
  ❌ 树高太高:100万数据,红黑树高度约20层 = 20次IO
  ❌ 每个节点只存一个值,IO利用率低
  ❌ 不适合磁盘存储

B+树高度:100万数据,高度约3-4层 = 3-4次IO

3. B树

缺点:
  ❌ 非叶子节点存储数据,导致每个节点存储的索引项更少
  ❌ 范围查询需要中序遍历,效率低
  
B+树优势:
  ✅ 非叶子节点只存索引,每个节点可存更多索引项
  ✅ 叶子节点用链表连接,范围查询效率高

2.2 B+树详解

B+树的特点

B+树结构示例(3阶B+树):

                [20, 50]               ← 根节点(只存索引)
              /    |    \
        [10,15]  [30,40]  [60,70]     ← 中间节点(只存索引)
         /  |  \   /  |  \   /  |  \
       [...] [...] [...] [...] [...] ← 叶子节点(存数据+指针)
         ↔     ↔     ↔     ↔     ↔   ← 双向链表

核心特性

  1. 所有数据都在叶子节点

    • 非叶子节点只存索引键
    • 叶子节点存完整数据(或指针)
  2. 叶子节点形成有序链表

    • 支持高效的范围查询
    • 支持顺序遍历
  3. 高度平衡

    • 所有叶子节点在同一层
    • 查询任意数据的IO次数一致
  4. 多路查找

    • 每个节点可以有多个子节点(不是二叉)
    • 减少树的高度,减少IO次数

B+树容量计算

假设

非叶子节点

每个节点能存储的索引项数 = 16KB / (8B + 6B) ≈ 1170个

3层B+树能存储的数据量:
  第1层(根):1个节点
  第2层:1170个节点
  第3层(叶子):1170 × 1170 = 1,368,900个节点
  
每个叶子节点存储数据:16KB / 1KB = 16条
总数据量:1,368,900 × 16 ≈ 2000万条

结论:3次IO可以查询2000万数据!

第二部分:索引类型

三、MySQL索引分类

3.1 按数据结构分类

3.2 按物理存储分类

3.3 按逻辑功能分类

3.4 按字段数量分类

3.5 按功能分类

四、主键索引与唯一索引对比

4.1 核心区别

特性主键索引唯一索引
NULL值不允许允许(可多个NULL)
数量只能1个可以多个
索引类型聚簇索引(InnoDB)二级索引
性能⭐⭐⭐⭐⭐ 最快(无需回表)⭐⭐⭐⭐ 较快(需回表)
作用唯一标识每一行保证业务唯一性

关系

主键 = 唯一索引 + NOT NULL + 聚簇索引(InnoDB) + 只能一个

4.2 NULL值处理差异

-- 主键:不允许NULL
CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO t1 VALUES (NULL, '张三');  -- ❌ 报错

-- 唯一索引:允许多个NULL
CREATE TABLE t2 (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(100) UNIQUE
);
INSERT INTO t2 (email) VALUES (NULL);  -- ✅ 成功
INSERT INTO t2 (email) VALUES (NULL);  -- ✅ 成功(SQL标准:NULL != NULL)
INSERT INTO t2 (email) VALUES ('a@example.com');  -- ✅ 成功
INSERT INTO t2 (email) VALUES ('a@example.com');  -- ❌ 报错(重复)

4.3 使用场景对比

-- 主键索引:系统标识
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,  -- 主键:系统唯一标识
    username VARCHAR(50) UNIQUE,           -- 唯一索引:业务约束
    email VARCHAR(100) UNIQUE,             -- 唯一索引:业务约束
    phone VARCHAR(20) UNIQUE               -- 唯一索引:业务约束
);

-- 订单表:主键 + 业务唯一号
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,  -- 主键:内部ID
    order_no VARCHAR(32) UNIQUE,           -- 唯一索引:业务订单号
    idempotent_key VARCHAR(64) UNIQUE      -- 唯一索引:幂等性控制
);

五、聚簇索引与非聚簇索引

5.1 聚簇索引(InnoDB主键)

定义:数据行和索引存储在一起,叶子节点存储完整数据行。

特点

结构

聚簇索引(主键索引):
              [20, 50]
             /    |    \
        [10,15] [30,40] [60,70]
         /  |      |       |
    [完整数据行] [完整数据行] [完整数据行]
    id:10      id:20       id:30
    name:张三   name:李四    name:王五
    age:25     age:30      age:35
    ...        ...         ...

5.2 非聚簇索引(二级索引)

定义:索引和数据分离,叶子节点存储主键值。

结构

二级索引(name索引):
              [李四, 王五]
             /    |    \
       [张三] [李四,刘备] [王五,赵六]
         |      |           |
    [name:张三  [name:李四   [name:王五
     → id:10]   → id:20]     → id:30]
                    ↓
            回表查询主键索引

回表查询

SELECT * FROM users WHERE name = '张三';

执行过程:
1. 在name索引中找到 '张三' → 得到主键id=10
2. 回到主键索引,通过id=10找到完整数据行
3. 返回结果

共2次索引查找(2次B+树遍历)

5.3 InnoDB vs MyISAM

特性InnoDBMyISAM
主键索引聚簇索引(数据在索引中)非聚簇索引(数据在独立文件)
二级索引存储主键值存储数据地址
回表性能需要通过主键索引查询直接通过地址查询
数据文件.ibd(索引+数据).MYD(数据)+ .MYI(索引)
事务支持✅ 支持❌ 不支持
行锁✅ 支持❌ 不支持(表锁)

第三部分:索引失效

六、索引失效的13种场景

6.1 索引列使用函数或表达式

原因:破坏索引的有序性。

-- ❌ 失效
SELECT * FROM users WHERE YEAR(create_time) = 2024;
SELECT * FROM users WHERE age + 1 = 25;

-- ✅ 正确
SELECT * FROM users 
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
SELECT * FROM users WHERE age = 24;

6.2 隐式类型转换

-- 假设phone是VARCHAR
-- ❌ 失效
SELECT * FROM users WHERE phone = 13800138000;

-- ✅ 正确
SELECT * FROM users WHERE phone = '13800138000';

规则:
- 字符串索引 + 数值查询 = 失效
- 数值索引 + 字符串查询 = 生效

6.3 LIKE前缀模糊

-- ❌ 失效
SELECT * FROM users WHERE name LIKE '%张三';
SELECT * FROM users WHERE name LIKE '%张三%';

-- ✅ 生效
SELECT * FROM users WHERE name LIKE '张三%';

6.4 OR条件有非索引列

-- ❌ 失效
SELECT * FROM users WHERE name = '张三' OR age = 20;  -- age无索引

-- ✅ 解决方案
-- 方案1:为age创建索引
-- 方案2:改写为UNION
SELECT * FROM users WHERE name = '张三'
UNION
SELECT * FROM users WHERE age = 20;

6.5 负向查询

-- ❌ 通常失效
SELECT * FROM users WHERE status != 1;
SELECT * FROM users WHERE id NOT IN (1, 2, 3);

-- ✅ 改写为正向
SELECT * FROM users WHERE status IN (0, 2, 3, 4);

6.6 违反最左前缀原则

-- 假设索引:INDEX(name, age, city)

-- ✅ 走索引
WHERE name = '张三'
WHERE name = '张三' AND age = 20
WHERE name = '张三' AND age = 20 AND city = '北京'

-- ❌ 不走索引
WHERE age = 20
WHERE city = '北京'
WHERE age = 20 AND city = '北京'

6.7 范围查询后的列失效

-- 假设索引:INDEX(name, age, city)
SELECT * FROM users 
WHERE name = '张三' AND age > 20 AND city = '北京';
-- name和age走索引,city不走(age是范围查询)

6.8-6.13 其他失效场景

详见完整表格(篇幅原因省略,包括IS NULL、区分度低、结果集过大、IN过多、字符集不一致等)

七、如何诊断索引问题

7.1 使用EXPLAIN

EXPLAIN SELECT * FROM users WHERE name = '张三';

关键字段:
- type: ALL(最差)< index < range < ref < const(最优)
- key: 实际使用的索引(NULL表示未使用)
- rows: 扫描行数(越少越好)
- Extra: 
  - Using index(覆盖索引,最优)
  - Using filesort(需要排序优化)
  - Using temporary(需要临时表优化)

7.2 查看索引统计

-- 查看索引信息
SHOW INDEX FROM users;

-- 更新统计信息
ANALYZE TABLE users;

-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;

-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;

第四部分:索引优化

八、索引设计原则

8.1 什么时候建索引

✅ 应该建索引

❌ 不应该建索引

8.2 索引设计6大原则

1. 选择性原则:高区分度列优先
2. 最左前缀原则:联合索引按顺序使用
3. 覆盖索引原则:包含查询所需列
4. 索引顺序原则:等值>范围、高频>低频
5. 避免冗余原则:删除重复索引
6. 主键设计原则:自增整型优先

8.3 联合索引设计

-- 查询需求:WHERE status = ? AND user_id = ? ORDER BY create_time

-- ✅ 推荐
CREATE INDEX idx_status_user_create 
ON orders(status, user_id, create_time);

-- 理由:
-- 1. status和user_id是等值查询,放前面
-- 2. create_time是排序列,放后面(避免filesort)
-- 3. 可以覆盖索引(如果只查这些列)

九、索引优化实战

9.1 案例1:函数导致索引失效

-- ❌ 问题SQL(1.5秒)
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';

-- EXPLAIN: type=ALL, rows=1000000

-- ✅ 优化后(0.01秒)
SELECT * FROM orders 
WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';

-- EXPLAIN: type=range, rows=1000, 提升150倍

9.2 案例2:覆盖索引优化

-- ❌ 问题SQL(10ms,需要回表)
SELECT user_id, order_no, amount, status 
FROM orders 
WHERE user_id = 12345;

-- ✅ 建立覆盖索引
CREATE INDEX idx_user_order_amount_status 
ON orders(user_id, order_no, amount, status);

-- 优化后(2ms,无需回表),提升5倍

9.3 案例3:深度分页优化

-- ❌ 慢(5秒)
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;

-- ✅ 快(0.5秒)- 子查询
SELECT o.*
FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 1000000, 10
) t ON o.id = t.id;

-- ✅ 最快(0.01秒)- 游标分页
SELECT * FROM orders 
WHERE id > 1000000  -- 上次最后一条ID
ORDER BY id LIMIT 10;

第五部分:面试必备

十、高频面试问题

Q1:什么是索引?作用是什么?

索引是一种数据结构(通常是B+树),用于帮助数据库高效查找数据。

作用

  1. 大幅提升查询速度(O(n) → O(log n))
  2. 加速排序和分组
  3. 避免全表扫描
  4. 保证数据唯一性

Q2:为什么使用B+树?

B+树 vs 红黑树

B+树 vs B树

Q3:聚簇索引和非聚簇索引的区别?

特性聚簇索引非聚簇索引
数据存储索引和数据一起索引和数据分离
叶子节点存储完整数据行存储主键值
数量限制一个表只能一个可以多个
查询性能快(无需回表)慢(需要回表)

Q4:主键索引和唯一索引的区别?

  1. NULL值:主键不允许,唯一索引允许
  2. 数量:主键1个,唯一索引多个
  3. 类型:主键是聚簇索引,唯一索引是二级索引
  4. 性能:主键查询更快(无需回表)

Q5:什么是覆盖索引?

查询的所有列都在索引中,无需回表查询。

优势:减少IO、提升速度、减少锁竞争

-- 假设索引:INDEX(name, age)

-- ✅ 覆盖索引
SELECT name, age FROM users WHERE name = '张三';
-- Extra: Using index

-- ❌ 非覆盖索引
SELECT * FROM users WHERE name = '张三';
-- 需要回表

Q6:联合索引的最左前缀原则?

联合索引INDEX(a, b, c)相当于创建了(a)(a,b)(a,b,c)三个索引。

查询必须包含最左列才能使用索引。

Q7:什么情况下索引会失效?

  1. 索引列使用函数
  2. 隐式类型转换
  3. LIKE前缀模糊
  4. OR条件有非索引列
  5. 负向查询
  6. 违反最左前缀
  7. 查询结果集过大

Q8:如何设计高效索引?

设计原则

  1. 选择性原则:高区分度列(>0.1)
  2. 最左前缀原则:合理安排顺序
  3. 覆盖索引原则:包含查询列
  4. 索引顺序:等值>范围、高频>低频

Q9:索引越多越好吗?

不是!索引有代价

建议

Q10:主键为什么推荐自增整型?

特性自增整型UUID
存储4/8字节36字节
插入顺序插入随机插入,页分裂
性能整型比较快字符串比较慢
分布式需特殊处理天然支持

折中方案:雪花算法(BIGINT,有序)

总结

索引核心知识图谱

索引本质:空间换时间的数据结构
    ↓
底层结构:B+树(多路平衡查找树)
    ↓
索引分类:
├─ 主键索引(聚簇索引,最快)
├─ 唯一索引(二级索引,较快)
├─ 普通索引(二级索引,常用)
└─ 特殊索引(全文、空间)
    ↓
优化要点:
├─ 避免失效(13种场景)
├─ 合理设计(6大原则)
├─ 覆盖索引(减少回表)
└─ 定期维护(ANALYZE、OPTIMIZE)
    ↓
性能提升:O(n) → O(log n)

索引使用口诀

索引设计三原则:选择性、有序性、覆盖性
索引失效三大忌:函数、类型、通配符
索引优化三步走:EXPLAIN、分析、重构

到此这篇关于MySQL数据库索引的文章就介绍到这了,更多相关MySQL索引指南内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文