当前位置: 首页 > news >正文

MySQL 索引:原理、分类与操作指南

MySQL-索引

1. 什么是索引?

1.1 概念

一个非常贴切的比喻是:索引就像一本书的目录

  • 没有索引(目录):当你想在书中查找某个特定内容时,你只能从第一页开始,一页一页地往后翻,直到找到为止。这在数据库中被称为全表扫描(Full Table Scan),效率非常低。

  • 有索引(目录):你可以通过目录快速定位到内容所在的章节和页码,然后直接翻到那一页。索引就是帮助数据库引擎快速找到数据的一种数据结构

官方定义:索引是帮助 MySQL 高效获取数据排好序数据结构

这个定义包含了三个关键点:

  1. 高效获取数据:目的就是为了加快查询速度。

  2. 排好序:索引中的数据是按照某种规则排序的,这是实现快速查找(如二分查找、范围检索)的基础。

  3. 数据结构:它本质上是一种数据结构,MySQL 中最常用的是 B+Tree。

1.2 索引的优缺点

1.2.1 优点
  • 大大加快了数据的检索速度(核心优势)。
1.2.2 缺点
  • 需要占用额外的磁盘空间

  • 会降低数据写入(增、删、改)的速度。因为当数据发生变化时,索引也需要同时进行维护和更新。

2. 索引的分类

2.1 从物理存储上划分

  1. 聚集索引

    • 定义:表数据行的物理存储顺序与索引键值的逻辑顺序相同。一个表 只能有一个 聚集索引。

    • InnoDB 的实现InnoDB 的表必然有一个聚集索引

      • 如果定义了主键(PRIMARY KEY),那么主键就是聚集索引。

      • 如果没有主键,则选择第一个唯一的非空索引(UNIQUE NOT NULL)作为聚集索引。

      • 如果都没有,InnoDB 会隐式创建一个隐藏的 ROWID 作为聚集索引。

    • 叶子节点:存储的是 完整的数据行(通常是按照 id 值顺序存储的)

  2. 非聚集索引(二级索引/辅助索引)

    • 定义:索引的逻辑顺序与数据行的物理存储顺序无关。一个表可以有多个非聚集索引

    • 叶子节点:存储的不是数据行本身,而是其 非聚簇索引存储的是索引列的字段值 + 对应数据行的主键值

    • 回表查询:当使用非聚集索引进行查询时,首先在非聚集索引的 B+Tree 中找到对应的主键值,然后再用这个主键值到聚集索引的 B+Tree 中查找完整的行数据。这个额外查找聚集索引的过程就叫做 回表

示例:
user 表:id(主键), nameage

  • 在 id 上建立了聚集索引。

  • 在 age 上建立了非聚集索引。

  • 执行 SELECT * FROM user WHERE age = 30;

    1. 在 age 索引的 B+Tree 中找到 age=30 的节点,获取对应的主键 id(例如 id=10)。

    2. 用 id=10 到 id 主键索引的 B+Tree 中查找,获取完整的行数据(被称为回表查询)。

2.2 从逻辑和功能上划分

  1. 主键索引(PRIMARY KEY)

    • 特殊的唯一索引,不允许为空。一张表只能有一个。
  2. 唯一索引(UNIQUE KEY)

    • 索引列的值必须唯一,但允许有空值。
  3. 普通索引(INDEX/KEY)

    • 最基本的索引,没有任何限制,仅用于加速查询。
  4. 复合索引

    • 由多个列组合而成的索引。

    • CREATE INDEX idx_name_age ON user(name, age);

    • 最左前缀原则:这是复合索引最重要的特性。查询时,必须从索引的最左列开始,并且不能跳过中间的列。

      • WHERE name = ‘张三’ (使用索引)

      • WHERE name = ‘张三’ AND age = 20 (使用索引)

      • WHERE age = 20 (不使用索引,因为跳过了 name

      • WHERE name LIKE ‘张%’ (使用索引)

  5. 全文索引(FULLTEXT)

    • 用于全文搜索,主要用于查找文本中的关键字,而不是直接比较索引中的值。适用于 MATCH AGAINST 操作。

3. 建立共识

  1. 磁盘中扇区通常是 512 字节,而操作系统与磁盘的 IO 过程通常以 8 个扇区,也就是 4KB 大小进行交互。

  2. 而 MySQL和磁盘进行数据交互的基本单位是 16KB。

  3. MySQL 中的数据文件,是以 page 为单位保存在磁盘当中的。

    • 单个 page 中不仅存储数据记录,还存储索引信息(这里的索引信息不是指B+树那种用于在页与页之间导航的索引(如指向其他页的指针和键值),而是指页内的元数据和定位信息),用来优化在单个 page 中查找数据的效率。

    • 一个数据页(Data Page)内部确实有自己的一套页内组织结构(例如,一个叫做 Slot Array 或 行偏移量表 的结构),用来快速定位页内的每一条记录。这确实优化了在单个Page内的查找效率。

    • 只有叶子节点保存数据,非叶子节点只保存索引信息。

  4. MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中,所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page(16KB)。

  5. 为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称为 Buffer Pool 的的大内存空间(内存池),来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。

4. 索引为什么能够提高查询速度?

索引的核心在于它通过特定的数据结构(如 B+Tree)将无序的数据变得有序,从而可以使用高效的查找算法。

以最常用的 B+Tree 索引为例:

  1. 有序性:B+Tree 的叶子节点存储了所有的数据(或数据的指针),并且这些叶子节点形成了一个有序的双向链表。

  2. 多路平衡查找树:B+Tree 是一个矮胖的树,它有很多分支(路)。这意味着在庞大的数据集中,只需要很少的几次 I/O 操作(比如 3-4 次)就能从根节点定位到目标数据,避免了全表扫描的巨额 I/O 开销。

简单过程
假设我们在 age 字段上建立了索引,要查找 age = 30 的所有记录。

  • 数据库引擎从索引树的根节点开始。

  • 比较根节点的值,决定下一步要查找哪个分支(比如,去 20-40 这个分支)。

  • 再比较分支节点的值,继续向下,直到找到 age=30 的叶子节点。

  • 从叶子节点中获取到对应数据行的位置(或直接获取数据),然后返回结果。

这个过程远比从磁盘中逐行扫描要快得多。

4.1 一个问题?

Mysql 索引如果是主键我可以理解每个索引中存储的就是主键值,那如果是年龄呢?比如age=30,万一表中很多age=30的,那怎么存储?

4.1.1 核心概念:二级索引的存储结构

在InnoDB存储引擎中(这是MySQL最常用的引擎),所有索引都使用B+树结构。关键的区别在于:

  • 主键索引(聚簇索引):叶子节点存储的是完整的数据行

  • 二级索引(如你创建的age索引):叶子节点存储的是两部分内容:

    1. 索引列的值(在你的例子中就是 age 的值,比如 30

    2. 对应数据行的主键值

所以,一个age索引的B+树叶子节点里,存的不是整行数据,而是 (age, 主键) 这样的组合。

4.1.2 当 age=30 有很多条时,如何存储?

假设我们有一张表 users

CREATE TABLE users ( 
id INT PRIMARY KEY, -- 主键 
name VARCHAR(100), 
age INT, 
INDEX idx_age (age) -- 为age字段创建的二级索引
);

表中数据如下:

id(主键)nameage
1张三30
3李四30
5王五25
7赵六30
9孙七28

那么,idx_age 这个索引在底层可能的存储结构(简化版B+树)是这样的:

叶子节点:

[ (25, 5) ] -> [ (28, 9) ] -> [ (30, 1), (30, 3), (30, 7) ]
  • 每个条目都是 (age, id)

  • 所有 age=30 的记录会被聚集存储在同一个或相邻的叶子节点中,并按主键id排序(即 1, 3, 7)。

4.1.3 查询过程是怎样的?
SELECT * FROM users WHERE age = 30;
  1. 索引查找:存储引擎首先在 idx_age 这棵B+树中快速定位到 age=30 的叶子节点。

  2. 获取主键列表:它找到了所有 (30, 1)(30, 3)(30, 7) 这样的条目。

  3. 回表查询:由于 SELECT * 需要获取所有列的数据,而 idx_age 索引中只存储了 age 和 id。因此,引擎必须拿着找到的每一个主键值(1, 3, 7),回到主键索引(聚簇索引) 的B+树中再去查找一遍,以获取完整的数据行。

  4. 返回结果:将从主键索引中取出的完整行数据返回给客户端。

这个过程被称为 回表。这也是为什么在有很多重复值的列上建索引,查询效率可能不高的原因之一,因为会产生大量的回表操作。

举个例子:低索引场景

执行 SELECT * FROM users WHERE gender = '男';

  1. 在 gender 索引中,我们找到的不是一个点,而是一个范围。假设表中有10000条记录,其中5000条是男性。

  2. 数据库会定位到索引中第一个 gender='男' 的条目,然后沿着叶子节点的链表向后扫描,依次取出5000个对应的主键ID(比如 id=8, 23, 47, 109, ... , 9897)。

  3. 现在,数据库需要根据这 5000个分散的主键,回到聚簇索引中进行 5000次 回表查询。

这5000次回表查询,意味着磁盘磁头需要在聚簇索引的B+树上进行5000次近乎随机的定位。即使这些ID在 gender 索引的 B+ Tree 中是顺序排列的,但它们在聚簇索引(按主键物理存储)的 B+ Tree 中的位置是完全无序、分散的。

4.1.4 如何优化这种情况?(覆盖索引)

如果你的查询不需要获取所有列,可以创建一个覆盖索引 来避免回表。

例如,你只查询 id 和 age

SELECT id, age FROM users WHERE age = 30;

这时,因为 idx_age 索引的叶子节点上已经包含了查询所需的所有数据(age 和 id),存储引擎在 idx_age 索引里就能拿到结果,不需要再回表。这个查询会非常快。

你甚至可以为了特定查询创建更优化的覆盖索引:

# 比如你经常根据年龄查名字
SELECT name FROM users WHERE age = 30;
# 可以创建一个包含name的索引
CREATE INDEX idx_age_name ON users (age, name);

这个 idx_age_name 索引的叶子节点存储的是 (age, name, id),上面的查询同样不需要回表。

4.1.5 总结
特性主键索引(聚簇索引)二级索引(如age索引)
叶子节点存储内容完整的数据行索引列的值 + 主键值
如何处理重复值主键本身唯一,无重复将重复的索引值与它们各自对应的不同主键组合在一起存储
查询流程 WHERE age=30不适用,除非主键是age1. 在age索引树找到所有(30, 主键)
2. 回表:用主键回主键索引树取完整数据

所以,age=30有很多条时,索引中存储的是多个 (30, 主键1)(30, 主键2)… 这样的条目,并通过B+树高效地链接在一起。查询时,先通过索引找到这些主键,然后再通过主键去获取实际数据。

5. 索引操作

5.1 创建主键索引

1️⃣ 第一种方式

-- 在创建表的时候,直接在字段名后指定 primary key
create table user1(id int primary key, name varchar(30));

2️⃣ 第二种方式

-- 在创建表的最后,指定某列或某几列为主键索引
create table user2(id int, name varchar(30), primary key(id));

3️⃣ 第三种方式

create table user3(id int, name varchar(30));
-- 创建表以后再添加主键
alter table user3 add primary key(id);

主键索引的特点:

  • 一个表中,最多有一个主键索引,当然也可以是复合主键。

  • 主键索引的效率最高(主键不可重复)。

  • 创建主键索引的列,它的值不能为 null,且不能重复。

  • 主键索引的列基本上是int。

5.2 创建唯一键索引

1️⃣ 第一种方式

-- 在表定义时,在某列后直接指定unique唯一属性。
create table user4(id int primary key, name varchar(30) unique); 

2️⃣ 第二种方式

-- 创建表时,在表的后面指定某列或某几列为unique
create table user5(id int primary key, name varchar(30), unique(name));

3️⃣ 第三种方式

create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);

唯一索引的特点:

  • 一个表中,可以有多个唯一索引。

  • 查询效率高。

  • 如果在某一列建立唯一索引,必须保证这列不能有重复数据。

  • 如果一个唯一索引上指定not null,等价于主键索引。

5.3 创建普通索引

1️⃣ 第一种方式

create table user8(id int primary key,    name varchar(20),email varchar(30),index [nikename] (name) --在表的定义最后,指定某列为索引
);

2️⃣ 第二种方式

create table user9(id int primary key, name varchar(20), email varchar(30));
alter table user9 add index(name); --创建完表以后指定某列为普通索引

3️⃣ 第三种方式

create table user10(id int primary key, name varchar(20), email varchar(30));
-- 创建一个索引名为 idx_name 的索引
create index idx_name on user10(name);

普通索引的特点:

  • 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多。

  • 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引。

5.4 查询索引

1️⃣ 第一种方式

show keys from tablename;

2️⃣ 第二种方式

show index from tablename;

5.5 删除索引

5.5.1 删除主键索引
alter table 表名 drop primary key;
5.5.2 其他索引删除
alter table tablename drop index 索引名

索引名就是 show keys from 表名 中的 Key_name 字段

5.5.3 还有一种方法
drop index 索引名 on 表名

5.6 索引创建的原则

  • 比较频繁作为查询条件的字段应该创建索引。

  • 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件。

  • 更新非常频繁的字段不适合作创建索引。

  • 不会出现在where子句中的字段不该创建索引。

http://www.dtcms.com/a/474108.html

相关文章:

  • Blender机箱盒体门窗铰链生成器资产预设 Hingegenious
  • 网站托管就业做美食有哪些网站
  • 神经符号AI的深度探索:从原理到实践的全景指南
  • 零食网站建设规划书建行输了三次密码卡锁怎么解
  • Python代码示例
  • 济南市历下区建设局官方网站wordpress高级套餐
  • ALLEGRO X APD版图单独显示某一网络的方法
  • 计算机网络基础篇——如何学习计算机网络?
  • 电子商务网站建设的总体设计wordpress dux主题5.0版本
  • 《jEasyUI 创建简单的菜单》
  • AI【前端浅学】
  • 怎么设置网站名称失眠先生 wordpress
  • 低空物流+韧性供应链:技术架构与企业级落地的开发实践指南
  • Quartus II软件安装步骤(附安装包)Quartus II 18超详细下载安装教程
  • 动规——棋圣匹配
  • 侵入别人的网站怎么做我的家乡网页制作步骤
  • Thonny(Python IDE)下载和安装教程(附安装包)
  • Fastdfs_MinIO_腾讯COS_具体逻辑解析
  • SDCC下载和安装图文教程(附安装包,C语言编译器)
  • 用python做的电商网站常德网站建设费用
  • LSTM新架构论文分享5
  • 自然语言处理分享系列-词向量空间中的高效表示估计(三)
  • 网页做网站的尺寸最新永久ae86tv最新
  • java的StringJoiner用法
  • 作业、工时、工作中心的一些配置杂记
  • 陇南做网站网站网站建设的原则有哪些
  • 网站建设费用价格明细表有道云笔记 同步 wordpress
  • Uniapp微信小程序开发:微信小程序支付功能后台代码
  • 制作网站谁家做的好怎样搞网络营销
  • win7winlogon调试指南winlogon!SignalManagerResetSignal需要运行多少次