索引划分
按照底层存储方式角度划分:
- 聚簇索引:索引结构和数据一起存放的索引,
InnoDB
中的主键索引就属于聚簇索引。 - 非聚簇索引(又称二级索引):索引结构和数据分开存放的索引,
InnoDB
中除主键索引外都是非聚簇索引。MySQL
的MyISAM
引擎,不管主键还是非主键,使用的都是非聚簇索引。
按照应用维度划分:
类型 | 说明 |
---|---|
主键索引(Primary Key ) | 表的主键,唯一且非空,默认是聚簇索引 |
唯一索引(Unique Index ) | 索引列的值必须唯一,但允许空值 |
普通索引(Index / Key ) | 最基本的索引类型,没有唯一性约束 |
联合索引(Composite Index ) | 多个列组成的索引 |
前缀索引(Prefix Index ) | 针对字符串字段的部分前缀创建索引,节省空间 |
全文索引(Fulltext Index ) | 针对文本搜索,适用于 TEXT 类型 |
空间索引(Spatial Index ) | 用于地理空间数据(MyISAM支持,InnoDB部分支持) |
主键索引(Primary Key
)
数据表的主键列使用的就是主键索引。一张数据表有只能有一个主键,并且主键不能为 null,不能重复。
在 MySQL
的 InnoDB
的表中,当没有显示的指定表的主键时,InnoDB
会自动先检查表中是否有唯一索引且不允许存在 null 值的字段,如果有,则选择该字段为默认的主键,否则 InnoDB
将会自动创建一个 6Byte
的自增主键。
二级索引
二级索引(Secondary Index
)的叶子节点存储的数据是主键的值并不是数据行,也就是说,通过二级索引可以定位主键的位置
-
唯一索引:唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大都是为了该属性列的数据的唯一性,而不是为了查询效率。
CREATE TABLE users ( email VARCHAR(100) UNIQUE ); CREATE UNIQUE INDEX idx_email ON users(email);
-
普通索引:普通索引的唯一作用就是为了快速查询数据**,一张表允许创建多个普通索引,并允许数据重复和 NULL。
CREATE TABLE users ( age INT, INDEX idx_age (age) ); CREATE INDEX idx_age ON users(age);
-
联合索引:使用表中的多个字段创建索引,也叫 组合索引 或 复合索引。
例如:以
score
和name
两个字段建立联合索引:CREATE TABLE orders ( user_id INT, product_id INT, INDEX idx_user_product (user_id, product_id) ); ALTER TABLE `orders` ADD INDEX iidx_user_product(user_id, product_id);
-
前缀索引:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
CREATE TABLE files ( filename VARCHAR(255), INDEX idx_filename_prefix (filename(20)) ); # 表示只对前 20 个字符建立索引。
-
全文索引:全文索引主要是为了检索大文本数据中的关键字的信息,支持分词匹配,不是简单的
LIKE %xxx%
;适用于TEXT
,VARCHAR
字段CREATE TABLE articles ( content TEXT, FULLTEXT INDEX idx_content (content) ); # 使用时 SELECT * FROM articles WHERE MATCH(content) AGAINST('mysql');
聚簇索引与非聚簇索引的区别
聚簇索引中叶子结点存放数据行本身、而非聚簇索引中叶子结点存放的是主键值,前者的查询速度要更快,因为后者查询可能需要回表(不需要回表就涉及后面的覆盖索引了);举个例子:
# 创建一个表
CREATE TABLE user (
id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
INDEX idx_username(username) -- 非聚簇索引
) ENGINE=InnoDB;
# 索引结构
idx_username B+Tree(非聚簇):
"alice" -> 101
"bob" -> 102
聚簇索引 B+Tree(主键):
101 -> { id:101, username:"alice", email:"a@example.com" }
102 -> { id:102, username:"bob", email:"b@example.com" }
# 执行查询
SELECT email FROM user WHERE username = 'alice';
# 查询流程:
username = 'alice' → 找到主键 101 → 回主键索引 → 取出 email
# 找到101之后去主键索引中查找对应数据行的过程就是回表
覆盖索引
如果一个索引包含所有需要查询的字段的值,我们就称之为 覆盖索引(Covering Index
) ,还是上面这个例子:
-- 我加上 email 字段的联合索引
CREATE INDEX idx_username_email ON user(username, email);
idx_username_email B+Tree(非聚簇):
("alice","a@example.com") -> 101
("bob","b@example.com") -> 102
-- 此时这个查询也能被覆盖索引命中,直接返回"a@example.com",不回表
SELECT email FROM user WHERE username = 'alice';
如何避免索引失效
索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:
-
创建了联合索引,但查询条件未遵守最左匹配原则;
最左前缀匹配原则指的是在使用联合索引时,
MySQL
会根据索引中的字段顺序,从左到右依次匹配查询条件中的字段。如果查询条件与索引中的最左侧字段相匹配,那么MySQL
就会使用索引来过滤数据,这样可以提高查询效率。假设有一个联合索引
(column1, column2, column3)
,其从左到右的所有前缀为(column1)
、(column1, column2)
、(column1, column2, column3)
(创建 1 个联合索引相当于创建了 3 个索引),包含这些列的所有查询都会走索引而不会全表扫描。范围查询(例如
>
、<
、BETWEEN
、LIKE '%xx'
)会中断匹配链,例如:CREATE INDEX idx_abc ON my_table(a, b, c); SELECT * FROM my_table WHERE a = 1 AND b = 2 AND c = 3;-- ✅ a 、b、c都命中 SELECT * FROM my_table WHERE a = 1 AND b > 2 AND c = 3;-- ✅ 只命中 a 和 b,c无效
在联合索引
(a,b,c)
的B+树中,叶子节点中的排列顺序是先按a
排序,再按b
排序,再按c
排序。
b > 2
,b
的值有无限多种可能;所以不能跳过这些不定的b
去精确定位c=3
的位置,只能全量扫描所有符合a=1 且 b>2
的记录,再筛c=3
; -
在索引列上进行计算、函数、类型转换等操作;
-
以 % 开头的 LIKE 查询比如
LIKE '%abc';
但是这种LIKE 'abc%'
是不会导致索引失效的
索引的排列顺序是根据比较字符串的首字母排序的,如果首字母相同,就根据比较第二个字母进行排序,以此类推。我们在进行模糊查询的时候,如果把 % 放在了前面,最左的 n 个字母便是模糊不定的,无法根据索引的有序性准确的定位,只能进行全表扫描,找出符合条件的数据。
-
查询条件中使用
OR
,且OR
的前后条件中有一个列没有索引,涉及的索引都不会被使用到; -
IN
的取值范围较大时会导致索引失效,走全表扫描(NOT IN 和 IN
的失效场景相同); -
发生隐式转换时(当操作符左右两边的数据类型不一致时,会发生隐式转换)
-- 假设 create_time 是 DATETIME 类型,有索引 SELECT * FROM orders WHERE create_time = '20250422'; -- '20250422'是一个字符串;MySQL 会尝试把它转成 DATETIME,实际变成 '2025-04-22 00:00:00';
适合与不适合创建索引的列
不适合创建索引的场景
- 低基数列(有很多重复值):如性别(只有男/女),索引效果差。
- 频繁更新的列:索引维护成本高,影响写入性能。
- 小表(数据量少):全表扫描可能更快。
- 不参与查询的列:无意义的索引会浪费资源。
适合创建索引的场景:
- 高基数列(如用户ID、订单号)
- 如状态(
status
)、时间(create_time
)等经常用于查询条件的列。 - 经常用作表连接的字段
- 经常出现在
ORDER BY/GROUP BY/DISDINCT
后面的字段