六,索引详解

一,概述

索引是帮助MYSQL高效获取数据有序数据结构

数据库维护着满足特定查找算法的数据结构,这种数据结构以某种方式指向数据。

这样就可以在数据结构上实现高级查找方法,这种数据结构就是索引。

  • 无索引的时候查询数据会进行全表扫描操作
  • 有索引的时候查询数据会进行排序二叉树的数据结构来查找数据

索引的优缺点:

  • 优点:提高排序效率,检索效率。
  • 缺点:降低插入,删除,更新的效率且索引本身占用空间。

二,索引的结构

索引是在存储引擎层实现,故不同的存储引擎有不同的索引结构。

2.1 分类

索引结构 描述
B+Tree结构 最常见的索引类型,大部分引擎都支持B+树索引
Hash结构 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询。
R-tree结构(空间索引) 空间索引是MyISAM引擎的一种特殊索引类型,主要用于地理空间数据描述,使用较少。
Full-text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式。

2.2 支持情况

索引 InnoDB MyISAM Memory
B+Tree索引 支持 支持 支持
Hash索引 不支持 不支持 支持
R-Tree索引 不支持 支持 不支持
Full-text 5.6版本后支持 支持 不支持

2.3 索引相关数据结构

2.3.1 二叉树

顺序插入的时候,会形成一个链表,查询性能大大降低,大量数据的情况下,层次较深,检索速度慢,可以通过红黑树解决,但红黑树在大数据量的情况下,层次也较深,检索速度很慢

2.3.2 B-Tree(多路平衡查找树)

介绍 :B树是一种 自平衡的、多路搜索树,专门为需要高效读写大量数据的场景设计(如数据库、文件系统)。

核心特点

  • 每个节点可以存储多个键(关键字),而不是像二叉树那样每个节点只能存一个键。
  • 通过严格的平衡规则,确保所有叶子节点位于同一层,避免树的高度过高。
  • 优化磁盘I/O:节点的大小通常设计为磁盘块的大小(如4KB),减少磁盘访问次数。

性质

  • 一颗m阶的B树存放(m-1)个关键字,一个节点最多m个指针引用。
  • 叶节点具有相同的深度,叶结点的指针为空
  • 结点中的数据从左到右递增
  • 当B-Tree作为索引元素时,所有的索引元素不可以重复

B树形成流程简述

首先定义一个5阶的B树(平衡5路查找树),现在我们要把

4、9、32、12、24、30、51、29、69、31、90、70、75、79、、80、85、91

  1. 根据B树的性质得5阶B树一个节点最多4个值,故取出4 9 32 12形成第一个节点并内部排序成4 9 12 32

  2. 插入24

  3. 插入30

  4. 插入51

  5. 插入29

  6. 插入69

  7. 插入31

  8. 插入90

  9. 插入70

  10. 插入75

  11. 插入79

  12. 插入80

  13. 插入85

  14. 插入91

具体去看数据结构B树的组成会更方便理解。暂时只要了解B树的结构性质即可。

2.3.3 B+Tree

介绍B+树 是一种自平衡的多路搜索树,是B树的扩展版本。它广泛应用于数据库和文件系统中,特别适合处理大量数据的 范围查询顺序访问

核心特点

  1. 所有数据存储在叶子节点,内部节点仅作为索引(存放键值,不存储实际数据)。
  2. 叶子节点通过指针链接,形成有序链表,便于范围查询。
  3. 更高的扇出(Fan-out):内部节点可容纳更多子节点,减少树的高度和磁盘I/O次数。

首先定义一个5阶的B树(平衡5路查找树),现在我们生成B+树如图

4、9、32、12、24、30、51、29、69、31、90、70、75、79、、80、85、91

b+树相 vs b树的区别

特性 B树 B+树
数据存储位置 所有节点均可存储数据 仅叶子节点存储数据,内部节点仅索引
叶子节点链接 叶子节点通过指针链接成有序链表
查询效率稳定性 不稳定(可能在内部节点找到数据) 稳定(必须访问叶子节点)
范围查询性能 较差(需遍历树) 极优(直接遍历叶子链表)
内部节点结构 键值与数据共存 键值仅用于路由,不存储数据
空间利用率 较低(非叶子节点存储数据) 较高(内部节点纯索引)

2.3.4 Hash

特点

  • 只能进行对比操作,即 = 和 in这种精确的值,不支持范围查询。

  • 无法利用索引进行排序。

  • 查询效率很高,一般进行一次索引即可(不出现hash冲突的情况下)效率高于B+树

在MYSQL数据库中,Memory引擎支持hash索引,但是在innodb引擎具有自适应hash功能

InnoDB 的 自适应哈希索引(Adaptive Hash Index,AHI) 是 InnoDB 引擎内部的一个优化机制,旨在针对特定查询场景加速等值查询(如 WHERE key = value)。其核心设计目标是 减少对 B+Tree 的频繁访问,尤其是在热点数据的查询场景中提升性能。

简单来说就是对频繁的等值查询建立一个缓存

2.4 为什么InnoDB引擎选择使用B+tree索引结构

InnoDB 选择 B+Tree 作为索引结构,主要基于以下核心原因:

  1. 减少磁盘 I/O 次数,提升查询效率

    • B+Tree 的多叉结构 :每个节点可以存储大量键值(M 叉,通常上千),使得树的高度较低(一般 3-4 层即可存储千万级数据)。树高越低,查询时需要的磁盘 I/O 次数越少。

    • 对比 B-Tree:B-Tree 的节点存储数据本身,导致单个节点能容纳的键值更少,树高可能更高,I/O 次数更多。

    • 对比二叉树:二叉平衡树(如红黑树)在存储海量数据时,树高会急剧增加(例如 1000 万数据需要约 24 层),导致 I/O 次数不可接受。

  2. 天然适合范围查询

    • 叶子节点的链表结构 :B+Tree 的所有数据存储在叶子节点,且叶子节点通过双向链表连接。范围查询(如 WHERE id BETWEEN 10 AND 100)只需遍历链表即可完成,无需回溯上层节点。

    • 对比 B-Tree:B-Tree 的数据分布在所有节点,范围查询需要复杂的中序遍历,可能涉及多次非叶子节点的访问。

  3. 查询性能稳定

    • 所有查询最终落到叶子节点 :无论查询条件是主键、唯一索引还是普通索引,B+Tree 的查询路径长度相同(等于树高),时间复杂度稳定为 O(log N)

    • 对比 B-Tree:B-Tree 可能在非叶子节点直接命中数据,导致查询时间不稳定。

  4. 充分利用磁盘预读特性

    • 局部性原理:磁盘按页(通常 4KB/16KB)读取数据,B+Tree 的节点大小设置为磁盘页的整数倍,单个节点可被一次性加载,减少 I/O 次数。

    • 顺序访问优化:B+Tree 的叶子节点链表适合顺序扫描(如全表扫描或范围查询),而机械磁盘的顺序读取速度远高于随机读取。

  5. 支持聚簇索引与二级索引的统一结构

    • InnoDB 的聚簇索引:数据行直接存储在 B+Tree 的叶子节点中,主键索引即数据文件,避免二次查询。

    • 二级索引(非聚簇索引):叶子节点存储主键值,通过回表查询获取完整数据,B+Tree 结构统一适配这种设计。


总结

InnoDB 选择 B+Tree 的核心原因是:在减少磁盘 I/O高效支持范围查询稳定查询性能之间实现了最佳平衡。这种设计完美契合了关系型数据库(尤其是 OLTP 场景)的典型负载,即大量基于索引的点查询、范围查询和排序操作。

三,索引的分类

3.1 按逻辑功能(用途)分类

  1. 主键索引(PRIMARY KEY)
    • 特点 :唯一标识每行数据,不允许重复和空值(NULL),每个表只能有一个主键索引。
    • 示例CREATE TABLE users (id INT PRIMARY KEY, ...);
  2. 唯一索引(UNIQUE INDEX)
    • 特点 :确保列值的唯一性,允许空值(NULL),但每个NULL视为唯一值(不同数据库实现可能不同)。
    • 示例CREATE UNIQUE INDEX email_unique ON users(email);
  3. 普通索引(INDEX)
    • 特点:最基本的索引,无唯一性约束,仅加速查询。
    • 示例CREATE INDEX idx_age ON users(age);
  4. 全文索引(FULLTEXT INDEX)
    • 特点:针对文本内容(如文章、描述)的关键词搜索,支持自然语言检索。
    • 引擎支持:MyISAM 默认支持,InnoDB 从5.6版本开始支持。
    • 示例CREATE FULLTEXT INDEX content_idx ON articles(content);
  5. 组合索引(复合索引)
    • 特点 :将多个列组合成一个索引,遵循最左前缀原则(查询条件需包含最左列才能生效)。
    • 示例CREATE INDEX idx_name_age ON users(name, age);
      • 有效查询:WHERE name='Alice'WHERE name='Alice' AND age=30
      • 无效查询:WHERE age=30(未使用最左列name

3.2 按数据结构分类

  1. B+树索引
    • 特点 :MySQL默认的索引类型,支持范围查询(>, <, BETWEEN)和排序,适用于等值查询和范围查询。
    • 适用场景:大多数情况下的索引选择,如主键、唯一索引、普通索引均基于B+树。
  2. 哈希索引
    • 特点 :基于哈希表实现,仅支持精确等值查询(=),查询速度极快(O(1)),但不支持范围查询和排序。
    • 引擎支持 :Memory引擎默认支持,InnoDB支持自适应哈希索引(由引擎自动管理)。
    • 示例CREATE INDEX idx_hash USING HASH ON users(email);
  3. 全文索引(倒排索引)
    • 特点:通过分词构建倒排列表,实现关键词搜索。
  4. R-Tree索引(空间索引)
    • 特点 :用于地理空间数据(如经纬度),支持空间查询(ST_Contains, ST_Distance)。
    • 引擎支持:MyISAM和InnoDB(5.7+)。
    • 示例CREATE SPATIAL INDEX location_idx ON places(coordinates);

3.3 按存储方式分类

  1. 聚集索引(Clustered Index)
    • 特点:索引的叶子节点直接存储行数据,数据按索引顺序物理排序。
    • 规则
      • 若表定义了主键,则主键为聚集索引。
      • 若无主键,则选择第一个唯一非空索引。
      • 若均无,InnoDB会生成隐藏的ROW_ID作为聚集索引。
    • 性能:数据查询快,但插入和更新可能需调整数据位置。
  2. 二级索引(Secondary Index)
    • 特点 :叶子节点存储主键值(非数据行),查询需回表(通过主键再到聚集索引获取数据)。
    • 示例:普通索引、唯一索引、组合索引均为二级索引。

回表 :指的是在使用**二级索引(非聚集索引)查询数据时,需要根据索引中存储的主键值,回到聚集索引(主键索引)**中再次查找完整数据行的过程。

回表的核心原理

  1. 索引结构差异
    • 聚集索引 :叶子节点直接存储完整数据行 (如 id 是主键时,按 id 排序存储数据)。
    • 二级索引 (如普通索引、唯一索引):叶子节点存储主键值,而非数据行。
  2. 查询流程
    • 步骤1 :通过二级索引查找到目标记录对应的主键值
    • 步骤2 :再通过主键值到聚集索引中查找完整数据行

3.4 其他特殊类型

  1. 前缀索引
    • 特点:对字段的前N个字符创建索引,节省空间,但可能降低区分度。
    • 示例CREATE INDEX idx_prefix ON articles(title(10));(仅索引title的前10个字符)
  2. 自适应哈希索引(AHI)
    • 特点:InnoDB自动为频繁访问的索引页创建哈希索引,用户无法手动创建。

四,索引的语法

创建索引

sql 复制代码
Create [unique|fulltext] index 索引名 on 表名(字段列表);
  • unique代表唯一索引,字段列表中不能出现重复
  • fulltext代表全文索引
  • 如果这俩个都不加就默认创建常规索引
  • 一个索引只关联一个 字段的叫单列索引
  • 一个索引关联多个 字段的叫联合索引
  • 关联了主键 的索引叫主键索引

查看索引

sql 复制代码
show index from 表名;

删除索引

sql 复制代码
Drop index 索引名 on 表名;

五,SQL性能分析

5.1 SQL执行效率

查看服务器状态信息

sql 复制代码
show [session|global] status;

5.1.1 常见状态变量及含义

变量名 说明
Connections 服务器启动以来总连接数(含成功和失败)。
Threads_connected 当前活跃连接数(即并发连接数)。
Queries 服务器启动以来执行的 SQL 语句总数(含 SELECT、UPDATE、DELETE 等)。
Innodb_rows_read InnoDB 引擎读取的行数。
Innodb_rows_inserted InnoDB 引擎插入的行数。
Slow_queries 执行时间超过 long_query_time 的慢查询数量。
Select_scan 全表扫描的 SELECT 查询次数(需优化索引)。
Table_locks_waited 表级锁等待次数(高值可能表示锁竞争)。
Aborted_connects 尝试连接到数据库但失败的次数(如密码错误)。

5.1.2 实际应用场景

场景1:监控查询负载

sql 复制代码
SHOW GLOBAL STATUS LIKE 'Queries';
  • 两次执行后计算差值,可得到 QPS(每秒查询量)

    sql 复制代码
    第一次值:Queries = 1000
    第二次值(间隔1秒后):Queries = 1200
    QPS = (1200 - 1000) / 1 = 200

场景2:分析索引有效性

sql 复制代码
SHOW GLOBAL STATUS LIKE 'Handler_read%';
  • Handler_read_first:索引首次被读的次数。
  • Handler_read_next:索引顺序读的次数。
  • Handler_read_prev:索引逆序读的次数。
  • Handler_read_rnd:随机读的次数(高值可能表明缺少索引或全表扫描)。

场景3:检测锁竞争

sql 复制代码
SHOW GLOBAL STATUS LIKE 'Table_locks_waited';
  • 若该值持续增长,说明存在表级锁竞争,需优化事务或改用行级锁(InnoDB)。

5.2 慢日志查询

5.2.1 配置慢查询日志

慢查询日志是 MySQL 中用于记录执行时间超过指定阈值的 SQL 语句的核心工具,帮助开发者快速定位低效查询并进行优化。

  • 慢查询日志记录了所有执行时间超过指定参数(默认10s)的所有SQL语句的日志,默认是关闭的。

查看当前配置

sql 复制代码
SHOW VARIABLES LIKE '%slow_query%';
-- 关键参数:
-- slow_query_log:是否开启(ON/OFF)
-- slow_query_log_file:日志文件路径
-- long_query_time:慢查询阈值(秒)
-- log_queries_not_using_indexes:是否记录未使用索引的查询(ON/OFF)

动态开启(无需重启MySQL)

sql 复制代码
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';

-- 设置阈值(如2秒)
SET GLOBAL long_query_time = 2;

-- 记录未使用索引的查询(即使执行时间未超阈值)
SET GLOBAL log_queries_not_using_indexes = 'ON';

永久配置(修改 my.cnf/my.ini)

sql 复制代码
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/mysql-slow.log
long_query_time = 2
log_queries_not_using_indexes = 1

重启 MySQL 生效:

sql 复制代码
systemctl restart mysqld

5.2.2 慢查询日志格式解析

一条典型的慢查询日志记录如下:

tex 复制代码
# Time: 2023-10-05T14:23:45.123456Z
# User@Host: root[root] @ localhost [127.0.0.1]
# Query_time: 5.123456  Lock_time: 0.001234 Rows_sent: 10  Rows_examined: 100000
SET timestamp=1696515825;
SELECT * FROM orders WHERE status = 'pending' ORDER BY create_time DESC;
  • Query_time:SQL 执行时间(单位:秒)。
  • Lock_time:等待锁的时间。
  • Rows_sent:返回给客户端的行数。
  • Rows_examined:扫描的行数(值越大,越可能缺少索引)。
  • SQL 语句:记录的完整查询内容。

5.2.3 分析慢查询日志的工具

mysqldumpslow(MySQL 自带)

bash 复制代码
# 按执行时间排序统计前10条慢查询
mysqldumpslow -s t -t 10 /var/lib/mysql/mysql-slow.log

# 按出现次数排序
mysqldumpslow -s c -t 10 /var/lib/mysql/mysql-slow.log

# 输出示例
Count: 5  Time=4.12s (20s)  Lock=0.00s (0s)  Rows=10.0 (50), root[root]@localhost
  SELECT * FROM users WHERE age > N

pt-query-digest(Percona Toolkit)

更强大的第三方工具,支持生成详细报告:

bash 复制代码
# 分析日志并输出报告
pt-query-digest /var/lib/mysql/mysql-slow.log > slow_report.txt

# 输出内容示例
# 1. 总体统计:总查询数、唯一 SQL 指纹、时间分布
# 2. 单个 SQL 分析:执行次数、平均/最大耗时、扫描行数、索引建议

5.3 Profiles

Profiling 是 MySQL 中用于分析单条 SQL 语句执行细节的工具,能够展示查询过程中各阶段的耗时,帮助开发者定位性能瓶颈

Profiles作用

  1. 逐阶段耗时分析:分解 SQL 执行流程(如语法解析、优化、锁等待、数据传输等),显示每个步骤的耗时。
  2. 精准定位瓶颈 :识别慢查询的具体阶段(如 Sending dataSystem lock)。
  3. 对比优化效果:在修改 SQL 或索引后,重新 Profiling 验证优化效果。

5.3.1 启用 Profiling

1. 开启 Profiling 功能

sql 复制代码
-- 开启会话级别的 Profiling(仅对当前连接有效)
SET SESSION profiling = 1;

-- 查看是否启用成功(Value 应为 ON)
SHOW VARIABLES LIKE 'profiling';

2. 执行待分析的 SQL

sql 复制代码
-- 示例:分析一个复杂查询
SELECT * FROM orders 
WHERE user_id IN (SELECT id FROM users WHERE age > 30) 
ORDER BY create_time DESC LIMIT 100;

3. 查看 Profiling 结果

sql 复制代码
-- 列出所有已记录的查询及其 Query_ID
SHOW PROFILES;

-- 查看某条查询的详细耗时(替换[QUERY_ID])
SHOW PROFILE FOR QUERY [QUERY_ID];

5.3.2 Profiling 输出解读

执行 SHOW PROFILE FOR QUERY 1; 示例输出:

sql 复制代码
+----------------------+----------+
| Status               | Duration |
+----------------------+----------+
| starting             | 0.000065 |
| checking permissions | 0.000010 |
| Opening tables       | 0.000023 |
| init                 | 0.000045 |
| System lock          | 0.000018 |
| optimizing           | 0.000016 |
| statistics           | 0.000025 |
| preparing            | 0.000020 |
| executing            | 0.000006 |
| Sending data         | 0.123456 |  -- 主要耗时阶段
| end                  | 0.000012 |
| query end            | 0.000009 |
| closing tables       | 0.000011 |
| freeing items        | 0.000020 |
| cleaning up          | 0.000015 |
+----------------------+----------+

关键状态说明

状态 说明
Sending data 从存储引擎读取数据并发送到客户端(常见瓶颈,可能涉及全表扫描或回表)。
System lock 等待表锁或行锁(高并发场景下需优化事务或索引减少锁冲突)。
Sorting result 对结果排序(无索引排序时可能消耗大量内存和 CPU)。
Creating tmp table 创建临时表(常见于 GROUP BY、子查询,需优化 SQL 或增加内存配置)。

5.4 Explain执行计划

EXPLAIN 是 MySQL 中分析 SQL 语句执行计划的核心工具,通过解析查询优化器选择的执行路径,帮助开发者判断索引使用情况、表关联顺序及潜在性能瓶颈。

5.4.1 EXPLAIN 基础语法

sql 复制代码
EXPLAIN [FORMAT=JSON] SELECT ...;
  • 默认格式:表格形式展示执行计划(常用)。
  • JSON 格式 :更详细信息,适合程序解析(如 EXPLAIN FORMAT=JSON SELECT ...)。

5.4.2 EXPLAIN 输出字段解析

字段 说明 优化关注点
id 查询序号(层级)。id 相同表示同级执行,id 越大优先级越高(如子查询)。 子查询优化、嵌套逻辑。
select_type 查询类型。 简单查询、子查询、UNION 等场景区分。
table 访问的表名(或别名)。 确认实际访问的表,尤其是多表 JOIN 场景。
partitions 匹配的分区(仅当表分区时有效)。 分区裁剪是否生效。
type 访问类型 (关键指标)。从最优到最差排序:system > const > eq_ref > ref > range > index > ALL 避免 ALL(全表扫描),优先优化为 refrange
possible_keys 可能使用的索引列表。 检查是否遗漏预期索引。
key 实际选择的索引。 是否命中高效索引,避免 NULL(未用索引)。
key_len 索引使用的字节数。 判断索引是否充分利用(如组合索引的前缀长度)。
ref 索引关联的列或常量。 确认索引匹配的字段(如 consttable.column)。
rows 预估扫描的行数(关键指标)。 值越小越好,高值可能需优化索引或查询条件。
filtered 存储引擎返回数据后,经过 WHERE 条件过滤的剩余行百分比(0~100)。 低百分比表示 WHERE 过滤效率低,可能需优化条件或索引。
Extra 附加信息 (关键指标)。常见值:Using indexUsing temporaryUsing filesort 等。 避免 Using filesortUsing temporary,优先优化排序和分组逻辑。

5.4.3 核心字段详解与优化方向

1. type(访问类型)

  • system:表仅有一行数据(系统表)。
  • const :通过主键或唯一索引查找,最多返回一行(如 WHERE id=1)。
  • eq_ref :JOIN 时使用主键或唯一索引关联(如 A JOIN B ON A.id=B.id)。
  • ref :非唯一索引查找(如 WHERE name='Alice')。
  • range :索引范围扫描(如 WHERE age > 20)。
  • index:全索引扫描(遍历索引树)。
  • ALL:全表扫描(需紧急优化)。

优化建议

  • 确保 WHERE 条件列有索引,避免 ALL
  • 使用组合索引时遵循最左前缀原则

2. Extra(附加信息)

  • Using index:覆盖索引(无需回表)。
  • Using where:存储引擎返回数据后,在 Server 层再次过滤。
  • Using temporary:使用临时表(常见于 GROUP BY、DISTINCT 未用索引)。
  • Using filesort:文件排序(内存或磁盘排序,需优化 ORDER BY)。
  • Using join buffer :使用 JOIN 缓冲区(增大 join_buffer_size 或优化 JOIN 条件)。

优化建议

  • 为 GROUP BY 和 ORDER BY 字段添加索引,消除临时表和文件排序。
  • 减少 SELECT *,使用覆盖索引。

3. select_type(查询类型)

  • SIMPLE:简单查询(无子查询或 UNION)。
  • PRIMARY:外层查询(包含子查询时)。
  • SUBQUERY:子查询中的第一个 SELECT。
  • DERIVED:派生表(FROM 子句中的子查询)。
  • UNION:UNION 中的第二个或后续查询。
  • UNION RESULT:UNION 的结果集。

优化建议

  • 将复杂子查询改写为 JOIN。
  • 避免多层派生表(DERIVED),改用临时表或优化查询结构。

5.4.4 EXPLAIN 优化流程图

  1. 检查 type 字段
    • 若为 ALL → 添加 WHERE 条件索引。
    • 若为 index → 检查是否可优化为范围扫描。
  2. 分析 Extra 字段
    • Using filesort → 为 ORDER BY 添加索引。
    • Using temporary → 优化 GROUP BY 或使用覆盖索引。
  3. 预估 rows 值
    • 高 rows 值 → 检查索引选择性或优化查询条件。
  4. 验证 key 选择
    • 未使用预期索引 → 强制索引(USE INDEX)或优化索引结构。

六,使用索引的规则

6.1 联合索引

  1. 索引结构

    • 数据组织方式 :联合索引按字段顺序构建 B+树 。 例如,索引 (col1, col2, col3) 的键值按 col1col2col3 排序存储。

      实际上默认还加了一个主键字段(主键,col1,col2,col3)

      • 排序规则 :先按 col1 排序,col1 相同则按 col2 排序,依此类推。
  2. 最左前缀原则(Leftmost Prefix Principle)

    联合索引 (col1, col2, col3) 可支持以下查询:

    sql 复制代码
    WHERE col1 = ?                        -- ✅ 使用索引
    WHERE col1 = ? AND col2 = ?           -- ✅ 使用索引
    WHERE col1 = ? AND col2 = ? AND col3 = ? -- ✅ 使用索引

    但以下查询无法使用该索引:

    sql 复制代码
    WHERE col2 = ?                        -- ❌ 未包含最左列 col1
    WHERE col1 = ? AND col3 = ?           -- ❌ 跳过中间列 col2
  3. 与单列索引的区别

    联合索引 != 创建多个索引

    • 覆盖性 :联合索引支持前缀组合的查询,但无法替代所有单列索引。 例如,索引 (col1, col2) 能加速 col1col1+col2 的查询,但无法加速单独查 col2

    • 索引数量:联合索引本质是单个索引,并非真正创建多个独立索引。

6.2 最左匹配规则的底层原理

索引的底层是一颗B+树,联合索引底层当然也是一颗B+树,区别在于联合索引的键值是多个,但是构建一颗B+树只能根据一个值来构建,故数据库依据联合索引最左字段来构建B+树!

假定创建一个(a,b,c)的联合索引,索引树如图

该图就是通过(a,b,c)联合索引形成的B+树,可以看出非叶子节点存储的是第一个关键字的所有a,叶子节点存储的是三个关键字的数据。

可以看出a是有序的b,c是无序的,当a相同时b是有序的,b相同时又是有序的,这就是最左匹配规则的底层原理!

联合索引就是按照第一列进行排序,然后第一列排好序的基础上再对第二列进行排序,以此类推。如果没有第一列直接访问第二列,第二列肯定是无序的,直接访问后面的列就用不到索引了。

6.3 查询优化器

MySQL 查询优化器是 SQL 执行前的核心模块,负责将用户的 SQL 语句转化为高效执行计划。其目标是以最小的资源消耗(CPU、I/O、内存)获取正确结果。

问:如果举例索引顺序为(a,b,c)但查询条件为 where b=1 and a = 2;为什么还用到了索引

答:理论上索引对顺序是敏感的,但是由于 MySQL 的查询优化器会自动调整 where 子句的条件顺序以使用适合的索引,所以 MySQL 不存在 where 子句的顺序问题而造成索引失效。

6.4 索引失效的几种情况与解决方法

6.4.1 在索引列上使用函数或表达式

场景

sql 复制代码
-- 索引列使用函数
SELECT * FROM users WHERE YEAR(create_time) = 2023;

-- 索引列参与运算
SELECT * FROM users WHERE age + 1 > 30;

原因: 索引存储的是原始值,而非计算后的结果,优化器无法直接匹配索引树。

解决

  • 改写 SQL,保持索引列原始值

    sql 复制代码
    SELECT * FROM users 
    WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
    
    SELECT * FROM users WHERE age > 29;  -- age > (30 - 1)
  • MySQL 8.0+ 函数索引 (直接索引计算后的值):

    sql 复制代码
    CREATE INDEX idx_year ON users( (YEAR(create_time)) );

6.4.2 违反最左前缀原则(联合索引)

场景 :联合索引 (a, b, c),但查询条件未包含最左列:

sql 复制代码
SELECT * FROM table WHERE b = 2 AND c = 3;

原因

联合索引按 a → b → c 顺序构建,跳过最左列时无法利用索引。

解决

  • 调整查询条件,包含最左列

    sql 复制代码
    SELECT * FROM table WHERE a = 1 AND b = 2 AND c = 3;
  • 重建联合索引顺序(根据高频查询场景调整字段顺序)。


6.4.3 隐式类型转换

场景

字段为字符串类型,但查询时使用数值:

sql 复制代码
-- 假设 phone 是 VARCHAR 类型
SELECT * FROM users WHERE phone = 13800138000;

原因: MySQL 会将字符串字段隐式转换为数值,导致索引失效。

解决

  • 保持类型一致

    sql 复制代码
    SELECT * FROM users WHERE phone = '13800138000';

6.4.4 使用 OR 连接非索引字段

场景

sql 复制代码
-- 索引为 (age)
SELECT * FROM users WHERE age = 25 OR name = 'Alice';

原因 : 若 OR 两侧字段不全是索引列,优化器会选择全表扫描。

解决

  • 使用 UNION 替代 OR

    sql 复制代码
    SELECT * FROM users WHERE age = 25
    UNION
    SELECT * FROM users WHERE name = 'Alice';
  • name 字段单独建索引

    sql 复制代码
    CREATE INDEX idx_name ON users(name);

6.4.5 模糊查询以通配符开头

场景

sql 复制代码
-- 索引为 (title)
SELECT * FROM articles WHERE title LIKE '%MySQL%';

原因 : 前导通配符 % 导致无法利用索引顺序扫描。

解决

  • 使用前缀匹配 (仅支持 LIKE 'MySQL%'):

    sql 复制代码
    SELECT * FROM articles WHERE title LIKE 'MySQL%';
  • 全文索引(针对大文本搜索):

    sql 复制代码
    CREATE FULLTEXT INDEX ft_content ON articles(title);
    SELECT * FROM articles WHERE MATCH(title) AGAINST('MySQL');

6.4.6 范围查询后使用联合索引列

场景 :联合索引 (a, b, c),但范围查询中断后续列:

sql 复制代码
SELECT * FROM table WHERE a = 1 AND b > 10 AND c = 2;

原因 : 范围查询 b > 10 后,c 无法利用索引过滤。

解决

  • 调整索引顺序(将等值列放在范围列前):

    sql 复制代码
    CREATE INDEX idx_a_c_b ON table(a, c, b);

6.4.7 索引选择性过低

场景

在性别(gender)字段上建索引:

sql 复制代码
SELECT * FROM users WHERE gender = 'Female';

原因: 性别只有少数枚举值(如 Male/Female),索引区分度低,优化器认为全表扫描更快。

解决

  • 避免对低区分度字段建索引 ,或结合其他字段建组合索引:

    sql 复制代码
    CREATE INDEX idx_gender_age ON users(gender, age);

6.4.8 使用 !=NOT 操作符

场景

sql 复制代码
-- 索引为 (status)
SELECT * FROM orders WHERE status != 'paid';

原因: 非等值查询需要扫描大部分索引树,优化器可能选择全表扫描。

解决

  • 改写为范围查询 (需结合业务逻辑):

    sql 复制代码
    SELECT * FROM orders WHERE status IN ('unpaid', 'pending');

6.4.9 全表扫描更快时

场景:当表中数据量极小时,优化器认为全表扫描成本低于索引扫描。

解决

  • 无需优化:此为优化器合理决策,无需干预。

6.4.10 未触发索引覆盖(回表开销大)

场景

sql 复制代码
-- 索引为 (age)
SELECT name, email FROM users WHERE age > 25;

原因 : 索引未包含 nameemail,需回表查询。

解决

  • 使用覆盖索引

    sql 复制代码
    CREATE INDEX idx_age_name_email ON users(age, name, email);

总结:索引失效场景速查表

场景 解决方法
函数或表达式 避免对索引列计算,或使用函数索引(MySQL 8.0+)
违反最左前缀原则 调整查询条件或索引顺序
隐式类型转换 保持字段与查询值类型一致
OR 连接非索引字段 改用 UNION 或为字段建索引
前导模糊查询 使用前缀匹配或全文索引
范围查询中断联合索引 调整索引顺序,优先等值列
低选择性索引 结合高区分度字段建组合索引
!=NOT 改写为 IN 或范围查询
覆盖索引未命中 创建覆盖索引,包含查询所有字段

通过合理设计索引和 SQL 语句,可有效避免索引失效,提升查询性能。建议结合 EXPLAIN 工具分析执行计划,验证优化效果。

6.5 SQL提示

就是explain字段中的possible_key,就MYSQL提示我们使用的索引

假定有多个索引,而MYSQL会自动选择其中一个他认为比较好的索引,但有时另外一个索引更好。这个时候我们就使用SQL提示来解决。

建议数据库使用指定索引

sql 复制代码
select * from 表名 use index(索引名) where 条件;

告诉数据库不要用指定索引

sql 复制代码
select * from 表名 ignore index(索引名) where 条件;

强制数据库使用指定索引

sql 复制代码
select * from 表名 force index(索引名) where 条件;

6.6 覆盖索引

创建一个索引,该索引包含查询中用到的所有字段,称为"覆盖索引"。使用覆盖索引,MySQL 只需要通过索引就可以查找和返回查询所需要的数据,而不必在使用索引处理数据之后再进行回表操作,覆盖索引可以一次性完成查询工作,有效减少IO,提高查询效率。

简单来的说,就是让查询的字段(包括where子句中的字段),都是索引字段

sql 复制代码
select a,b,c from 表名 where a=条件 and b=条件 and c=条件;

使用联合索引(a,b,c)即可实现覆盖索引。

6.7 前缀索引

当字段类型为(varchar,char)的时候,有时候需要索引很长的字符串,这样会占用大量空间,查询时候浪费大量磁盘IO,非常影响效率。

解决办法:将字符串的一部分前缀建立索引即可。

6.7.1 语法

建立前缀索引

sql 复制代码
Create index 索引名 on 表名(字段(n));
  • 前缀长度:根据索引选择性决定,索引的选择性越高,查询效率就越高。

    • 唯一索引的选择性为1,这时最好的索引选择性,性能最高。
  • 选择性:索引值中不重复的部分,和数据中记录总数的比值。

  • 选择性换算公式:

    sql 复制代码
    select count( distinct substring(字段名,1,n)  ) /  count(*) from 表名;

    n表示前几个字符,自己随机选,求出最好的选择性即可.

七,索引的设计原则

MySQL 的 InnoDB 存储引擎 中,主键(PRIMARY KEY)默认会自动创建一个聚集索引(Clustered Index),你不需要手动为它额外添加索引。这是 InnoDB 引擎的核心设计之一,目的是优化数据的存储和检索效率。

  1. 针对数据量大的,查询频繁的表建立索引。
  2. 针对常作为,where,order by,group by的条件的字段建立索引。
  3. 尽量选择区分度高的建立索引,区分度越高效率越高。
  4. 尽量建立唯一索引。
  5. 尽量使用联合索引,减少单列索引,查询的时候尽量使用覆盖所有,减少回表查询,从而提高销量。
  6. 控制索引的数量,索引越多维护的代价就越大,且影响增删改的效率,占用磁盘空间。
  7. 如果是字符串类型索引,且字符串较长,尽量使用前缀索引。
  8. 如果所有列不能存储null值,创建表的时候使用not null约束,让查询优化器找到每列是否包含null值,从而更好的选择用哪个索引查询。
相关推荐
星辰离彬8 小时前
Java 与 MySQL 性能优化:Java应用中MySQL慢SQL诊断与优化实战
java·后端·sql·mysql·性能优化
程序猿小D9 小时前
[附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+jsp实现的个人财务管理系统,推荐!
java·数据库·mysql·spring·毕业论文·ssm框架·个人财务管理系统
发仔12314 小时前
Oracle与MySQL核心差异对比
mysql·oracle
叁沐17 小时前
MySQL 08 详解read view:事务到底是隔离的还是不隔离的?
mysql
wkj00117 小时前
navicate如何设置数据库引擎
数据库·mysql
ladymorgana17 小时前
【Spring Boot】HikariCP 连接池 YAML 配置详解
spring boot·后端·mysql·连接池·hikaricp
kk在加油20 小时前
Mysql锁机制与优化实践以及MVCC底层原理剖析
数据库·sql·mysql
合作小小程序员小小店20 小时前
web网页开发,在线%ctf管理%系统,基于html,css,webform,asp.net mvc, sqlserver, mysql
mysql·sqlserver·性能优化·asp.net·mvc
JosieBook20 小时前
【Java编程动手学】Java常用工具类
java·python·mysql
hello 早上好21 小时前
MsSql 其他(2)
数据库·mysql