Mysql 索引知识点大全【聚簇索引、二级索引、回表查询、索引下推、覆盖索引、联合索引】
MySQL 聚簇索引、二级索引、回表查询与索引下推详解
1. 聚簇索引 (Clustered Index)
基本概念
- 定义:索引的叶子节点存储的是完整的数据行(数据与索引存储在一起)
- 特性:
- 每个InnoDB表有且只有一个聚簇索引
- 物理存储顺序与索引顺序一致
- 主键自动成为聚簇索引
创建规则
- 如果表定义了PRIMARY KEY,则使用主键作为聚簇索引
- 如果没有主键,选择第一个UNIQUE NOT NULL的列作为聚簇索引
- 如果以上都没有,InnoDB会创建一个隐藏的rowid作为聚簇索引
优势
- 快速访问:通过索引可直接获取数据,无需二次查找
- 范围查询高效:相邻数据物理存储在一起
- 覆盖索引查询高效
2. 二级索引 (Secondary Index/Non-clustered Index)
基本概念
- 定义:索引的叶子节点存储的是主键值(非聚簇索引)
- 特性:
- 每个表可以有多个二级索引
- 叶子节点不包含完整数据行
- 需要回表操作获取完整数据
结构示例
二级索引结构:索引列值 -> 主键值
常见类型
- 普通索引
- 唯一索引
- 复合索引
- 全文索引
3. 回表查询 (Bookmark Lookup)
概念
- 当查询的列不全部包含在二级索引中时,需要通过二级索引找到主键值,再通过主键值到聚簇索引中查找完整记录的过程
执行流程
- 在二级索引中查找符合条件的记录,获取主键值
- 使用主键值到聚簇索引中查找完整数据行
- 返回查询结果
示例
-- 表结构CREATE TABLE user ( id INT PRIMARY KEY, name VARCHAR(20), age INT, INDEX idx_age (age));-- 回表查询示例SELECT * FROM user WHERE age = 25;-- 1. 使用idx_age索引找到age=25的记录,获取主键id-- 2. 使用主键id到聚簇索引中查找完整记录
性能影响
- 需要两次索引查找,性能较低
- 可通过覆盖索引优化(查询列都包含在索引中)
4. 索引下推 (Index Condition Pushdown, ICP)
基本概念
- **MySQL 5.6+**引入的优化技术
- 原理:将WHERE条件中索引列的过滤条件下推到存储引擎层执行
- 目的:减少回表操作次数,提高查询性能
工作流程(有ICP vs 无ICP)
无ICP时:
- 存储引擎根据索引查找记录
- 返回所有匹配索引条件的记录给Server层
- Server层根据其他条件过滤
有ICP时:
- 存储引擎根据索引查找记录
- 在存储引擎层就应用WHERE条件中的索引列过滤
- 只返回满足所有条件的记录给Server层
示例
-- 表结构CREATE TABLE employee ( id INT PRIMARY KEY, name VARCHAR(50), dept_id INT, salary INT, INDEX idx_dept_salary (dept_id, salary));-- 查询SELECT * FROM employee WHERE dept_id = 3 AND salary > 5000;-- 无ICP:先找到所有dept_id=3的记录,再在Server层过滤salary>5000-- 有ICP:在存储引擎层就过滤dept_id=3 AND salary>5000,减少回表次数
使用条件
- 只能用于二级索引(非聚簇索引)
- 适用于range、ref、eq_ref和ref_or_null访问方法
- WHERE条件中的列必须都是索引列
- 对于InnoDB表,ICP只适用于二级索引
索引下推的使用条件
ICP目标是减少全行记录读取,从而减少IO 操作,只能用于非聚簇索引。聚簇索引本身包含的表数据,也就不存在下推一说。
只能用于range、 ref、 eq_ref、ref_or_null访问方法;
where 条件中是用 and 而非 or 的时候。
ICP适用于分区表。
ICP不支持基于虚拟列上建立的索引,比如说函数索引
ICP不支持引用子查询作为条件。
ICP不支持存储函数作为条件,因为存储引擎无法调用存储函数。
查看ICP状态
-- 查看ICP是否开启SHOW VARIABLES LIKE \'optimizer_switch\';-- 输出中包含index_condition_pushdown=on-- 开启/关闭ICPSET optimizer_switch = \'index_condition_pushdown=on|off\';
5. 性能优化建议
- 合理设计索引:
- 高频查询条件列建立索引
- 考虑复合索引的顺序
- 减少回表:
- 使用覆盖索引(查询列都包含在索引中)
- 避免SELECT *,只查询需要的列
- 利用索引下推:
- 确保WHERE条件中的索引列能够被下推
- 复合索引中,将过滤性高的列放在前面
- 监控索引使用:
EXsql SELECT ...; -- 查看执行计划SHOW INDEX FROM table_name; -- 查看表索引
MySQL 覆盖索引详解
1. 覆盖索引基本概念
覆盖索引(Covering Index)是指一个查询的所有字段都包含在索引中,查询只需要通过索引就能获取所需数据,而无需回表查询数据行。
核心特点:
- 查询的列都包含在索引中
- 不需要访问数据行(避免回表操作)
- 显著提高查询性能
2. 覆盖索引工作原理
普通索引查询流程:
- 通过索引树查找符合条件的索引记录
- 获取主键值
- 通过主键值回表查询完整数据行(回表操作)
- 返回结果
覆盖索引查询流程:
- 通过索引树查找符合条件的索引记录
- 直接从索引中获取所需列的值(无需回表)
- 返回结果
3. 覆盖索引的优势
- 减少I/O操作:避免访问数据行,减少磁盘I/O
- 提高查询速度:省去了回表操作的时间
- 减轻服务器负担:减少CPU和内存消耗
- 特别适合统计查询:如COUNT()、SUM()等聚合操作
4. 如何创建和使用覆盖索引
示例表结构:
CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50), age INT, city VARCHAR(50), INDEX idx_username_age (username, age));
覆盖索引查询示例:
-- 覆盖索引查询(所有查询列都在索引中)SELECT username, age FROM users WHERE username = \'john\';-- 非覆盖索引查询(需要回表获取city字段)SELECT username, age, city FROM users WHERE username = \'john\';
创建覆盖索引:
-- 为常用查询创建覆盖索引ALTER TABLE users ADD INDEX idx_cover_name_age_city (username, age, city);
5. 判断查询是否使用覆盖索引
使用EXsql
命令查看执行计划,当Extra
列显示Using index
时,表示使用了覆盖索引:
EXsql SELECT username, age FROM users WHERE username = \'john\';
输出示例:
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------------+| 1 | SIMPLE | users | NULL | ref | idx_username_age | idx_username_age | 203 | const | 1 | 100.00 | Using index |+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------------+
6. 覆盖索引的最佳实践
- 选择合适的列顺序:
- 将高频查询条件列放在索引前面
- 将需要返回的列放在索引后面
- 避免过度索引:
- 只为真正需要的查询创建覆盖索引
- 过多的索引会影响写入性能
- 考虑索引大小:
- 大字段(如TEXT)不适合包含在覆盖索引中
- 索引列总长度不宜过长
- 复合索引设计:
-- 好的设计:查询条件和返回列都包含在索引中CREATE INDEX idx_cover ON orders (customer_id, status, order_date, amount);-- 查询示例(使用覆盖索引)SELECT status, order_date, amount FROM orders WHERE customer_id = 100 AND status = \'completed\';
7. 覆盖索引的局限性
- 写入开销:维护更多列的索引会增加INSERT/UPDATE/DELETE的开销
- 存储空间:覆盖索引通常比普通索引占用更多空间
- 不适合所有查询:仅对特定查询模式有效
- 无法覆盖所有场景:当查询需要大量列时,创建覆盖索引可能不实际
8. 实际应用案例
案例1:用户信息查询
-- 常用查询:根据用户名获取用户年龄和城市SELECT age, city FROM users WHERE username = ?;-- 最佳索引设计CREATE INDEX idx_user_cover ON users (username, age, city);
案例2:订单统计
-- 常用查询:统计某客户已完成订单的总金额SELECT SUM(amount) FROM orders WHERE customer_id = ? AND status = \'completed\';-- 最佳索引设计CREATE INDEX idx_order_cover ON orders (customer_id, status, amount);
通过合理设计覆盖索引,可以显著提高这些常见查询的性能。
MySQL联合索引底层结构与最左前缀匹配原理
一、联合索引的底层结构
1. B+树结构
联合索引在InnoDB引擎中采用B+树结构存储,与单列索引结构类似,但有重要区别:
- 索引键:由多个列的值按定义顺序拼接组成复合键
- 排序规则:先按第一列排序,第一列相同按第二列排序,以此类推
- 叶子节点:存储完整的索引键值+主键值(非聚簇索引)
2. 物理存储示例
对于索引INDEX idx_a_b_c (a, b, c)
,索引键的存储形式为:
(a1,b1,c1) -> 主键1(a1,b1,c2) -> 主键2(a1,b2,c1) -> 主键3(a2,b1,c1) -> 主键4...
3. 与单列索引的区别
二、最左前缀匹配原则
1. 基本概念
最左前缀匹配原则指联合索引按照从左到右的顺序使用,查询条件必须包含索引的第一列(最左列)才能使用索引。
2. 匹配规则详解
对于联合索引(a,b,c)
:
✅ 能使用索引的情况:
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
WHERE a = 1 AND c = 3
(部分使用,只用a列)
❌ 不能使用索引的情况:
WHERE b = 2
WHERE c = 3
WHERE b = 2 AND c = 3
3. 特殊匹配情况
a > 1
a = 1 AND b > 2
a = 1 AND b LIKE \'abc%\'
a = 1 AND b LIKE \'%abc\'
a = 1 ORDER BY b
三、联合索引的实际应用
1. 索引设计建议
- 高频查询优先:将最常用的查询条件放在最左边
- 高区分度优先:选择性高的列(唯一值多)放在前面
- 考虑排序需求:ORDER BY/GROUP BY的列可加入索引
- 避免过度索引:通常3-4列的联合索引足够
2. 性能优化示例
-- 表结构CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, create_time DATETIME, status TINYINT, amount DECIMAL(10,2), INDEX idx_user_status_time (user_id, status, create_time));-- ✅ 高效查询(完全匹配)SELECT * FROM orders WHERE user_id = 1001 AND status = 1 ORDER BY create_time DESC;-- ✅ 高效查询(部分匹配)SELECT * FROM orders WHERE user_id = 1001 ORDER BY create_time DESC;-- ❌ 低效查询(违反最左前缀)SELECT * FROM orders WHERE status = 1 AND create_time > \'2023-01-01\';
3. 使用EXsql验证
EXsql SELECT * FROM orders WHERE user_id = 1001 AND status = 1;
观察key
列显示idx_user_status_time
且type
为ref
表示索引有效使用
四、常见误区与注意事项
- 列顺序误区:
- 错误认知:
INDEX(a,b)
和INDEX(b,a)
是等价的 - 实际情况:两者是完全不同的索引,适用不同查询
- 错误认知:
- 索引合并误区:
- MySQL可能使用
index_merge
策略合并多个单列索引 - 但通常不如设计良好的联合索引高效
- MySQL可能使用
- 索引长度限制:
- InnoDB索引最大长度767字节(utf8mb4下约191字符)
- 长字段不适合作为联合索引的前导列
- 更新代价:
- 联合索引的更新代价高于单列索引
- 频繁更新的列不宜放在索引中
通过合理设计联合索引并遵循最左前缀原则,可以显著提高查询性能,减少全表扫描。