MySQL索引特性

目录

一、MySQL缓冲区

没有索引,可能会有什么问题

认识磁盘

定位扇区

磁盘随机访问与连续访问

逻辑结论:

二、MySQL的page

理解多page模式

除了B+树其他数据结构为何不行?

为什么选择B+?

聚簇索引和非聚簇索引

三、索引的建立

1.创建主键索引

2.唯一索引的创建

3.创建普通索引

4.全文索引的创建

5.查询索引

6.删除索引的方法

7.索引创建原则


一、MySQL缓冲区

MySQL是一款有客户端和服务端的网络应用,mysql是它的客户端,mysqld是它的服务端服务端本质就是一个进程,它存在于内存当中。 而我们存储在MySQL中的数据是保存在磁盘上的,当我们对MySQL中数据进行增删查改操作时,不可能是直接在磁盘上进行操作,而是将对应的数据加载到内存中,在内存中对数据进行操作,操作完毕之后再写回磁盘。

mysqld是MySQL的服务端,它存在于内存中,mysqld服务端启动起来以后会先向内存申请一段空间buffer pool,作为MySQL的数据缓冲区。 MySQL是有着更高IO场景的应用软件,所以为了提高基本IO的效率,MySQL进行IO的基本单位是16KB。(操作系统是4kb) 也就是说,磁盘这个硬件设备的基本单位是512字节,而MySQL InnoDB存储引擎使用16KB进行IO交互 ,即MySQL与磁盘进行数据交互的基本单位是16KB。这个基本数据单元,在MySQL这里叫做page(注意这里的page是MySQL的page,不是系统的page)。

**当mysqld的buffer pool中充满大量page的时候,MySQL也要管理所有的page,管理的原则也是先描述再组织。**MySQL会为每个page建立一个数据结构,该数据结构记录了每个page的详细信息,再通过特定的算法和数据结构将其组织起来。这里的组织方式就和MySQL的索引有关。

没有索引,可能会有什么问题

索引可以提高数据库的性能,索引是物美价廉的东西。不用加内存,不用改程序,不用调sql,只要执行 正确的 create index ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高 是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价值,在于提高一个海量数据的检索速度。


常见索引分为:

  • 主键索引(primary key)
  • 唯一索引(unique)
  • 普通索引(index)
  • 全文索引(fulltext)--解决中子文索引问题。

MySQL 给用户提供存储服务,而存储的都是数据,数据在磁盘这个外设当中。磁盘是计算机中的一个机 械设备,相比于计算机其他电子元件,磁盘效率是比较低的,在加上IO本身的特征,可以知道,如何提高效率,是 MySQL 的一个重要话题。

认识磁盘

在看看磁盘中一个盘片

数据库文件**,本质其实就是保存在磁盘的盘片当中。** 也就是上面的一个个小格子中,就是我们经常所说 的扇区。当然,数据库文件很大,也很多,一定需要占据多个扇区。

所以我们由此得出所以,最基本的,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区。 而我们能够定位任何一个扇区,那么便能找到所有扇区,因为查找方式是一样的。

定位扇区

从图中得知,每个盘面都有一个磁头,那么磁头和盘面的对应关系便是1对1的。所以,我们只需要知道磁头、柱面、扇区对应的编号。即可在磁盘上定位所要访问的扇区。这种磁盘数据定位方式叫做 CHS 。 不过实际系统软件使用的并不是CHS (但是硬件是),而是 LBA ,这是一种线性地址,可以想象成虚拟地址与物理地址。系统会将 LBA 地址最后会转化成为 CHS ,交给磁盘去进行数据读取。

磁盘随机访问与连续访问

随机访问:本次IO所给出的扇区地址和上次IO给出扇区地址不连续,这样的话磁头在两次IO操作之间需要作比较大的移动动作才能重新开始读/写数据。

连续访问:如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次 IO操作,这样的多个IO操作称为连续访问。

因此尽管相邻的两次IO操作在同一时刻发出,但如果它们的请求的扇区地址相差很大的话也只能称为随机访问,而非连续访问。

逻辑结论:

  1. MySQL 中的数据文件,是以page为单位保存在磁盘当中的。
  2. MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数 据。
  3. 而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。
  4. 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。 而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。(也就是说,mysql得决定什么时候刷新缓存区数据。)
  5. 为了更好的进行上面的操作,MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称为 Buffer Pool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。
  6. 为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数

二、MySQL的page

经过上面的分析,我们发现,page的存在,极大决定了io的次数,所以索引,应运而生。

我们先来做一个实验,创建一张user表,表中将id设置为主键,它会默认生成主键索引。

cpp 复制代码
create table if not exists user (
id int primary key,
age int not null,
name varchar(16) not null
);

随后我们再乱序插入一些数据。

cpp 复制代码
mysql> insert into user (id, age, name) values(3, 18, '杨过');
Query OK, 1 row affected (0.01 sec)

mysql> insert into user (id, age, name) values(4, 16, '小龙女');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(2, 26, '黄蓉');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(5, 36, '郭靖');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(1, 56, '欧阳锋');
Query OK, 1 row affected (0.01 sec)

但当我们查看表中数据的时候,我们会发现,表中的数据自动按照id升序排序了。这是MySQL帮我们做的主键排序。MySQL为什么要帮我们做排序呢?这就需要理解MySQL单个page的内部结构了。

MySQL中要管理很多数据表文件,而要管理好这些文件,就需要先描述再组织。我们以上面创建的user表为例子,假设user表中的数据加载到MySQL的page中,呈现出来的是下图的形式
不同的page在MySQL中,都是16KB,并且使用page_prev和page_next构成双向链表。而单个page内部的数据记录之间,也是通过单链表连接的。因为创建user表时我们添加了主键,所以MySQL会默认按照主键给我们的数据进行排序。

排序的目的是为了优化查询的效率 。在单个page内部存放数据的模块,实质上也是一个链表的结构,链表的特点就是增和删特别快,查询和修改比较慢,所以必须要优化查询效率,数据有序了是非常方便查询的,查询快了,修改也就快了。

除此之外,单个page内部还会引入页目录,就像一本书的目录一样,通过目录可以快速定位每一章每一节的起始页码。例如下图的例子,引入页目录以后,如果我们要查找id=4的记录,原本要线性遍历,查找4个节点才能找到。现在可以直接从页目录2开始查找,只需要查找2个节点就可以找到,这也是提高了查询的效率。但这必须建立在page内部数据是有序的基础上,所以MySQL对表内数据按主键排序也是为了更好地引入页目录。

理解多page模式

通过上面的分析,我们知道,上面页模式中,只有一个功能,**就是在查询某条数据的时候直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能。**但是,我们也可以看到,现在的页模式内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据。

如果有1千万条数据,一定需要多个Page来保存1千万条数据,多个Page彼此使用双链表链接起 来,而且每个Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这效率太低了。所以一定要引入页目录。

而单个页内部通过目录可以快速定位到需要查找的数据记录,但这仅局限于单页内查找数据,如果我们的MySQL表数据很大,分布在多个page中,不仅要对单个page内部进行查询,还要在page之间进行查询**,如果page之间查询还是按照链式查询的话,那么****查询的效率依旧是很低的**

所以为了解决这个问题,MySQL为多个page之间也引入了目录结构。它会让几个page不存放任何数据记录,单独存放page的目录。每个目录对应每个page的第一个数据记录的地址,这样在多page之间查找的效率就得到了提高。

注意:这部分的空间浪费是有意义的,就像书本的前几页目录,都是为了更快更准的找到对应的内容。

可是,这样子我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?不用担心,可以在加目录页。

当我们再加以后我们就会发现,这个结构成为了下面。

这时我们就会发现,这就是传说中的B+树!

所以InnoDB 在建立索引结构来管理数据的时候,用的就是B+树

除了B+树其他数据结构为何不行?

  • 链表?线性遍历,效率低
  • 二叉搜索树?极端情况退化,可能退化成为线性结构
  • AVL &&红黑树?虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶B+,意味着树整体 过高,大家都是自顶向下找,层高越低,意味着系统与硬盘更少的IO Page交互。虽然你很秀,但是有更秀的。就是B+树
  • Hash?官方的索引实现方式中, MySQL 是支持HASH的,不过 InnoDB 和 MyISAM 并不支持.Hash跟进其算法特征,决定了虽然有时候也很快(O(1))。不过有一个致命的缺陷,hash在面对范围查找时效率太低,查找一个范围的值得一次次线性遍历

B树?最值得比较的是 InnoDB 为何不用B树作为底层索引?

这是B树的结构,B树节点,既有数据,又有Page指针,而B+,只有叶子节点有数据,其他目录页,只有键值和 Page指针。B+叶子节点,全部相连,而B没有

为什么选择B+?

  1. 节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少。
  2. 叶子节点相连,更便于进行范围查找

更通俗的说法便是,无论是AVL还是红黑,还是B树都是瘦高型的数据结构,而只有B+树是胖矮型的,io的次数更少,对于B和B+来说,两者都可,但是B+树更优异。

聚簇索引和非聚簇索引

聚簇索引指的是B+树的叶子节点是将索引和数据存放在一起的,而非聚簇索引指的是B+树的叶子节点没有将索引和数据存放在一起。

MySQL的InnoDB存储引擎就是聚簇索引,它将用户的数据和索引数据保存在一起。而MySQL的MyISAM存储引擎是非聚簇索引,它将索引数据和用户数据分离,也就是说B+树的叶子节点没有数据,只有对应数据的地址。

当然,MySQL除了默认会建立主键索引之外,我们用户也有可能建立按照其它列信息建立的索引,一般这种索引称为辅助索引或者普通索引。对于MyISAM存储引擎,建立辅助索引和主键索引没有区别,无非就是主键不能重复,而非主键可以重复。但是InnoDB存储引擎的辅助索引和主键索引却不一样,InnoDB的辅助索引中叶子节点并没有数据,而只有列数据对应记录的主键值,所以在查找数据的时候,通过辅助索引找到目标记录的主键值,然后用主键值在主键索引中检索获得记录,这个过程称为回表的过程。所以如果是通过辅助索引来查找数据,需要查找两遍索引。

三、索引的建立

1.创建主键索引

第一种方式:

在建表的时候,直接在字段后面指定primary key,即给某一字段添加主键。

cpp 复制代码
create table user1(id int primary key, name varchar(30));

第二种方式:

在创建表的最后,指定某列或某几列为主键索引

cpp 复制代码
create table user2(id int, name varchar(30), primary key(id)); 

第三种方式:

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

主键索引的特点:

  1. 一个表中,最多有一个主键索引,当然也可以使用复合主键。
  2. 主键索引的效率高,因为主键不可重复。
  3. 创建主键索引的列,它的值不能为null,且不能重复。
  4. 主键索引的列基本上是int。

2.唯一索引的创建

第一种方式

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

第二种方式

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

第三种方式

cpp 复制代码
create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);

唯一索引的特点:

  1. 一个表中,可以有多个唯一索引。
  2. 唯一索引查询效率高。
  3. 如果在某一列上建立唯一索引,必须保证这列不能有重复数据。
  4. 如果一个唯一索引上指定not null,那么等价于主键索引。

3.创建普通索引

第一种方式

cpp 复制代码
create table user8(id int primary key,
 name varchar(20),
 email varchar(30),
 index(name) --在表的定义最后,指定某列为索引
);

第二种方式

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

第三种方式

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

普通索引的特点:

  1. 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
  2. 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引

4.全文索引的创建

当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。MySQL提供全文索引机制,但是有 要求,要求表的存储引擎必须是MyISAM,而且默认的全文索引支持英文,不支持中文。如果对中文进 行全文检索,可以使用sphinx的中文版(coreseek)。

5.查询索引

第一种方法: show keys from 表名

第二种方法: show index from 表名;

第三种方法(信息比较简略): desc 表名;

6.删除索引的方法

  • 第一种方法-删除主键索引: alter table 表名 drop primary key;
  • 第二种方法-其他索引的删除: alter table 表名 drop index 索引名; 索引名就是show keys from 表名中的 Key_name 字段 mysql> alter table user10 drop index idx_name;
  • 第三种方法方法: drop index 索引名 on 表名 mysql> drop index name on user8;

7.索引创建原则

  1. 比较频繁作为查询条件的字段应该创建索引
  2. 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
  3. 更新非常频繁的字段不适合作创建索引
  4. 不会出现在where子句中的字段不该创建索引
相关推荐
oouy1 小时前
《Java泛型:给你的代码装上“快递分拣系统”,再也不会拆出一双鞋!》
后端
Python私教1 小时前
别再瞎折腾 LangChain 了:从 0 到 1 搭建 RAG 知识库的架构决策实录
后端
微学AI1 小时前
openGauss在AI时代的向量数据库应用实践与技术演进深度解析
后端
5***r9351 小时前
SQL实现md5加密方法
数据库·sql
laocooon5238578861 小时前
vue3 本文实现了一个Vue3折叠面板组件
开发语言·前端·javascript
随风飘的云1 小时前
redis的qps从100飙升到10000的全流程解决方案
后端
i***77801 小时前
mysql 迁移达梦数据库出现的 sql 语法问题 以及迁移方案
数据库·sql·mysql
5***E6851 小时前
mysql重置root密码(适用于5.7和8.0)
数据库·mysql·adb
用户345848285051 小时前
java除了AtomicInteger,还有哪些常用的原子类?
后端