mysql:索引

索引

为什么使用索引

![img](file:///C:\Users\Boerk\AppData\Local\Temp\ksohtml16464\wps2.png)

假如给数据使用 二叉树 这样的数据结构进行存储,如下图所示

![img](file:///C:\Users\Boerk\AppData\Local\Temp\ksohtml16464\wps3.png) |

索引概述

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
索引的本质:索引是数据结构。你可以简单理解为“排好序的快速查找数据结构”,满足特定查找算法。 这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现 高级查找算法 。

优点

  1. 类似大学图书馆建书目索引,提高数据检索的效率,降低 数据库的IO成本 ,这也是创建索引最主要的原因。

  2. 通过创建唯一索引,可以保证数据库表中每一行 数据的唯一性 。

  3. 在实现数据的参考完整性方面,可以 加速表和表之间的连接 。换句话说,对于有依赖关系的子表和父表联合查询时, 可以提高查询速度。

  4. 在使用分组和排序子句进行数据查询时,可以显著 减少查询中分组和排序的时间 ,降低了CPU的消耗。

缺点
增加索引也有许多不利的方面,主要表现在如下几个方面:

  1. 创建索引和维护索引要 耗费时间 ,并且随着数据量的增加,所耗费的时间也会增加。

  2. 索引需要占 磁盘空间 ,除了数据表占数据空间之外,每一个索引还要占一定的物理空间, 存储在磁盘上 ,如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。

  3. 虽然索引大大提高了查询速度,同时却会 降低更新表的速度 。当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。

因此,选择使用索引时,需要综合考虑索引的优点和缺点。

索引可以增加查询效率,但是会降低增删改的效率。

通常情况下,在增删改之前删除索引,在完成后再次创建索引


设计一个索引

mysql> CREATE TABLE index_demo(
    c1 INT,
    c2 INT,
    c3 CHAR(1),
    PRIMARY KEY(c1)
) ROW_FORMAT = Compact;
#ROW_FORMAT为行格式
#这个新建的 index_demo 表中有2个INT类型的列,1个CHAR(1)类型的列,而且我们规定了c1列为主键,这个表使用 Compact 行格式来实际存储记录的。这里我们简化了index_demo表的行格式示意图:

这个新建的 index_demo 表中有2个INT类型的列,1个CHAR(1)类型的列,而且我们规定了c1列为主键,这个表使用 Compact 行格式来实际存储记录的。这里我们简化了index_demo表的行格式示意图:

image-20220320180248485

  • record_type :记录头信息的一项属性,表示记录的类型, 0 表示普通记录、 2 表示最小记
    录、 3 表示最大记录、 1 暂时还没用过,下面讲。

  • next_record :记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,我们用箭头来表明下一条记录是谁。

  • 各个列的值 :这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。

  • 其他信息 :除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。

image-20220320181420890

我们在根据某个搜索条件查找一些记录时为什么要遍历所有的数据页呢?因为各个页中的记录并没有规律,我们并不知道我们的搜索条件匹配哪些页中的记录,所以不得不依次遍历所有的数据页。所以如果 我们想快速的定位到需要查找的记录在哪些数据页中该咋办?我们可以为快速定位记录所在的数据页而建立一个目录,建这个目录必须完成下边这些事:

  • 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。
  • 给所有的页建立一个目录项
    • 每一个目录项存储了当前数据页中最小主键值。以及数据页号。
    • 以第十号数据页举例,创建了目录项1,他记载了最小主键值:1和数据页编号:10。

image-20220320181636375

  • 至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引
  • 此时,我们需要查找主键为20的数据,则在目录项中查找最小主键。就会立即排除目录项1、2、4,找到主键20在目录项4,数据页9中。

同理,我们可以将目录项也作为一个数据页,然后再次创建目录项数据页的目录项。

image-20220320182035870

然后存在多个目录项数据页的时候常见一个更高一级的数据页。

image-20220320182602590

由此迭代,便形成了B+树

image-20220321092114044

  • B+Tree
    一个B+树的节点其实可以分成好多层,规定最下边的那层,也就是存放我们用户记录的那层为第0 层, 之后依次往上加。之前我们做了一个非常极端的假设:存放用户记录的页 最多存放3条记录 ,存放目录项记录的页 最多存放4条记录 。其实真实环境中一个页存放的记录数量是非常大的,假设所有存放用户记录的叶子节点代表的数据页可以存放 100条用户记录 ,所有存放目录项记录的内节点代表的数据页可以存放 1000条目录项记录 ,那么:

  • 如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放 100 条记录。

  • 如果B+树有2层,最多能存放 1000×100=10,0000 条记录。

  • 如果B+树有3层,最多能存放 1000×1000×100=1,0000,0000 条记录。

  • 如果B+树有4层,最多能存放 1000×1000×1000×100=1000,0000,0000 条记录。相当多的记录!!!

你的表里能存放 条记录吗?所以一般情况下,我们 用到的B+树都不会超过4层 ,那我们通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有所谓的 Page Directory (页目录),所以在页面内也可以通过 二分法 实现快速定位记录。


  • 常见索引概念

索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)非聚簇(非聚集)索引。我们也把非聚集索引称为二级索引或者辅助索引。

聚簇索引

  1. 用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
    页内 的记录是按照主键的大小顺序排成一个 单向链表 。
    各个存放 用户记录的页 也是根据页中用户记录的主键大小顺序排成一个 双向链表 。
    存放 目录项记录的页 分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个 双向链表 。
  2. B+树的 叶子节点 存储的是完整的用户记录。
    所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。

优点

  • 数据访问更快 ,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
  • 聚簇索引对于主键的 排序查找 和 范围查找 速度非常快
  • 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多 个数据块中提取数据,所以 节省了大量的io操作 。

缺点

  • 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
  • 更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
  • 二级索引访问需要两次索引查找 ,第一次找到主键值,第二次根据主键值找到行数据

二级索引

不由主键或者不由非空且唯一的列构成的索引称为二级索引。

image-20220321094258933

回表:我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树!

我的理解:二级索引构成的B+树中,只存有当前构成索引的字段和主键字段。当根据当前字段找到时,根据主键回到聚簇索引中查询详细信息。

image-20220321094545814

联合索引

联合索引属于非聚簇索引(二级索引)

根据两个或多个字段构成B+树,根据第一个字段排列,第一字段相同时,再根据第二字段排列,以此类推。

image-20220321095121534

含有的字段:构成该B+树的字段+主键


InnoDB的B+树索引的注意事项

  1. 根页面位置万年不动

    1. 首先在一个根目录中存储数据(叶子节点)
    2. 当这个数据页有3条数据再存数据时
    3. 开辟一个当前数据页的复制
    4. 对数据页进行页分裂得到另一个新页
    5. 将新插入的数据存入新页
    6. 当前数据页变为一个目录页
  2. 内节点中目录项记录的唯一性

    1. 要保证二级索引的唯一性,否则会出现一些情况image-20220321100258224

    2. 此时视图插入一个(c1,c2,c3) values(9,1,c)

    3. 这个二级索引就无法插入了,因为不知道应该插在哪里,c2为1,可能插在页4,也可能插在页5.

    4. 因此构建目录页时,需将主键也带上。

  3. 一个页面最少存储2条记录


MyISAM中的索引方案

MyISAM中只有非聚簇索引,没有聚簇索引

image-20220321101145930

每一个索引都直接保存地址值。


MyISAM与InnoDB对比

MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。小结两种引擎中索引的区 别:

  1. 在InnoDB存储引擎中,我们只需要根据主键值对 聚簇索引 进行一次查找就能找到对应的记录,而在中却需要进行一次 回表 操作,意味着MyISAM中建立的索引相当于全部都是 二级索引 。
  2. InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是 分离的 ,索引文件仅保存数据记录的地址。
  3. InnoDB的非聚簇索引data域存储相应记录 主键的值 ,而MyISAM索引记录的是 地址 。换句话说,
    InnoDB的所有非聚簇索引都引用主键作为data域。
  4. MyISAM的回表操作是十分 快速 的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
  5. InnoDB要求表 必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。

索引的代价

索引是个好东西,可不能乱建,它在空间和时间上都会有消耗:

  • 空间上的代价
    每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会 占用 16KB 的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。

  • 时间上的代价

    每次对表中的数据进行 增、删、改 操作时,都需要去修改各个B+树索引。而且我们讲过,B+树每层节点都是按照索引列的值 从小到大的顺序排序 而组成了 双向链表 。不论是叶子节点中的记录,还是内节点中的记录(也就是不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序 而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需 要额外的时间进行一些 记录移位 , 页面分裂 、 页面回收 等操作来维护好节点和记录的排序。如果我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。


一些其他数据结构

Hash结构

hash的特点:输入相同的值,输出的也一定相同。反之则不成立。

将hash(key)存入数组。

从效率上来说,哈希比B+树还要快

哈希可能存在哈希冲突。使用链表将相同值进行“焊接”

image-20220321105452822


二叉搜索树

如果我们利用二叉树作为索引结构,那么磁盘的IO次数和索引树的高度是相关的。

image-20220321110103047

创造出来的二分搜索树如下图所示:

image-20220321110122324

为了提高查询效率,就需要 减少磁盘IO数 。为了减少磁盘IO的次数,就需要尽量 降低树的高度 ,需要把原来“瘦高”的树结构变的“矮胖”,树的每层的分叉越多越好。


AVL树

AVL树,即二叉平衡树或者平衡二叉树。

image-20220321110322557

针对同样的数据,如果我们把二叉树改成 M 叉树 (M>2)呢?当 M=3 时,同样的 31 个节点可以由下面的三叉树来进行存储:image-20220321110341542

你能看到此时树的高度降低了,当数据量N大的时候,以及树的分叉数M大的时候,M叉树的高度会远小于二叉树的高度(M>2)。所以,我们需要把树从“瘦高"变"矮胖”

B-TREE

B-Tree,即Balance Tree多路平衡查找树

image-20220321110529537

一个 M 阶的 B 树(M>2)有以下的特性:

  1. 根节点的儿子数的范围是 [2,M]。

  2. 每个中间节点包含 k-1 个关键字和 k 个孩子,孩子的数量 = 关键字的数量 +1,k 的取值范围为

[ceil(M/2), M]。

  1. 叶子节点包括 k-1 个关键字(叶子节点没有孩子),k 的取值范围为 [ceil(M/2), M]。

  2. 假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i]

<Key[i+1]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k 个指针,即为:P[1], P[2], …,

P[k],其中 P[1] 指向关键字小于 Key[1] 的子树,P[i] 指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k] 指向关键字大于 Key[k-1] 的子树。

  1. 所有叶子节点位于同一层。

上面那张图所表示的 B 树就是一棵 3 阶的 B 树。我们可以看下磁盘块 2,里面的关键字为(8,12),它有 3 个孩子 (3,5),(9,10) 和 (13,15),你能看到 (3,5) 小于 8,(9,10) 在 8 和 12 之间,而 (13,15) 大于 12,刚好符合刚才我们给出的特征。

然后我们来看下如何用 B 树进行查找。假设我们想要 查找的关键字是 9 ,那么步骤可以分为以下几步:

  1. 我们与根节点的关键字 (17,35)进行比较,9 小于 17 那么得到指针 P1;
  2. 按照指针 P1 找到磁盘块 2,关键字为(8,12),因为 9 在 8 和 12 之间,所以我们得到指针 P2;
  3. 按照指针 P2 找到磁盘块 6,关键字为(9,10),然后我们找到了关键字 9。

你能看出来在 B 树的搜索过程中,我们比较的次数并不少,但如果把数据读取出来然后在内存中进行比较,这个时间就是可以忽略不计的。而读取磁盘块本身需要进行 I/O 操作,消耗的时间比在内存中进行比较所需要的时间要多,是数据查找用时的重要因素。 B 树相比于平衡二叉树来说磁盘 I/O 操作要少 , 在数据查询中比平衡二叉树效率要高。所以 只要树的高度足够低,IO次数足够少,就可以提高查询性能

image-20220321111123931


B+树

B+ 树和 B 树的差异:

  1. 有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数
    +1。

  2. 非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最 小)。

  3. 非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中,

  4. 所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大 小从小到大顺序链接。

B 树和 B+ 树都可以作为索引的数据结构,在 MySQL 中采用的是 B+ 树。但B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然。

image-20220321111907311

image-20220321112005546

image-20220321112138860


InnoDB数据存储结构

磁盘与内存交互的基本单位是

每一页的大小是16KB,在数据库中,无论是读多行还是读一行,都是将这些行所在页进行加载。

也就是说数据库I/O操作最小的单位是页。

数据页的特点

  • 在无论上不相连
  • 通关双向链表关联

页的上层结构

  • 区:一个区有64个连续的页,所以一个区的大小是1MB
  • 段:段是数据库分配的单位,不同类型的数据库对象存在不同的段中
  • 表空间:逻辑上的容器。

名称 占用大小 说明
File Header 28字节 文件头
Page Header 56字节 页头,页的状态信息
Infimum & Supremum 26字节 最大和最小记录
User Records 不确定 用户记录
Free Space 不确定 休闲记录
Page Directory 不确定 页目录,存储用户记录的相对位置
File Trailer 8字节 文件尾,校验页是否完整

索引的创建于设计原则

索引的分类

  • 功能逻辑上说,索引主要有 4 种,分别是普通索引唯一索引主键索引全文索引
  • 按照物理实现方式 ,索引可以分为 2 种:聚簇索引非聚簇索引
  • 按照作用字段个数进行划分,分成单列索引联合索引

  • 普通索引

可以添加在任何字段上,只是用于提高查询效率。

  • 唯一索引

当一个字段被声明唯一约束时,就默认会创建唯一索引。一个表中可以有多个唯一索引

  • 主键索引

当声明主键约束的时候,就会创建主键索引。一张表中最多只能有一个主键索引,也只能有一个主键。

主键索引是一种聚簇索引,因此主键索引或存储表中的数据。所以只能存在一个。

  • 单列索引

只作用在一个字段上。一个表中可以存在多个单列索引

小结:不同的存储引擎支持的索引类型也不一样

  • InnoDB :支持 B-tree、Full-text 等索引,不支持 Hash 索引;
  • MyISAM : 支持 B-tree、Full-text 等索引,不支持 Hash 索引;
  • Memory :支持 B-tree、Hash 等索引,不支持 Full-text 索引;
  • NDB :支持 Hash 索引,不支持 B-tree、Full-text 等索引;
  • Archive :不支持 B-tree、Hash、Full-text 等索引;

创建索引

语法

# 创建表时,添加约束,会隐式的添加索引
CREATE TABLE dept(
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(20)
);#为主键添加了主键索引

CREATE TABLE emp(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20) UNIQUE,
dept_id INT,
CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);#为主键添加了主键索引、为emp_name添加了非空索引、为dept_id添加了普通索引(外键)

#显式的创建索引
CREATE TABLE table_name [col_name data_type]
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ASC | DESC]
#查看索引
SHOW CREATE TABLE book;
SHOW CREATE TABLE book\G #命令行
#或者
SHOW INDEX FROM book;
  • 普通索引
CREATE TABLE book(
    book_id INT , book_name VARCHAR(100), 
    authors VARCHAR(100), info VARCHAR(100) ,
    comment VARCHAR(100), year_publication YEAR, 
    INDEX(year_publication)#普通索引
);
  • 唯一索引
# 声明有唯一索引的字段,在添加数据时,要保证唯一性,但是可以添加null
CREATE TABLE book1(
    book_id INT ,
    book_name VARCHAR(100),
    AUTHORS VARCHAR(100),
    info VARCHAR(100) ,
    COMMENT VARCHAR(100),
    year_publication YEAR,
    UNIQUE INDEX uk_idx_cmt(COMMENT)#声明索引
);
#Duplicate entry '1' for key 'book1.uk_idx_cmt'
  • 主键索引
#通过定义主键约束的方式定义主键索引
CREATE TABLE book2(
book_id INT PRIMARY KEY ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);

#通过删除主键约束的方式删除主键索引
ALTER TABLE book2
DROP PRIMARY KEY;
  • 单列索引
#创建单列索引
CREATE TABLE book3(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR,
#声明索引
UNIQUE INDEX idx_bname(book_name)
);
  • 联合索引
#创建联合索引
CREATE TABLE book4(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR,
#声明索引
INDEX mul_bid_bname_info(book_id,book_name,info)
);

在表已经创建好了的情况下添加索引

CREATE TABLE book5(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);
#添加普通索引
ALTER TABLE book5 ADD INDEX idx_cmt(COMMENT);
#添加唯一索引
ALTER TABLE book5 ADD UNIQUE uk_idx_bname(book_name);
#添加多列索引
ALTER TABLE book5 ADD INDEX mul_bid_bname_info(book_id,book_name,info);

#方式二
CREATE INDEX ... ON ...
CREATE TABLE book6(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);
#添加普通索引
CREATE INDEX idx_cmt ON book6(COMMENT);
#添加唯一索引
CREATE UNIQUE INDEX  uk_idx_bname ON book6(book_name);
#添加多列索引
CREATE INDEX mul_bid_bname_info ON book6(book_id,book_name,info);

删除索引

#方式1:ALTER TABLE .... DROP INDEX ....
ALTER TABLE book5 
DROP INDEX idx_cmt;

#方式2:DROP INDEX ... ON ...
DROP INDEX uk_idx_bname ON book5;
posted @ 2022-03-21 22:50  Boerk  阅读(57)  评论(0)    收藏  举报