MySQL - 页

MySQL - 页,第1张

页是 InnoDB 管理存储空间的最小单位。一个页的大小一般是 16 KB。InnoDB 有许多种页用于不同的作用。其中数据页则是用于存储数据。数据页存储的内容为:

其中 Infimum + supremum 以及 User Records 为页中存储数据的部分。其中 Infimum 表示页中的最小记录,而 supremum 表示页中的最大记录。这两个记录不存储实际的值,而仅仅表示开头以及结尾。User Records 部分按行存储数据。User Records 中的每一条记录格式为:

插入到页中的记录是按主键大小进行排序。利用其中的 next_record 可以查找到下一条记录。在不考虑索引的情况下,如果我们要寻找其中的某条记录可以通过遍历链表的方式进行查找。但是如果当页中的数据过多,o(n) 的时间复杂度明显不满足快速查找的需求。因此 InnoDB 在页中设计了页目录。页目录中有多个槽,其规则如下:

因此实际搜索时,可以利用槽进行二分搜索,将算法复杂度降到了 。这个结构有点类似于一个两层的跳跃表。

由于一个页中实际能存储的数据有限,因此记录会被分配到多个页进行存储。页与页之间有着双向链表的结构。

在 innodb 中使用 B+ 树作为索引。实际上索引在 mysql 中也是作为页进行管理的。例如:

索引页与数据页类似,只是索引页中一条记录只存在两列。分别是页对应的最我号,以及页的页编号。当然,一个 b+ 树肯定存在多个级别,因此实际上的存存储格式为:

这里可以看出索引页与数据页其实并没有太多的区别。只不过数据页中存储着真实的数据,而索引页只存储索引。这里也可以看出主键索引实际上是聚集索引,当查找到最终的数据页时是可以直接获得数据。

许多个页组成的空间之为页空间。每个表空间对应着一个真实的文件 表名.ibd。每一个独立表空间中又会分为多个区。每一个区实际上是 64 个连续的页组成。每256个区划又会分为一组。

为什么会提出区的概念呢?原因是查找数据的时候,在页与页之间会通过双向链表进行查找。如果两个页随机分配物理地址,则其之间的物理位置可能非常远。那么在查找的时候无疑会形成大量的随机 IO。降低磁盘的性能。因此,当表中数据过大的时候,以区为单位进行分配连续的磁盘空间,可以减少随机 IO 的数量。

表空间中还有段的概念,当我们利用索引进行查询的时候。很多时候实际上是利用 B+ 树的叶子节点进行范围扫描。但是如果将索引页和数据页都存放在一个区中,那么数据页不一定是连续的磁盘空间。因此当进行范围扫描的时候又会存在随机 IO 的情况。因此索引页和数据页实际上是存放在不同的区中。存放索引页的区的集合又成为一个段,当然非索引页存放的区的集合则为另一个段。

我们知道,磁盘的速度是远远小于内存的速度。因此 InnoDB 会将查询的页缓存在内存 Buffer Pool 中,以免每一次请求都从磁盘中获取,加快查询速度。当然,内存不可能无止尽的使用。因此 InnoDB维护了一个 free 链表。 free 链表指向 Buffer Pool 中可用的部分。

当页面进行修改之后,缓存的中的页页不会马上落盘,这样的页称为脏页。InnoDB 维护了一个 flush 链表指向了脏页。当 buffer 的空间不足时,InnoDB 会进行刷页 *** 作,将脏页写入到磁盘中,腾出内存空间供新的页缓存使用。

一般来说,数据有冷热之分。如果经常刷新热点数据到磁盘中,肯定不划算。因为热点数据经常被查询修改,当写入到磁盘中后又会很快读入到缓存中,做了很多无用功。因此 InnoDB 采用了 LRU 算法统计哪些是热点数据,哪些是非热点数据。每次刷盘时从首先 LRU 链表的尾部将热点数据刷入到磁盘中。

InnoDB 并不是采用最简单的链表,而是划分区域的链表。其设计的原因是,InnoDB 在某些时候会采取预读的 *** 作,将一个区的数据全部读入到内存中。这些数据就会出现在 LRU 链表的头部。如果这些预读的数据最终不能被查询,那么真正的热点数据反而被挤到了链表的尾部,这样一旦存在预读行为 LRU 链表的功能就丧失了。同样,当用户进行扫描全表的 *** 作时,大量的页也会被加载到缓存中将 Buffer 占满。因此 InnoDB 将 LRU 分为两个区域-热数据(young 区)以及冷数据(old 区)。

对于第一种情况,当页被缓存到 Buffer 时首先会被放在 old 区。如果该页后续被继续访问,则会被放到 young 区中。而如果该页后续没有被继续访问到,则会逐渐移动到 old 区尾部。

对于扫描全表的情况,扫描全表有一个特点。即页中的每一条数据都会被访问到,同一个页第一次访问到最后一次访问的间隔时间一定很短。因此 InnoDB 设计了一个策略,如果当一个页加载到内存中,并且该页在第一此访问与最后一次访问间隔相差小于 1s (默认值),则该页就不会被加入到 young 区中。因此这种方式可以避免全表扫描时对 LRU 链表的污染。

MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。这种并发控制的方法,主要应用在RC和RR隔离级别的事务当中,利用执行select *** 作时,访问记录版本链,使得不同事物的读写,写读可以并发执行,提高系统性能。

Innodb 有两个隐藏字段 trx_id(事务id)和roll_pointer(回滚指针)。

transaction id

innoDB里面每个事务有一个唯一的事务ID,叫作transaction id,它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。

roll_pointer

指向上一事务版本的指针。

版本链

是一个单链表结构,对于同一行数据,每一个事务对其进行更新的时候都会产生一个新的版本,就会存储在这个链表当中。

一个存储事务id的列表。

readview的几个参数:

m_ids:表示活跃事务id列表

min_trx_id:活跃事务中的最小事务id

max_trx_id:已创建的最大事务id

creator_trx_id:当前的事务id。

readview的生成时机:

RC隔离级别:每次读取数据前,都生成一个readview;

RR隔离级别:在第一次读取数据前,生成一个readview;

使用场景:

[ 创建事务节点 ] 当我创建一个新的事务需要读取一行数据, 我会查询活跃的事务列表假设我当前的事务id是200, 当前活跃的事务id没有我的200, 因此需要去拷贝一个最新的不活跃事务并在版本链最后插入一个新节点200mysql会去对比版本链和readView, 假设版本链数据为[1,50,100,150], 活跃列表为[100,150], 说明100和150都是未提交的活跃事务, 再向前一个节点50不在活跃事务列表说明事务50已经提交, 所以事务200拷贝事务50并插入版本链最后, 且将200追加到readView活跃列表的最后一个元素

[ 使用事务节点 ] 当我再次进行200号事务的查询或修改, 我需要读版本链的数据, 因为上一次 *** 作已经在版本链做了200号节点, 因此我读的数据都是200号节点的数据, 这样就隔离了其他未提交的事务我的全部增删查改都在200号版本链上进行

[ readView实现事务隔离级别 ]以上两点都是基于隔离级别"读已提交"来进行说明的当mysql设置为"可重复读"时, 不同事务仍然是保存在版本链的不同节点上, 只不过新的事务创建的时候拷贝了当下的readView列表, 只要新事物不提交就一直使用这个拷贝的活跃列表假设此时100号数据提交了, 我在新事务执行了select 会去查活跃列表发现100号事务还是未提交状态, 因此读取到的还是50号事务提交的记录。

原子性,一致性,隔离性,持久性。

未提交读(read uncommitted)、提交读(read committed)、可重复读(repeatable read)、序列化读(serializable)

方法如下:

1.头文件:

#include

#include

#include//这个是必需要包含的,下面对mysql的所有 *** 作函数,都出自这里

2.定义一个MYSQL变量:

MYSQLmysql;

这里MYSQL是一个用于连接MySql数据库的变量。

在后面对mysql数据库的 *** 作中,我们就用这个MYSQL变量作为句柄的。

3.定义数据库参数:

charhost[32]=”localhost”

charuser[32]=”username”

charpasswd[32]=”pwd”

chardbname[32]=”testdb”

4.数据库 *** 作

1).初始化数据库:

mysql_init(&mysql);

2).连接数据库:

mysql_real_connect(&mysql,host,user,passwd,dbname,0,NULL,0);

我们在 *** 作时,可以对以上的函数进行if测试,如果初始化或者连接出错,作出相应提示,以便调试。

5.对数据库的 *** 作:

Mysql_query(&mysql,“select*fromtestdbwherecondition”)

我们在实际 *** 作中,为了更方便的使用程序中的某些变量,我们将会用到一个函数:

intsprintf(char*str,constchar*format,?)

这个函数用来格式化我们的字符串,然后将变量按照给你的格式,赋给第一个参数。

我们使用这个方法方法可以很方便的使用我们的变量来对数据库进行 *** 作。例如我们将要进行数据库的查询 *** 作,我们就可以这样使用:

sprintf(sql,”select*fromtestdbwhereusername=‘%s’”,u_name)

然后使用mysql_query(&mysql,sql)进行查询。

MySQL是一个关系型数据库管理系统,由瑞典MySQLAB公司开发,属于Oracle旗下产品。MySQL是最流行的关系型数据库管理系统之一,在WEB应用方面,MySQL是最好的RDBMS(RelationalDatabaseManagementSystem,关系数据库管理系统)应用软件之一。

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。相比于线性表顺序结构, *** 作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/8359988.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-15
下一篇 2023-04-15

发表评论

登录后才能评论

评论列表(0条)

保存