MySQL7️⃣索引(❗)

索引(index):帮助 MySQL 高效获取数据的有序数据结构。

数据库系统既维护数据,还维护索引(以某种方式引用数据)。

  • 优点
    1. 提高查询效率,降低数据库 I/O 成本
    2. 通过有序索引列对数据排序,降低排序成本 和 CPU 消耗
  • 缺点
    1. 索引也占用内存
    2. 执行 DML 时需要更新索引,导致表的更新效率降低

1、数据结构

MySQL 索引在存储引擎层实现,

不同存储引擎的索引结构可能有所不同。

主要的索引结构

说明 InnoDB MyISAM Memory
B+ tree 🔥 若非特别指明,索引通常是指 B+ tree
Hash 仅支持精确匹配,不支持范围查询 - -
R tree 通常用于地理空间数据类型 - -
Full text 通过建立倒排索引,快速匹配文档 5.6+ ✔ -

1.1、B+ tree(❗)

InnoDB 使用优化的 B+ tree 索引结构。

在此之前,先了解二叉树,B tree、标准 B+ tree。

1.1.1、二叉树

每个结点存放 key(包括数据)和指针

缺点:以二叉搜索树(BST)为例

  1. 线性情况

    • 在极端情况下,添加的元素都比根节点大或者小,导致一侧子树线性增长
    • 此时的结构无异于链表,时间复杂度是 O(n)
  2. 层级多

    • 在大数据量的场景下,由于二叉树的度是 2(每个结点最多存储 2 个孩子)。

    • 导致树的层级多,检索速度慢

      image-20220313182256080

如果使用红黑树呢?

  1. 线性情况:可以避免(旋转达到平衡)
  2. 层级多:无法避免(红黑树也是二叉树,度 == 2)

1.1.2、B tree

多路平衡查找树算法可视化

每个结点存放 key(包括数据)和指针

特点

  1. 最大度数为 k 的 B tree,每个结点最多存储 k - 1 个 key 和 k 个指针。

  2. 当结点存储的 key 达到度数,中间元素向上分裂

    image-20220313182924686

1.1.3、B+ tree

B tree 的变体

  • 非叶结点:仅存放 key(不包括数据) 和指针
  • 叶结点:存储 key(包括数据),叶子结点形成单向链表。

特点

  1. 当结点存储的 Key 达到度数时,中间元素保留且向上分裂

  2. 相比标准的 B tree,可以存储的数据更多。

    • 非叶结点仅起到索引作用。

    • 叶节点才存储数据。

      image-20220313203627149

1.1.4、优化 B+ tree

B+ tree 的变体

特点:相比标准 B+ tree,叶子结点形成双向循环链表

image-20220313205325659

1.2、Hash

参考 👉 哈希及 Java 实现

特点

  1. 支持等值查询( =,in),不支持范围查询(between,>)

  2. 不支持排序

  3. 查询效率高:若不存在哈希冲突,通常只需一次检索。

    image-20220313204626865

1.3、对比

为什么 InnoDB 存储引擎使用 B+ tree 索引结构

思路:介绍二叉树、B 树、Hash 索引,对比并说明其缺点。

二叉树 B tree B+ tree Hash
存储机制 结点存放数据和指针 结点存放数据和指针 非叶节点存指针,叶结点存数据 Hash 表
缺点 可能出现线性情况;
数据量大时层级多。
相比 B+ 树,每页能存储的结点数减少 - 不支持范围查询和排序

2、分类(❗)

2.1、MySQL 索引类型

含义 特点 关键字
主键索引 针对主键创建 自动创建,唯一 PRIMARY
唯一索引 避免某数据列中的值重复 可有多个 UNIQUE
常规索引 快速定位特定数据 可有多个
全文索引 查找文本关键词,而不是比较索引值 可有多个 FULLTEXT

2.2、InnoDB 索引形式

2.2.1、分类

在 InnoDB 中,根据索引的存储形式分为两类。

聚集索引(Clustered) 二级索引(Secondary)
结构 数据与索引一起存储 数据与索引分开存储
叶节点特点 保存数据 关联主键
数量 有且唯一 0 或多

2.2.2、聚集索引的选取

根据数据库表的情况,决定聚集索引

  1. 存在主键:使用主键索引
  2. 不存在主键:使用首个唯一索引
  3. 不存在主键和唯一索引:自动生成隐藏的 rowid 作为聚集索引。

case:数据查找过程

Hint:查询条件不是主键,因此先查二级索引,再查聚集索引

SELECT * FROM user WHERE name = 'Arm';
  1. 先根据 name 字段的二级索引,查找到关联主键

  2. 回表查询:根据二级索引查到的主键,到聚集索引中查找对应的行数据。

    image-20220314003005448

3、语法(❗)

3.1、索引

3.1.1、创建

通常命名为 idx_表名_列名

  • 类型

    1. 常规索引:无需指定
    2. 唯一索引:UNIQUE
    3. 全文索引:FULLTEXT
  • 列数

    1. 单列索引
    2. 联合索引:为多个列创建的索引。
  • 前缀索引:为字符串的部分前缀建立的索引。

    # 索引
    CREATE [UNIQUE|FULLTEXT] INDEX 索引名
    ON 表名 (列名, ... ) ;
    
    # 前缀索引
    CREATE [UNIQUE|FULLTEXT] INDEX 索引名
    ON 表名 (列名(前缀长度), ... ) ;
    

前缀索引

  • 分析:针对字符串类型的字段(如 varchar, text)
    • 建立常规索引时会占用很大内存。
    • 浪费大量磁盘 I/O,影响查询效率。
  • 对策(前缀索引):为字符串的部分前缀建立索引,前缀长度由索引的选择性决定。
    • 选择性:表中不重复的索引值与记录总数的比值。
    • 索引查询效率:与选择性成正比(唯一索引的选择性为 1,查询效率最高)。

3.1.2、查看 & 删除

  1. 查看索引

    SHOW INDEX FROM 表名;
    
  2. 删除索引

    DROP INDEX 索引名
    ON 表名;
    

case:建立索引

根据不同情况,为 t_user 表创建索引。

  1. name 姓名字段,可能重复。 👉 常规

    CREATE INDEX idx_user_name
    ON t_user(name);
    
  2. phone 手机号字段,非空且唯一。👉 唯一

    CREATE UNIQUE INDEX idx_user_phone
    ON t_user(phone);
    
  3. 为 profession、age、status 字段创建索引。👉 联合

    CREATE INDEX idx_user_pro_age_sta
    ON t_user(profession, age, status);
    
  4. 为 email 创建索引。👉 前缀

    # 计算前缀长度
    SELECT COUNT(DISTINCT email) / COUNT(*)
    FROM tb_user;
    
    CREATE INDEX idx_user_email
    ON t_user(email(前缀长度));
    

3.2、SQL prompt

SQL 提示:在 DQL 语句中加入提示信息,提示 MySQL 做出相应动作。

  1. USE INDEX:建议 MySQL 使用指定索引。
  2. FORCE IDNEX:强制 MySQL 使用指定索引。
  3. IGNORE INDEX:忽略指定索引。

示例

# 建议
EXPLAIN SELECT * FROM tb_user
USE INDEX(idx_user_pro)
WHERE profession = '软件工程';

# 强制
EXPLAIN SELECT * FROM tb_user
FORCE INDEX(idx_user_pro)
WHERE profession = '软件工程';

# 忽略
EXPLAIN SELECT * FROM tb_user
IGNORE INDEX(idx_user_pro)
WHERE profession = '软件工程';

4、索引使用(❗)

借助 SQL 性能分析,对比有无索引的执行效率。

4.1、最左前缀法则

说明

如果想要使用联合索引,需要遵守最左前缀法则。

(最左打头,中间不断)

  1. 最左前缀法则:针对查询条件,必须根据联合索引的最左侧开始包含字段。
  2. 部分失效:跳过某一列,则只能使用该列之前的索引,之后的索引失效。

case

联合索引:字段列表依次为 profession, age, status

CREATE INDEX idx_prof_age_status
ON tb_user(profession, age, status);
  1. 正常使用

    EXPLAIN SELECT * FROM tb_user 
    WHERE profession = '计算机科学';
    
    EXPLAIN SELECT * FROM tb_user 
    WHERE profession = '计算机科学' AND age = 17;
    
    EXPLAIN SELECT * FROM tb_user 
    WHERE profession = '计算机科学' AND age = 17 AND status = '0';
    
  2. 失效情况

    1. 完全失效:跳过了最左侧的字段。

      EXPLAIN SELECT * FROM tb_user 
      WHERE age = 17 AND status = '0';
      
    2. 部分失效:跳过了中间的字段 age,只有最左侧字段使用到索引。

      EXPLAIN SELECT * FROM tb_user 
      WHERE profession = '计算机科学' AND status = '0';
      

4.2、覆盖索引

说明

假如 DQL 查询使用到了索引,且该索引包含了所有要查询的列

则无需进行回表查询。

  1. 通常是联合索引能达到覆盖索引的效果。
  2. 尽量使用覆盖索引,避免使用 SELECT * 查询所有记录。

case

Hint:假设 name 有常规索引 idx_user_name(二级索引)

SELECT 字段列表,决定了该索引是否为覆盖索引。

回表查询 覆盖查询
SQL SELECT *
FROM tb_user
WHERE name='Arm';
SELECT id, name
FROM tb_user
WHERE name='Arm';
WHERE 字段列 条件为 name,查二级索引找到关联主键 同左
说明 二级索引只包含了 id 和 name 同左
覆盖索引? ❌ 除了 id 和 name,SELECT 还需要查询其它字段,需回表查询 ✔ SELECT 查询列表恰好是 id 和 name,无需回表查询

使用 EXPALIN 解释 DQL 执行,观察 extra 列:

  • using index condition:说明使用索引 + 回表查询。
  • using where; using index:说明使用索引 + 覆盖索引。

4.3、八大失效情况

  1. 索引本身失效

  2. 最左前缀法则

  3. 范围查询联合索引中某个列使用范围查询( ><),该列右侧的列索引失效。

    • 解决方案:在业务允许的情况下,改用 >=<=

    • 示例:age 右侧的 status 列索引失效。

      # 联合索引
      CREATE INDEX idx_prof_age_status
      ON tb_user(profession, age, status);
      
      # 范围查询:age
      SELECT * FROM tb_user
      WHERE profession = '计算机科学'
      	AND age < 20
      	AND status='0';
      
  4. 索引列运算:在索引列上进行运算,如函数运算。

    SELECT * FROM tb_user
    WHERE age + 3 = 18;
    
    # 优化:在程序中或等号右侧,提前计算数值
    SELECT * FROM tb_user
    WHERE age = 18 - 3;
    
  5. 索引列函数:在索引列上调用了 MySQL 函数。

    # 索引
    CREATE INDEX idx_phone ON tb_user(phone);
    
    # 导致idx_phone失效
    SELECT * FROM tb_user
    WHERE SUBSTRING(phone,8,4) = '6333';
    
  6. 字符串不加引号:数据库会隐式类型转换(数字 → 字符串),但索引失效。

    # 正常
    SELECT * FROM tb_user
    WHERE phone = '15882288333';
    # 索引失效
    SELECT * FROM tb_user
    WHERE phone = 15882288333;
    
  7. 头部模糊查询:即 LIKE %内容

    1. 原因:B+tree 索引是按顺序排列的,如果头部模糊查询,则无法根据确定的字母是去寻找索引中的特定结点,因此会进行全文扫描。

      # 正常
      SELECT * FROM tb_user WHERE profession LIKE '计算%';
      SELECT * FROM tb_user WHERE profession LIKE '计%程';
      
      # 失效:头部模糊匹配
      SELECT * FROM tb_user WHERE profession LIKE '%工程';
      SELECT * FROM tb_user WHERE profession LIKE '%工%';
      
    2. 解决方案建立倒序索引,翻转查询条件

      CREATE INDEX idx_user_profession_reverse
      ON tb_user (REVERSE(profession));
      
      SELECT * FROM tb_user
      WHERE REVERSE(profession) LIKE REVERSE('%工程');
      
  8. OR 连接条件:OR 连接的条件,左右两侧的字段都有索引才生效。

  9. 数据分布影响:当查询记录数是表的大部分数据,MySQL 评估使用全表查询效率更高,索引失效。

4.4、五大设计原则

  1. 何时建立索引
    • 数据量大,且查询频繁的表。
    • 常作为查询条件(WHERE)、排序(ORDER BY)、分组(GROUP BY)操作的字段。
    • 尽量选择区分度高的列建立索引。
    • 尽量建立唯一索引。
  2. 前缀索引:字符串类型的字段,且字段内容较长。
  3. 联合索引:尽量减少使用单列索引,联合索引往往可以覆盖索引,节省存储空间,避免回表查询,提高查询效率。
  4. 索引数量:要控制索引的数量。索引越多,维护开销越大,影响增删改的效率。
  5. NULL 值:如果索引列不能存储 NULL 值,需使用 NOT NULL 约束。
posted @ 2022-06-13 12:21  Jaywee  阅读(213)  评论(0编辑  收藏  举报

👇