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 = 1WHERE a = 1 AND b = 2WHERE a = 1 AND b = 2 AND c = 3WHERE a = 1 AND c = 3(部分使用,只用a列)
❌ 不能使用索引的情况:
WHERE b = 2WHERE c = 3WHERE b = 2 AND c = 3
3. 特殊匹配情况
a > 1a = 1 AND b > 2a = 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字符)
 - 长字段不适合作为联合索引的前导列
 
 - 更新代价:
- 联合索引的更新代价高于单列索引
 - 频繁更新的列不宜放在索引中
 
 
通过合理设计联合索引并遵循最左前缀原则,可以显著提高查询性能,减少全表扫描。


