MySQL索引总结(小白也能看懂)

索引划分

按照底层存储方式角度划分:

  • 聚簇索引:索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
  • 非聚簇索引(又称二级索引):索引结构和数据分开存放的索引,InnoDB中除主键索引外都是非聚簇索引。MySQLMyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。

按照应用维度划分:

类型 说明
主键索引(Primary Key 表的主键,唯一且非空,默认是聚簇索引
唯一索引(Unique Index 索引列的值必须唯一,但允许空值
普通索引(Index / Key 最基本的索引类型,没有唯一性约束
联合索引(Composite Index 多个列组成的索引
前缀索引(Prefix Index 针对字符串字段的部分前缀创建索引,节省空间
全文索引(Fulltext Index 针对文本搜索,适用于 TEXT 类型
空间索引(Spatial Index 用于地理空间数据(MyISAM支持,InnoDB部分支持)

主键索引(Primary Key)

数据表的主键列使用的就是主键索引。一张数据表有只能有一个主键,并且主键不能为 null,不能重复。

MySQLInnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引且不允许存在 null 值的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键。

二级索引

二级索引(Secondary Index)的叶子节点存储的数据是主键的值并不是数据行,也就是说,通过二级索引可以定位主键的位置

  1. 唯一索引:唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大都是为了该属性列的数据的唯一性,而不是为了查询效率。

    CREATE TABLE users (
      email VARCHAR(100) UNIQUE
    );
    CREATE UNIQUE INDEX idx_email ON users(email);
    
  2. 普通索引:普通索引的唯一作用就是为了快速查询数据**,一张表允许创建多个普通索引,并允许数据重复和 NULL。

    CREATE TABLE users (
      age INT,
      INDEX idx_age (age)
    );
    CREATE INDEX idx_age ON users(age);
    
  3. 联合索引:使用表中的多个字段创建索引,也叫 组合索引复合索引

    例如:以 scorename 两个字段建立联合索引:

    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);
    
  4. 前缀索引:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。

    CREATE TABLE files (
      filename VARCHAR(255),
      INDEX idx_filename_prefix (filename(20))
    );
    # 表示只对前 20 个字符建立索引。
    
  5. 全文索引:全文索引主要是为了检索大文本数据中的关键字的信息,支持分词匹配,不是简单的 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:"[email protected]" }
102 -> { id:102, username:"bob", email:"[email protected]" }

# 执行查询
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""[email protected]"-> 101"bob""[email protected]"-> 102

-- 此时这个查询也能被覆盖索引命中,直接返回"[email protected]",不回表
SELECT email FROM user WHERE username = 'alice';

如何避免索引失效

索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:

  • 创建了联合索引,但查询条件未遵守最左匹配原则;

    最左前缀匹配原则指的是在使用联合索引时,MySQL 会根据索引中的字段顺序,从左到右依次匹配查询条件中的字段。如果查询条件与索引中的最左侧字段相匹配,那么 MySQL 就会使用索引来过滤数据,这样可以提高查询效率。

    假设有一个联合索引(column1, column2, column3),其从左到右的所有前缀为(column1)(column1, column2)(column1, column2, column3)(创建 1 个联合索引相当于创建了 3 个索引),包含这些列的所有查询都会走索引而不会全表扫描。

    范围查询(例如 ><BETWEENLIKE '%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 > 2b的值有无限多种可能;所以不能跳过这些不定的 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';
    

适合与不适合创建索引的列

不适合创建索引的场景

  1. 低基数列(有很多重复值):如性别(只有男/女),索引效果差。
  2. 频繁更新的列:索引维护成本高,影响写入性能。
  3. 小表(数据量少):全表扫描可能更快。
  4. 不参与查询的列:无意义的索引会浪费资源。

适合创建索引的场景:

  1. 高基数列(如用户ID、订单号)
  2. 如状态(status)、时间(create_time)等经常用于查询条件的列
  3. 经常用作表连接的字段
  4. 经常出现在ORDER BY/GROUP BY/DISDINCT后面的字段

你可能感兴趣的:(mysql,数据库,java,sql)