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

《MySQL:MySQL索引特性》

索引理解

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

常见索引分为

  • 主键索引(primary key)
  • 唯一索引(unique)
  • 普通索引(index)
  • 全文索引(fultext)——用于解决文本数据中快速搜索特定关键词或短语的问题

认识磁盘

关于磁盘的基本了解在下面这篇文章中有提到。下面再简单说明一下。

【Linux】基础I/O -> 如何谈文件与文件系统?-CSDN博客

MySQL与存储

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

了解磁盘

再看看磁盘中一个盘面。

扇区。

数据库文件,本质就是保存在磁盘的盘片中。其实也就是扇区。数据库文件很大很多,一定需要占据多个扇区。

在使用Linux时,所看到的大部分(部分内存文件系统除外)目录或者文件,其实就是保存在硬盘当中的。

可以ls /var/lib/mysql查看自己mysql中的文件。

找到一个文件的全部,就是在磁盘找到所有保存文件的扇区。能够定位任何一个扇区,就能找到所有的扇区(因为查找方式是一样的)。

定位扇区

  • 柱面(磁道):多盘磁盘,每盘都是双面,大小完全相等。同半径的磁道整体上就构成了一个柱面。
  • 每个盘面都有一个磁头,磁头和盘面的对应关系是1对1。
  • 只需要知道,柱面(cylinder等价于磁道)、磁头(heads)、扇区(sector)对应的编号,就可以在磁盘上定位所要访问的扇区。这种磁盘数据定位方式叫做“CHS”定位法。但是,在实际系统软件使用的并不是CHS(硬件是),而是LBA,一种线性地址(可以想象成物理地址与虚拟地址)。系统讲LBA地址最后会转化成CHS,交给磁盘去进行数据读取。

现在能够在硬件层面定位任何一个基本数据块(扇区)了。那么在系统软件上,就直接按照扇区512字节(部分4096字节)进行IO交互吗?不是。

1、如果OS直接使用硬件提供的数据大小进行交互,那么系统IO代码就和硬件强相关,如果硬件发生变化,系统必须也跟着变化。

2、单次IO只有512字节,太小了。IO单位小,意味着读取同样的数据内容,需要访问磁盘的次数就多,效率就低。

3、类比文件系统,就是在磁盘的基本结构下建立的,文件系统读取基本单位就不是扇区,是数据块。

即,系统读取磁盘,是以块为单位的,基本单位是4KB。

磁盘随机访问与连续访问

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

连续访问:如果本次IO给出的扇区地址和上次IO结束的扇区地址是连续的,那磁头就能很快开始这次IO操作,不需要做较大的移动,这样的多个IO操作称为连续访问。

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

磁盘是通过机械运动进行寻址的,随机访问不需要过多的定位,故效率比较高。

MySQL与磁盘交互基本单位

MySQL作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高基本的IO效率,MySQL进行IO的基本单位是16KB(下面统一使用InnoDB存储引擎来作说明)。

163849byte/1024=16KB。

也就是说,磁盘这个硬件设备的基本单位是512字节,系统和磁盘进行交互是以块4KB为单位进行交互,而MySQL InnoDB引擎使用16KB和磁盘进行IO交互。

即,MySQL和磁盘进行数据交互的基本单位是16KB。这个基本数据单元,在MySQL这里叫做page(和系统的page不同,不要混肴)。

几点说明

1、MySQL中的数据文件,是以Page为单位保存在磁盘当中的。

2、MySQL的CURD操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。

3、只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中,CURD的操作都是在内存中进行的

4、所以在特定时间内,数据一定是磁盘中有内存中也有。后续后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page

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

6、为了更高的效率,一定要尽可能的减少系统和磁盘IO的次数

  1. 数据在Buffer Pool中进行CURD
  2. 在Buffer Pool发生变化的数据刷新到OS的文件缓冲区
  3. OS以16KB刷新到磁盘

磁盘数据搬到内存做数据处理,内存数据搬到磁盘做持久化。

索引理解

建立测试表。

插入多条记录。

查看查询结果。

发现,记录竟然是按照主键大小经过排序的!

为何MySQL与磁盘进行IO交互是以Page为单位?

为何MySQL和磁盘进行IO交互的时候,要采用Page的方案进行交互呢?用多少,加载多少不行吗?

  • 如上面的5条记录,如果MySQL要查找id=2的记录,第一次加载id=1,第二次加载id=2,一次一条记录,那么就需要2次IO。如果要找id=5,就需要5次IO。
  • 但是,如果着5条(或者更多)都被保存在一个Page中(16KB,能保存很多记录),那么第一次IO查找id=2时,整个Page都会被加载到MySQL的Buffer Pool中,这里完成了一次IO。但是往后如果在查找id=1,3,4,5等记录时,完全不需要进行IO了,而是直接在内存中进行了。所以,就在单Page里面,大大减少了IO的次数。

怎么保证,用户下一次找的数据,就在这个Page里面?

  • 不能严格保证,但是有很大概率,因为有局部性原理。

往往IO效率低下的最主要矛盾不是IO单次数据量的大小,而是IO的次数。

预加载机制可以有效利用局部性原理,减少IO次数,提高效率。

理解单个Page

MySQL内部,一定存在大量的Page,也就决定了MySQL必须要将多个同时存在的Page管理起来,要管理所有的MySQL内的Page,需要“先描述,再组织”。

所以,不要简单的将Page认为是一个内存块,Page内存也必须写入对应的管理信息!

struct page

{
        struct page * next;

        struct page * prev;

        char buffer[NUM]; //16KB

        // ...

};

将所有的page用“链表”的形式管理起来,在Buffer pool内部,对MySQL中的Page进行了一个建模!

不同的Page,在MySQL中都是16KB,使用prev和next构成双向链表。

因为有主键的问题,MySQL会默认按照主键给我们的数据进行排序,从上面的Page内数据记录可以看出,数据是有序且彼此关联的。

为什么MySQL在插入数据时要对其进行排序呢?按照正常顺序插入数据不好吗?

插入数据时排序的目的,就是优化查询的效率

页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询效率是必须的。

正是因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的,而且,如果运气好,可以提前结束查找过程的。

主要是为了方便建立目录。

理解多个Page

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

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

页目录

我们在看《C语言程序设计》这本书的时候,如果要看“指针章节”,找到该章节有两种做法。

1、从头逐页的向后翻,直到找到目标内容。

2、通过书提供的目录,发现“指针章节”在123页,那么我们便可以直接翻到234页。同时,查找目录的方案,可以顺序找,不过因为目录肯定少,所以可以快速提高定位。

本质上,书中的目录,是多花了纸张的(本来可以写更多的内容),但是却提高了查询的效率。

所以,目录,是一种“以空间换时间的做法”。

单页情况

针对上面的单页Page,是否也能引入目录呢?当然可以。

当前,在一个Page内部,就引入了目录。比如,要查找id=4的记录,之前必须线性遍历4次才能拿到结果。现在直接通过目录2[3],直接进行快速定位新的其实位置,提高了效率。

此时,就可以回答上面的问题了,为什么MySQL通过键值会自动排序?

可以很方便的引入目录。

多页情况

MySQL中每一页的大小只有16KB,单个Page大小固定,所以随着数据量不断增大,16KB不可能存下所有的数据,那么必定会有多个页来存储数据。

在单表数据不断被插入的情况下,MySQL会在容量不足的时候,自动开辟新的Page来存储新的数据,然后通过指针的方式,将所有的Page组织起来。

(注意:上面的图,是理想结构,要保证整体有序,那么新插入的数据,不一定会在新Page上面,这里仅仅做演示。)

这样,我们就可以通过多个Page遍历,Page内部通过目录来快速定位数据。可是,貌似这样也有效率问题,在Page之间,也是需要MySQL遍历的,遍历就意味着依旧需要进行大量的IO,将下一个Page加载到内存,进行线性检测。这样,有没有更好的办法进一步提升效率呢?

给Page也带上目录。

  1. 使用一个目录项来指向某一页,而这个目录项中存放的就是将要指向的页中存放的最小数据的键值。
  2. 和页内目录不同的地方在于,这种目录管理的级别是,而页内目录管理的级别是
  3. 其中,每个目录项的构成是:键值+指针。(图中没有画指向页的指针)

存在一个目录页来管理页目录,目录页中的目录项存放的数据就是指向的那一页中最小的数据。有数据,就可以通过比较,找到要访问的那个Page,进而通过指针,找到下一个Page。

目录页的本质也是页,普通页中存放的数据是用户数据,而目录页中存放的数据是普通页的地址和普通页最小数据的键值。

但是,每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,还是需要遍历啊。此时,可以再加目录页。

这就是B+树结构!至此,就给表user构建完了主键索引。

此时,随便找个一个id,查找的Page数一定减少了,也就意味着IO次数减少了,那么效率也就提高了。

  • Page分为目录页和数据页。目录页只存放下级Page的最小键值和地址。
  • 查找的时候,自顶向下找,磁盘只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了IO次数。

InnoDB在建立索引结构来管理数据的时候,其他数据结构为何不行?

  • 链表:线性遍历。
  • 二叉搜索树:退化问题,有可能退化成线性结构,线性遍历。
  • AVL&&红黑树:虽然平衡或近似平衡,但是毕竟是二叉结构,相比多阶B+,意味着整体树过高,都是自顶向下查找,层高越低,意味着系统与硬盘IO,加载到内存的Page越少,也就是IO次数越少,效率越高。
  • Hash:官方的索引实现方式,MySQL是支持Hash的,不过InnoDB和MyISAM不支持。Hash根据其算法特征,决定了查找时间也很快O(1),但是,在面对范围查找时就明显不行(尽管查找的数据相邻,但是依旧需要Hash查找)。
  • B树:?

InnoDB为何不用B树作为底层索引?

B树结构:

B+树结构:

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

为何选择B+?

1、非叶子节点不存储数据,意味着一个节点就可以存储更多的目录项,也就是可以管理更多的Page。所以,这棵树一定是“矮胖型”的树!查找的时候,途径的节点就少(每途径一个节点就意味着一个Page要加载到内存,要进行一次IO),找到目标数据只需要更少的Page,IO次数就少了,效率就高了。

2、叶子节点全部使用链表相连,更便于进行范围查找。

聚簇索引和非聚簇索引

MyISAM存储引擎:主键索引。

MyISAM引擎,同样使用B+树作为索引结果,叶子节点的data域存放的是数据记录的地址。下图为MyISAM的主键索引,Col1为主键。

MyISAM存储引擎的最大特点是,将索引Page和数据Page分离,也就是叶子节点没有数据,只有对应数据的地址。

而InnoDB存储引擎,是将索引Page和数据Page放在一起的。

像InnoDB这种,用户数据和索引数据在一起的索引方案,叫做聚簇索引。

像MyISAM这种,用户数据和索引数据分离的索引方案,叫做非聚簇索引。

MySQL除了默认会建立主键索引外,用户也有可能按照其他列信息建立索引,一般这种索引叫做辅助(普通)索引。

对于MyISAM,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可以重复。

下图就是基于MyISAM的Col2建立的索引,和主键索引没有差别。

InnoDB除了主键索引,用户也会建立辅助(普通)索引,以上表中的Col3建立对应的辅助索引如图。

InnoDB的非主键索引中叶子节点并没有数据,而只有对应记录的key值。

所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主键索引中检索获得记录。这种过程,叫做回表查询

为什么InnoDB针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?

原因是:太浪费空间了。

索引操作

    查询索引

    • 方式一:show keys from 表名
    • 方式二:show index from 表名
    • 方式三:desc 表名(显示出的信息简略)

    删除索引

    • 删除主键索引:alter table 表名 drop primary key
    • 删除其他索引:alter table 表名 drop index 索引名;索引名就是show keys from 表名中的Key_name字段
    • 删除其他索引:也可以drop index 索引名 on 表名

    创建主键索引

    • 方式一

    //创建表的时候,直接在字段名后指定primary key

    create table user1(id int primary key, name varchar(20));

    • 方式二

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

    create table user2(id int, name varchar(20), primary key(id));

    • 方式三

    create table user3(id int, name varchar(20));

    //创建表以后再添加主键索引

    alter table user3 add primary key(id);

    创建主键索引的操作和创建主键的操作是一样的,创建了一个主键,MySQL会默认给这个主键创建索引。

    主键索引特点

    • 一个表中,最多有一个主键索引,可以使用复合主键
    • 主键索引效率高
    • 创建主键索引的列,不能为null,不能重复
    • 主键索引的列基本上是int

    演示:

    创建主键索引。

    查询主键索引。

    删除主键索引。

    添加主键索引。

    创建唯一索引

    • 方式一

    //创建表的时候,直接在字段名后指定unique唯一属性

    create table user4(id int primary key, name varchar(20) unique);

    • 方式二

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

    create table user5(id int, name varchar(20), unique(name));

    • 方式三

    create table user6(id int, name varchar(20));

    //创建表以后再添加唯一索引

    alter table user6 add unique(name);

    唯一索引特点

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

    演示:

    创建唯一索引。

    查询唯一键索引。

    删除唯一键索引。

    查询唯一键索引。

    删除了唯一键索引,只剩下主键索引了。

    添加唯一键索引。

    删除唯一键索引。

    创建普通索引

    • 方式一

    //在表定义最后,指定某列为普通索引

    create table user8(id int primary key, name varchar(20), email varchar(30), index(name));

    • 方式二

    create table user9(id int primary key, name varchar(20), email varchar(30);

    //创建完表以后指定某列为普通索引

    alter table user9 add index(name);

    • 方式三

    create table user10(id int primary key, name varchar(20), email varchar(30);

    //创建一个索引名为 idx_name 的索引

    create index idx_name on user10(name);

    普通索引的特点

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

    演示:

    创建普通索引。

    查询普通索引。

    删除普通索引。

    添加普通索引。

    创建一个索引名为idx_name的索引。

    创建全文索引

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

    有如下articles表。

    查询有没有database数据。

    使用这种方式,虽然查到了数据,但是没有使用全文索引。

    可以使用explain工具看一下是否使用到索引。

    下面使用全文索引。

    使用explain分析这个sql语句。

    复合索引(了解)

    复合索引是将多个列组合在一起创建的索引,数据库系统会根据这些列的顺序来存储和排序数据,以加速基于这些列的查询操作。

    创建复合索引。

    查询复合索引。

    删除复合索引。

    工作原理

    复合索引会按照创建索引时指定的列顺序,对数据进行排序和存储。例如,若在列name和列email上创建了复合索引(name,email),MySQL首先会按照列name的值进行排序,当列name的值相同时,再按照列email的值进行排序。查询时,MySQL会根据查询条件从索引中快熟定位符合条件的数据。

    复合索引特点

    • 对于涉及多个列的查询条件,复合索引可以一次性定位到符合条件的数据,减少了MySQL的扫描范围。
    • 覆盖索引:如果查询的所有列都包含在复合索引中,MySQL可以直接从索引中获取所需数据,而无需再访问数据行,进一步提高查询效率

    最左匹配原则

    在使用复合索引时,查询条件必须从索引的最左边列开始,并且按照索引定义的列顺序依次使用。例如,复合索引(name,email),查询条件必须为where name='张三' and emial='1234@qq.com'。

    http://www.xdnf.cn/news/4575.html

    相关文章:

  • 解锁 Postgres 扩展日!与瀚高共探 C/Java 跨语言扩展技术的边界与未来
  • si551x时钟芯片linux下调试总结
  • 基于 SpringBoot + Vue 的校园管理系统设计与实现
  • STM32的看门狗
  • English of Root for May 7th
  • 工程师转型算法工程师 深入浅出理解transformer-手搓板
  • zst-2001 历年真题 知识产权
  • 端口安全配置
  • Docker+Kubernetes落地指南:从单机到集群的平滑迁移
  • 【大模型系列篇】Qwen3思考预算及思考模式切换实现原理探索
  • Qt 中基于 spdlog 的高效日志管理方案
  • nginx 上传文件,413 request entity too large
  • 计划评审技术PERT
  • Yii2.0 模型规则(rules)详解
  • STM32 CAN总线
  • Linux网络编程day6 下午去健身
  • MATLAB导出和导入Excel文件表格数据并处理
  • 大模型范式转移:解码深度学习新纪元
  • 【Day 21】HarmonyOS实战:从智慧医疗到工业物联网
  • 【FreeRTOS-消息队列】
  • PyQt5 实现自定义滑块,效果还不错
  • grpc到底是啥! ! !!
  • shell操作文件上传
  • 第3章 模拟法
  • SDC命令详解:使用get_ports命令进行查询
  • 浅谈广告投放从业者底层思维逻辑
  • C语言 指针(8)
  • 第七章 模板制作工具
  • ubuntu 挂载硬盘
  • 当“信任”遇上“安全”:如何用Curtain Logtrace记录文件操作活动 守护团队与数据的双重底线?