乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

索引

导学

索引是数据库性能优化的核心。没有索引,查询百万级数据可能需要数秒;有了合适的索引,同样的查询只需毫秒。本节从原理到实践,彻底讲透索引。

定义

索引(Index):一种数据结构,存储了列值与行位置(或主键值)的映射关系。它让数据库可以快速定位数据,而无需扫描全表。

索引的本质:B+Tree

MySQL 5.7 的 InnoDB 存储引擎使用B+Tree作为索引数据结构。

B+Tree 的核心特征:

  • 所有数据(或数据指针)都存储在叶子节点
  • 叶子节点之间通过链表相连,支持高效的范围扫描
  • 树高通常为 3-4 层,百万级数据只需 3-4 次 I/O

索引类型

聚簇索引(Clustered Index)

  • 每个表只有一个聚簇索引,默认是主键索引
  • 叶子节点直接存储完整的行数据
  • 表数据本身就是按聚簇索引排序的

示例:

CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);

当前数据状态(逻辑存储顺序,按主键排序):

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

结果解读:虽然插入顺序是 1,2(自增),InnoDB 的聚簇索引会按 emp_id 物理排序存储。查询 WHERE emp_id = 1 时,直接通过 B+Tree 定位到叶子节点,读取完整行数据。

二级索引(Secondary Index)

  • 叶子节点存储索引列值 + 主键值
  • 查询时需要"回表":先查二级索引找到主键,再用主键查聚簇索引

当前数据状态:employees 表有 2 条数据。

操作语句:

-- 在 emp_name 列上创建二级索引
CREATE INDEX idx_emp_name ON employees(emp_name);

-- 执行一个需要回表的查询
SELECT score FROM employees WHERE emp_name = '大翔';

执行过程分析:

  1. 在 idx_emp_name 二级索引中查找 emp_name = '大翔',找到对应的主键值 1
  2. 用主键值 1 回到聚簇索引中查找完整行数据
  3. 从聚簇索引中读取 score = 100

结果:

score
100

结果解读:这就是"回表"过程。如果查询只查 emp_name 列,则不需要回表;但只要查了不在索引中的列(如 score),就必须回表。

SQL 示例

场景一:创建索引

当前数据状态:创建演示表

CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);

SELECT * FROM employees;
emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

操作语句:

-- 创建普通索引
CREATE INDEX idx_dept ON employees(dept);

-- 创建表时添加索引
CREATE TABLE scores (
    score_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_id INT,
    score DECIMAL(5,2),
    INDEX idx_emp_id (emp_id)
);

操作后数据状态:

SHOW INDEX FROM employees;
TableNon_uniqueKey_nameSeq_in_indexColumn_name
employees0PRIMARY1emp_id
employees1idx_dept1dept

结果解读:idx_dept 是普通索引,允许重复值。现在执行 SELECT * FROM employees WHERE dept = '技术部' 时,MySQL 会使用 idx_dept 索引快速定位到符合条件的行,而不是扫描全表。

场景二:查看索引

当前数据状态:employees 表已有 PRIMARY 和 idx_dept 两个索引。

操作语句:

SHOW INDEX FROM employees;

操作后数据状态:

TableNon_uniqueKey_nameSeq_in_indexColumn_nameCollationCardinality
employees0PRIMARY1emp_idA2
employees1idx_dept1deptA1

结果解读:

  • Non_unique=0 表示唯一索引(主键),Non_unique=1 表示普通索引
  • Cardinality 表示索引列的不重复值数量(预估值),Cardinality 越高,索引选择性越好
  • Collation=A 表示按升序排列

场景三:删除索引

当前数据状态:employees 表有 idx_dept 索引。

操作语句:

DROP INDEX idx_dept ON employees;

SHOW INDEX FROM employees;

操作后数据状态:只剩 PRIMARY 索引。

结果解读:删除索引后,WHERE dept = '技术部' 的查询将退化为全表扫描,但写入操作会变快。

场景四:覆盖索引

当前数据状态:重新创建索引

CREATE INDEX idx_dept ON employees(dept);

操作语句:

-- 创建复合索引 (dept, emp_name)
CREATE INDEX idx_dept_name ON employees(dept, emp_name);

-- 查询 dept 和 emp_name 两列
SELECT dept, emp_name FROM employees WHERE dept = '技术部';

操作后数据状态:

deptemp_name
技术部大翔
技术部白歌

结果解读:查询的列(dept, emp_name)都在 idx_dept_name 索引中,MySQL 直接从二级索引的叶子节点获取数据,无需回表查聚簇索引。这就是覆盖索引,是查询优化的重要手段。

对比需要回表的查询:

-- 需要回表,因为 score 不在 idx_dept_name 索引中
SELECT dept, emp_name, score FROM employees WHERE dept = '技术部';

执行过程:先查 idx_dept_name 找到主键,再用主键查聚簇索引获取 score。

场景五:索引加速 ORDER BY

当前数据状态:employees 表有 2 条数据。

无索引时的排序:

EXPLAIN SELECT * FROM employees ORDER BY score;
+----+-------------+-----------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+-----------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL |    2 | Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+------+----------------+

Using filesort 表示 MySQL 需要额外排序操作。

操作语句:

CREATE INDEX idx_score ON employees(score);

EXPLAIN SELECT * FROM employees ORDER BY score;

操作后数据状态:

+----+-------------+-----------+-------+---------------+----------+---------+------+------+-------+
| id | select_type | table     | type  | possible_keys | key      | key_len | ref  | rows | Extra |
+----+-------------+-----------+-------+---------------+----------+---------+------+------+-------+
|  1 | SIMPLE      | employees | index | NULL          | idx_score| 6       | NULL |    2 | NULL  |
+----+-------------+-----------+-------+---------------+----------+---------+------+------+-------+

结果解读:type=index 表示使用了索引扫描,Extra 中没有 Using filesort,说明 MySQL 直接利用索引的有序性返回结果,无需额外排序。

索引的代价

收益代价
加速查询占用磁盘空间
加速排序(ORDER BY)降低写入速度(维护索引)
加速分组(GROUP BY)增加优化器选择成本

写入代价示例:

-- 创建两张相同的表,一个有索引,一个无索引
CREATE TABLE no_index_tbl (id INT PRIMARY KEY, val INT);
CREATE TABLE with_index_tbl (id INT PRIMARY KEY, val INT, INDEX idx_val (val));

-- 插入 10000 条数据对比
-- 无索引表更快,因为不需要维护额外的 B+Tree

常见误区

误区正解
索引越多越好过多索引会严重拖慢写入(INSERT/UPDATE/DELETE),并占用大量空间。
所有列都建索引低基数列(如性别,只有两种值)建索引效果极差。
索引列用函数还能走索引WHERE YEAR(dt) = 2024 无法使用 dt 的索引,应改写为范围查询。

面试考点

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

聚簇索引叶子节点存完整行数据(InnoDB 主键索引);非聚簇索引(二级索引)叶子节点存索引列值+主键值,需要回表。

Q:为什么主键推荐用自增整数?

自增主键是顺序插入,B+Tree 叶子节点顺序填充,页分裂少;UUID 随机插入导致频繁页分裂和碎片。

Q:什么是回表?如何减少回表?

通过二级索引查到主键后,再用主键查聚簇索引获取完整数据,这个过程叫回表。减少回表的方法:使用覆盖索引(查询列全在索引中),或索引下推(Index Condition Pushdown)。

小结

  • 索引是 B+Tree 结构,让查询从 O(N) 降到 O(log N)
  • InnoDB 主键索引是聚簇索引,叶子节点存完整行数据
  • 二级索引需要回表,覆盖索引可避免回表
  • 索引有维护代价,不是越多越好

下一章引子:普通索引允许重复值,但有些场景需要确保唯一性——这就是唯一索引。

上一页
外键
下一页
唯一索引