mysql系列
前言
在mysql数据库中,索引是一种特殊的数据结构,它与表中数据关联,就像书籍的目录与正文的关系——目录通过章节标题和页码快速定位内容,而索引则通过存储数据的关键列值及其对应物理位置,帮助数据库快速定位目标数据。
本篇文章以主流的innodb引擎为例,展开介绍
案例
在mysql中操作存储大量数据的表时,我们往往会面临处理数据慢,性能低下等问题,这时只需要建立索引就可以将这种问题优化。
从操作执行时间不难看出,建立索引给我们操作带来的巨大提升。
在学习索引是如何优化mysql的性能之前,需要先知道mysql为什么存在性能方面的问题。
一、认识mysql与磁盘
1.1 mysql与存储
mysql 给用户提供数据存储服务的,早在之前我就介绍过,mysql使用的库、表对数据存储,在linux下都表现为特殊结构的文件,要想对数据进行持久化保存,这些文件最终都要存储在磁盘中,所有mysql下数据存储在磁盘这个外设当中,相比于计算机其他电子元件,磁盘效率是比较低的,在加上io本身的特征,可以知道,如何提交效率,是 mysql 的一个重要话题。
linux文件系统这篇文章中详细介绍了系统对磁盘的访问,强烈建议看一下,这里就不介绍了
1.2 mysql 与磁盘交互基本单位
mysql
作为一款应用软件,可以想象成一种特殊的文件系统,它有着更多的io
需求,而io
操作会大大影响执行效率,所以,为了提高基本的io
效率, mysql
进行io
的基本单位是 16kb
。
磁盘这个硬件设备的基本单位是
512
字节,操作系统在和磁盘交互时以4kb
为单位,而mysql innodb
引擎使用16kb
和内存进行io
交互。所以可以理解为,mysql
和磁盘进行数据交互的基本单位是 16kb 。这个基本数据单元,在mysql
这里叫做page
(注意和系统的page区分)。
show global status like 'innodb_page_size';
使用这个sql语句可以查找引擎页大小
。
二、 mysql 数据交互核心:bufferpool 与 io 优化机制
通过前文介绍可知,mysql 中的数据文件在磁盘上以 page
(16kb) 为单位存储。当执行 curd
(增删改查)操作时,mysql 需先通过计算定位目标数据的位置——这一过程依赖 cpu 参与,而 cpu 只能直接操作内存数据。因此,数据必须先从磁盘临时加载到内存中,形成“磁盘一份、内存一份”的临时状态。待内存中的数据操作完成后,再通过特定策略将更新同步回磁盘,这一过程即涉及磁盘与内存的交互(io
),而 io
的基本单位正是 page
。
为高效管理内存中的数据、减少频繁的磁盘 io,mysql 服务器在启动时会在内存中申请一块专用的大内存区域,称为 bufferpool
(缓冲池)。它的核心作用是:
- 缓存热点数据:将频繁访问的
page
临时存储在内存中,避免每次操作都直接读写磁盘(局部性原理:当你对某一块数据操作时,你的下一次操将有很大概率,会使用后面的数据)。 - 优化 io 效率:所有数据操作先在
bufferpool
中完成,操作完成后同步到磁盘,大幅减少磁盘 io 次数(不可以操作一行,就获取一行)。
因此,减少系统与磁盘的 io 次数是提升 mysql 效率的核心原则,而 bufferpool
正是实现这一目标的关键机制——它通过内存缓存降低了磁盘 io 对性能的影响。
三、索引的理解
3.1 测试案例
建立测试表 create table if not exists user ( id int primary key, --一定要添加主键哦,只有这样才会默认生成主键索引 age int not null, name varchar(16) not null ); 插入多条记录,注意此处数据的主键顺序 insert into user (id, age, name) values(3, 18, '杨过'); insert into user (id, age, name) values(4, 16, '小龙女'); insert into user (id, age, name) values(2, 26, '黄蓉'); insert into user (id, age, name) values(5, 36, '郭靖'); insert into user (id, age, name) values(1, 56, '欧阳锋');
可以发现mysql会将插入的数据默认变为有序,那么这样做有什么好处呢?
排序插入是为了优化查询效率
具体形式后面介绍
3.2 page
我们目前可以简单理解一个独立表文件是由一个或者多个page
构成的,那么这个表该如何管理这些page
呢?
不同的 page
,在 mysql
中,都是 16kb
,使用 prev
和 next
构成双向链表(像这种结构在学习linux时,我们经常遇到),因为有主键的问题, mysql
会默认按照主键给我们的数据进行排序,从上面的page内数据记录可以看出,数据是有序且彼此关联的。
页内部存放数据的模块,实质上是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询的效率是必须的,正是因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的(这一点在后面感受)
通过页模式,mysql 查询时会将一整页数据(16kb)加载到内存,以此减少硬盘 io 次数、提升性能。
但页模式内部采用链表结构,本质上需通过逐条数据比较定位目标。若表数据量大,且目标数据位于最后一个 page 的最后一条,仍需遍历全表,导致查找速度过慢。
3.3 页目录
在课本中查找知识点时,我们会选择优先查看目录,找到具体的页,再从页中查找知识点,这样的查找效率要比从头开始找,高效的多。在这里每一个独立的page或每一行数据,都可以视为“页”,而我们要做的是,给这些“页”添加属于他们的目录。
page内部:
现在,要在一个page内部,查找id=3记录,直接通过目录2[3],直接进行定位新的起始位置,提高了效率。现在我们可以再次正式回答上面的问题了,为何通过键值 mysql 会自动排序?可以很方便引入目录,提高查找效率
图中是为了迎合上面的数据,在实际情况下目录间的区间是很大的,在进行目录查找时,一次查找可以pass掉很多数据。
多page情况:单表数据不断被插入的情况下, mysql 会在容量不足的时候,自动开辟新的page来保存新的数据,然后通过指针的方式,将所有的page组织起来。
上面的方法帮我们提高了表内部遍历数据的效率,但是仍需要将每个page都,加载值内存中,为了进一步减少io
操作,我们采用页目录的方式
依照这个思路,我们还可以对目录页再次添加目录管理,现在可以得出结论:
- page分为目录页和数据页。目录页只放各个下级page的最小键值。
- 查找的时候,自定向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了io次数
这个结构最终就是一颗b+树,整个过程中我们所要io的次数,就是整个结构数的高度
目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。
3.3 对比其他结构
innodb 在建立索引结构来管理数据的时候,其他数据结构为何不行?
- 链表?线性遍历
- 二叉搜索树?退化问题,可能退化成为线性结构
- avl &&红黑树?虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶b+树,意味着树整体过高,大家都是自顶向下找,层高越低,意味着系统与硬盘更少的io page交互。
- hash?官方的索引实现方式中, mysql 是支持hash的,不过 innodb 和 myisam 并不支持hash跟进其算法特征,决定了虽然有时候也很快(o(1)),不过,在面对范围查找就明显不行。
b树?最值得比较的是 innodb 为何不用b树作为底层索引?
b树节点,既有数据,又有page指针,而b+,只有叶子节点有数据,其他目录页,只有键值和page指针b+,叶子节点,全部相连,而b没有,为何选择b+
节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以io操作次数更少。叶子节点相连,更便于进行范围查找
具体结构特征,你可以搜点图片理解
四、聚簇索引 vs 非聚簇索引
myisam
引擎同样使用b+树作为索引结果,叶节点的data域存放的是数据记录的地址。下图为myisam
表的主索引,col1
为主键
其中,myisam
最大的特点是,将索引page
和数据page
分离,也就是叶子节点没有数据,只有对应数据的地址相较于innodb
索引,innodb
是将索引和数据放在一起的。现在我们就可以回答,第一篇文章遗留的问题了
innodb引擎 create table itest( id int primary key, name varchar(11) not null )engine=innodb; myisam引擎 create table mtest( id int primary key, name varchar(11) not null )engine=myisam;
myisam
这种用户数据与索引数据分离的索引方案,叫做非聚簇索引,innodb
这种用户数据与索引数据在一起索引方案,叫做聚簇索引。
mysql 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做辅助(普通)索引。对于myisam ,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复。
下图就是基于 myisam 的 col2 建立的索引,和主键索引没有差别
同时我们以上表中的 col3 建立对应的辅助索引如下图:
myisam 的非主键索引中叶子节点并没有数据,而只有对应记录的key值。
所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询为何myisam 针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?原因就是太浪费空间了。
那么普通索引为什么要存在呢?
当我们以符合间进行索引时如:(姓名,qq),只知道第一个键值,需要查找第二个键值,我们就可以直接查找:
对于复合索引,匹配原则是,从做到右的,也就是说,我们只需要知道姓名,就可以得到qq,这种普通索引方式,要比主键索引更快。
五、索引操作
5.1 创建主键索引
// 在创建表的时候,直接在字段名后指定 primary key create table user1(id int primary key, name varchar(30)); //在创建表的最后,指定某列或某几列为主键索引 create table user2(id int, name varchar(30), primary key(id)); //创建表以后再添加主键 create table user3(id int, name varchar(30)); alter table user3 add primary key(id);
主键索引的特点:
- 一个表中,最多有一个主键索引,当然可以使符合主键
- 主键索引的效率高(主键不重复)
- 创建主键索引的列,它的值不能为null,且不能重复
- 主键索引的列基本上是int
这些特点,在介绍主键时都说过,只是当时没有提出索引的概念
5.2 唯一索引的创建
//在表定义时,在某列后直接指定unique唯一属性。 create table user4(id int primary key, name varchar(30) unique); //创建表时,在表的后面指定某列或某几列为unique create table user5(id int primary key, name varchar(30), unique(name)); //创建表以后再添加 create table user6(id int primary key, name varchar(30)); alter table user6 add unique(name);
唯一索引的特点:
- 一个表中,可以有多个唯一索引
- 查询效率高
- 如果在某一列建立唯一索引,必须保证这列不能有重复数据
- 如果一个唯一索引上指定not null,等价于主键索引
5.3 普通索引的创建
//在表的定义最后,指定某列为索引 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); //创建一个索引名为 idx_name 的索引 create table user10(id int primary key, name varchar(20), email varchar(30)); create index idx_name on user10(name);
普通索引的特点:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
- 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
5.4 查询索引
show keys from 表名; show index from 表名;
5.5 删除索引
//删除主键索引 alter table 表名 drop primary key; //其他索引的删除 alter table 表名 drop index 索引名; drop index 索引名 on 表名
余下指令你自己测试吧
到此这篇关于mysql 索引结构、对比与操作实践详细攻略的文章就介绍到这了,更多相关mysql 索引结构内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论