MySQL数据库操作篇5(索引的原理及用法)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: MySQL数据库操作篇5(索引的原理及用法)

索引是MySQL中的一大利器,这篇文章旨在讲清楚什么是索引,索引的底层实现原理是什么,使用索引能带来哪些好处,以及索引如何使用较好

此文章会用到前面提到过的一些术语,比如元组,一个元组就指表中的一行数据


什么是索引

通过前面的学习,我们了解过,在数据库中创建一个表,我们就可以往表中插入数据,然后需要数据的时候在表中查找就行了,普通用户可以直接使用数据库设计者提供的功能,不用关心如何实现查找,作为程序开发人员,我们需要关心这个问题。前面我们讲了很多MySQL中查找数据的方法,但是这些查找总得有个底层的算法实现吧,我们最容易想到的查找算法是遍历,把表中所有的数据都遍历一遍,不就可以查到想要的数据了嘛

对于小批量的数据,确实可以这么做,但是数据库就是存放大批量数据的,一张表中可能有几万行数据,如果这个时候还使用遍历查找,那么查找效率会非常的低,为了解决这个问题,数据库设计者就提出了索引这个概念,所以索引就是来解决数据查找效率的问题的

索引的实现是靠比较优秀的查找算法,不用加内存,不用改程序,不用调sql,只要执行

正确的 create index ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价值,在于提高一个海量数据的检索速度


索引的底层实现原理

搞明白索引是干嘛的,有什么用之后,我们接着来看,如何实现索引。为了更透彻的理解索引,此篇文章要从硬件和软件两个方面同时介绍。

查找过程本质就是在做排除,把无用数据排除在外,高效率的查找就是在较短的时间内,排除掉尽可能多的数据,要做到这一点,就需要我们人为把数据分配整齐,想一想我们按照类别给数据进行划分,只需要去待查找数据的所属类别里查找即可,此时其他所有的类别就都被排除掉了。

例如我们书本中常见的目录就是一种高效的查询方式,书本根据内容的不同,划分为几个章节,目录中记录着这些章节的具体页码,当想要查找某个信息时,只需要去该信息所属的那个章节即可,而其他的章节在这个查找过程中就被排除掉了,这样就能很快查到信息,比遍历一本书要快速很多,索引的实现原理就类似于此

我们先来了解MySQL与磁盘的交互,关于磁盘的内容,笔者在Linux的IO篇里详细的讲解了,如果不明白磁盘工作原理的,请移步查看。操作系统每次与磁盘IO的大小是4kb。大家可以想想,每次4kb字节的IO,对于操作系统来说可能就够用了,但是对于数据库来说,小了一些,因为数据库中存放的数据是相当大的,因此数据库需要与磁盘一次IO更多,根据实践,数据库一次与磁盘进行16kb的IO是比较好的

但是之前我们了解过,只有操作系统才能够和硬件进行交互,而数据库本质是属于应用层的,所以它没有权利和磁盘直接进行IO,它与磁盘的IO操作要依托操作系统来完成

MySQL创建表本质就是创建一个文件,所以MySQL与操作系统进行交互时,也是通过文件来进行的,如上图,MySQL会申请一个缓冲区用来存取与OS进行IO操作的数据

这个过程是麻烦的,我们在逻辑上可以忽略OS这个角色,认为MySQL直接与磁盘进行IO操作,且一次16kb大小,也就是说MySQL 和磁盘进行数据交互的基本单位是 16KB 。这个基本数据单元,在 MySQL 这里叫做page(注意和系统的page区分)

为什么要提到page呢?因为MySQL 中的数据文件,是以page为单位保存在磁盘当中的,一张表,也就是一个数据文件,是由一个或者多个page构成的。MySQL中的数据文件以page为单位,势必会造成存储空间不能完全利用的情况,为什么要以page为单位,而不是存多少用多少呢?这就涉及到IO的效率了,我们都知道与磁盘的IO效率是很低的,如果一次以表中一个元组(表中的一行)的大小为单位,那么在查找时,有100个元组就要进行100次IO操作,而使用page为单位,一次IO就可以把100个元组的数据都加载进内存

各个page之间通过双向链表连接起来,每个page里包含着所存的数据内容,同时也有指向前一个和后一个page的指针

大家可以想一想,如果一个page里存满了数据,那么在一个page里查找数据,如果使用线性遍历的方法进行查找的话,效率会很低,还记得我们前面的提到过比较高效的目录形式吗?为了提高在一个page中遍历的效率,我们可以将page中的数据先按顺序进行一个排序,排完序之后再将其按照序号划分成目录,就拿上面这个page为例来进行划分,划分结果如下图

这里数据量比较小,不过不影响我们理解这种思想,假设查找id为4的人是谁,大致过程为在目录区中使用二分法查找id为4属于哪个目录,图中只有两个目录,第二个目录标记的最大id值为3,因为4>3,我们就从目录二进入到id为3的数据,然后向下移动一位,就找到了id为4的数据,随后返回结果即可。这里数据量很小,效果并不明显,随着数据量增大,这种查询算法的优势愈加明显

当然,我们前面说过一张表可能有一个page或者多个page构成,为了展示整张表的全貌,这里假设一个page只能存放五个元组,MySQL中一张表的存储结构大致如下(为了作图方便,数据中人物的名字是重复的,只关注数据的id序号即可)

理解完上面的内容,接下来还得思考一些问题,数据库就是用来存放数据的,那么大量的数据势必会造成page数目的剧增。我们前面解决的是如何在一个page内部高效的查找数据,当page数目变得非常多的时候,我们就得考虑,如何在page和page之间进行高效的查找,以上图为例,假设我们要查找id值为14的全部数据,那么会先加载第一个page,通过内部的查询后发现不在该page里,于是加载第二个page,通过内部的查询后发现不在该page里,于是加载第三个page,终于在内部查询到了,返回查询结果

将每一个page加载到内存,进行线性检测。这样就显得我们之前的page内部的目录,有点杯水车薪了,那么如何解决呢?解决方案,其实就是我们之前的思路,给page也带上目录

使用一个目录项来指向某一页,这个目录项存放的是将要指向的页中存放的最小数据的键值

和页内目录不同的地方在于,这种目录管理的级别是页,而页内目录管理的级别是行

其中,每个目录项的构成是:键值+指针

大致过程如下图

将page也放到一个目录中,这样就不用怕随着page数目的增多,查询效率低下的问题了

在上图中最下层,存放着真实数据的页,我们称之为普通页,而第二层,标志普通页的目录的页我们称之为目录页,目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址

虽然目录页改善了普通页的查找效率,但我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?不用担心,我们还可以给目录页再加一层目录页

大致操作如下图

过这三层目录结构的搭配使用,查找数据的效率将会变得非常之高,这个结构其实就是B+树, 也是索引的底层实现原理,不过这并不意味着,所有的数据库的索引都是靠B+树来实现的,这取决于数据库使用的存储引擎,我们常用的InnoDB使用的就是B+树来实现,也有的存储引擎使用B树,使用哈希等结构,根据不同的应用场景来选择合适的


B树 VS B+树

B树和B+树都是常用的数据结构,也是MySQL数据库都支持的结构形式,不同的存储引擎所使用的数据结构都不尽相同,这里简单展示一下B树和B+树是如何在数据库中应用的,以及两者之间的区别,关于这两种数据结构如何创建,笔者将会在数据结构栏里详谈

下图是B树的结构图

下图是B+树的结构图


聚簇索引 vs 非聚簇索引

在使用索引时,我们可能经常会听到聚簇索引和非聚簇索引这些概念,这些具体指代的是什么呢?如下图,在这个B+树索引中,叶子节点存放的并不是数据,而是数据在内存上的指针,通过这个指针就可以找到数据,大家可以发现,在这个过程中,数据就和索引结构分离开了,像这样的索引方案,我们就称之为非聚簇索引

在下面这张图中,B+树的叶子节点存放的就是数据本身,而不是数据的指针,像这样索引结构和数据放在一起的我们就称之为聚簇索引

InnoDB是采用聚簇索引,MyISAM采用的是非聚簇索引,不过这两个存储引擎的索引结构均是B+树,你可能好奇,为什么要分成聚簇索引和非聚簇索引呢?下面就是两者各自的优缺点

聚簇索引的优点:

 聚簇索引将数据行物理上按照索引的顺序存储在磁盘上,因此对于按索引列进行范围查询或排序的操作,聚簇索引可以提供更高的查询性能

 聚簇索引可以减少磁盘I/O操作,因为相邻索引键值的数据行通常在磁盘上也是相邻存储的,减少了磁盘读取的次数

 聚簇索引可以节省存储空间,因为数据行按照索引顺序存储,可以减少数据行中重复的索引列的存储

聚簇索引的缺点:

 聚簇索引的维护代价相对较高。当插入或删除数据时,因为数据行的物理存储顺序可能需要进行调整,所以维护聚簇索引的代价较高

 聚簇索引一般只能创建在唯一键或主键上,对于非唯一键的查询性能可能较差

非聚簇索引的优点:

 非聚簇索引的维护成本相对较低。当插入或删除数据时,不需要调整数据行的物理存储顺序,因此维护非聚簇索引的代价较低

 非聚簇索引可以创建在任意列上,不限唯一键或主键。可以提供对非唯一键的高效查询

非聚簇索引的缺点:

 非聚簇索引的查询性能可能较低。因为数据行在磁盘上的物理存储顺序与索引顺序不一定一致,对于范围查询或排序操作,可能需要进行更多的磁盘I/O操作

 非聚簇索引通常需要占用更多的存储空间,因为索引结构中需要存储索引列的值和指向数据行的指针。 根据具体的应用场景和查询需求,选择合适的索引类型可以提高数据库的查询性能。聚簇索引适用于经常进行范围查询或排序的列,而非聚簇索引适用于非唯一键或不经常进行范围查询的列。


需要大家注意的是,聚簇索引一定要有唯一键索引,也就是主键索引,如果自己没有设置,MySQL会默认设置,除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做辅助(普通)索引,关键点来了,普通索引的叶子节点可不像主键索引那样存放着数据,而是存放着对应的主键值,拿到这个主键值后,还要回过头根据这个主键值,再去查一遍主键索引,最终找到数据,这个过程叫做回表查询,这也是为什么强调聚簇索引一定要有主键索引的原因。为什么不把普通索引的叶子节点也放上数据呢?因为主键所引上已经存放数据了,再存放一次就会造成数据的重复,空间的极大浪费


索引的相关操作

了解完索引是什么,以及索引的底层实现原理之后,我们接下来进入实操环节,看看如何在MySQL中创建和使用索引,在创建数据库时要根据未来的需求来确定采用哪种存储引擎,默认情况下都是采用InnoDB存储引擎

1.创建主键索引

在InnoDB中会默认根据主键创建主键索引,故而只需要指明主键即可

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

create table test(id int primary key, name varchar(30));

在InnoDB中会默认根据主键创建一个主键索引

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

create table test(id int, name varchar(30), primary key(id));

3.create table test(id int, name varchar(30));

-- 创建表以后再添加主键

alter table test add primary key(id);

主键索引的特点:

一个表中,最多有一个主键索引,当然可以使符合主键

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

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

主键索引列的数据类型基本上是int,有利于数据内容的排序和划分


2.创建唯一键索引

在InnoDB中会默认根据唯一键创建唯一键索引,故而只需要指明唯一键即可

1. -- 在表定义时,在某列后直接指定unique唯一属性。

create table test(id int primary key, name varchar(30) unique);

2.-- 创建表时,在表的后面指定某列或某几列为unique

create table test(id int primary key, name varchar(30), unique(name));

3.create table test(id int primary key, name varchar(30));

-- 创建表以后再添加唯一键

alter table test add unique(name);

唯一索引的特点:

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

查询效率高

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

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


3.创建普通索引

1.create table test(id int primary key,

name varchar(20),

email varchar(30),

index(name) --在表的定义最后,指定某列为索引

);

2.create table test(id int primary key,

name varchar(20),

email varchar(30),

);

alter table user9 add index(name); --创建完表以后指定某列为普通索引

3.create table test(id int primary key,

name varchar(20),

email varchar(30),

);

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

create index idx_name on test(name);

普通索引的特点:

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

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


索引的查询

第一种方法: show keys from 表名
第二种方法: show index from 表名;
第三种方法(信息比较简略):desc 表名;


删除索引

第一种方法-删除主键索引: alter table 表名 drop primary key;

第二种方法-其他索引的删除: alter table 表名 drop index 索引名;

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

第三种方法方法: drop index 索引名 on 表名


索引创建原则

比较频繁作为查询条件的字段应该创建索引
唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
更新非常频繁的字段不适合作创建索引
不会出现在where子句中的字段不该创建索引

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1天前
|
存储 关系型数据库 MySQL
MySQL bit类型增加索引后查询结果不正确案例浅析
【8月更文挑战第17天】在MySQL中,`BIT`类型字段在添加索引后可能出现查询结果异常。表现为查询结果与预期不符,如返回错误记录或遗漏部分数据。原因包括索引使用不当、数据存储及比较问题,以及索引创建时未充分考虑`BIT`特性。解决方法涉及正确运用索引、理解`BIT`的存储和比较机制,以及合理创建索引以覆盖各种查询条件。通过`EXPLAIN`分析执行计划可帮助诊断和优化查询。
|
4天前
|
SQL 存储 关系型数据库
mysql加索引真的会锁表吗?揭秘背后的技术细节与规避策略
【8月更文挑战第16天】在数据库管理中,添加索引能大幅提升查询效率。MySQL执行此操作时的锁定行为常引起关注。文章详细解析MySQL中索引添加时的锁定机制及其原理。不同存储引擎及SQL语句影响锁定策略:MyISAM需全表锁定;InnoDB提供更灵活选项,如使用`ALTER TABLE... LOCK=NONE`可在加索引时允许读写访问,尽管可能延长索引构建时间。自MySQL 5.6起,在线DDL技术可进一步减少锁定时间,通过`ALGORITHM=INPLACE`和`LOCK=NONE`实现近乎无锁的表结构变更。合理配置这些选项有助于最小化对业务的影响并保持数据库高效运行。
15 4
|
3天前
|
存储 关系型数据库 MySQL
mysql中的left join、right join 、inner join的详细用法
【8月更文挑战第16天】在MySQL中,`INNER JOIN`、`LEFT JOIN`与`RIGHT JOIN`用于连接多表。`INNER JOIN`仅返回两表中匹配的行;`LEFT JOIN`保证左表所有行出现于结果中,右表无匹配时以NULL填充;`RIGHT JOIN`则相反,保证右表所有行出现于结果中。例如,查询学生及其成绩时,`INNER JOIN`仅显示有成绩的学生;`LEFT JOIN`显示所有学生及他们对应的成绩,无成绩者成绩列为空;`RIGHT JOIN`显示所有成绩及对应学生信息,无学生信息的成绩条目则为空。
|
4天前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
4天前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
6天前
|
缓存 NoSQL Redis
一天五道Java面试题----第九天(简述MySQL中索引类型对数据库的性能的影响--------->缓存雪崩、缓存穿透、缓存击穿)
这篇文章是关于Java面试中可能会遇到的五个问题,包括MySQL索引类型及其对数据库性能的影响、Redis的RDB和AOF持久化机制、Redis的过期键删除策略、Redis的单线程模型为何高效,以及缓存雪崩、缓存穿透和缓存击穿的概念及其解决方案。
|
2天前
|
canal 关系型数据库 MySQL
"揭秘阿里数据同步黑科技Canal:从原理到实战,手把手教你玩转MySQL数据秒级同步,让你的数据处理能力瞬间飙升,成为技术界的新晋网红!"
【8月更文挑战第18天】Canal是一款由阿里巴巴开源的高性能数据同步系统,它通过解析MySQL的增量日志(Binlog),提供低延迟、可靠的数据订阅和消费功能。Canal模拟MySQL Slave与Master间的交互协议来接收并解析Binary Log,支持数据的增量同步。配置简单直观,包括Server和Instance两层配置。在实战中,Canal可用于数据库镜像、实时备份等多种场景,通过集成Canal Client可实现数据的消费和处理,如更新缓存或写入消息队列。
28 0
|
4天前
|
存储 NoSQL 关系型数据库
Web中的数据库:原理、应用与代码实现
Web中的数据库:原理、应用与代码实现
|
6天前
|
存储 安全 数据库
数据库系统原理课程设计
这篇文章是关于数据库系统原理课程设计的,主题为图书借阅管理系统,涵盖了系统需求分析、数据库设计(包括概念结构、逻辑结构、规范化、物理设计以及安全性和维护设计)、系统详细设计(视图、存储过程和触发器设计),以及安全性设计,并强调了设计仅供参考,鼓励自主学习。
|
14天前
|
缓存 关系型数据库 MySQL
MySQL调优秘籍曝光!从索引到事务,全方位解锁高可用秘诀,让你的数据库性能飞起来!
【8月更文挑战第6天】MySQL是顶级关系型数据库之一,其性能直接影响应用的高可用性与用户体验。本文聚焦MySQL的高性能调优,从索引设计到事务管理,逐一解析。介绍如何构建高效索引,如联合索引`CREATE INDEX idx_order_customer ON orders(order_id, customer_id);`,以及索引覆盖查询等技术。
39 0