MYSQL-7.内存

本文详细介绍了MySQL内存的四个关键部分:工作组件(包括数据库连接池和线程池)、线程本地内存(如堆栈、排序缓冲区等)、mysql共享内存(如KeyBuffer、QueryCache等)以及存储引擎缓冲区(如InnoDB的BufferPool)。讨论了内存结构的优点与缺点,以及InnoDB引擎的缓冲策略和内存管理机制。
摘要由CSDN通过智能技术生成

内存

Mysql的内存结构

  1. 大体可分为四个板块:mysql工作组件、线程本地内存、mysql共享内存、存储引擎缓冲区;

在这里插入图片描述

Mysql server工作组件

  1. 对应着mysql架构图中的组件层:

    在这里插入图片描述

  2. Mysql在启动时,会将这些工作组件初始化到内存中;

  3. 数据库连接池:存储数据库内部的连接对象,这些连接对象中包含了客户端连接信息(客户端IP、登陆的用户、所连接的DB等),同时还会在内部绑定一个工作线程,所以也可以将其理解为线程池;mysql复用连接的本质是复用线程,当出现一个新的客户端连接时,会根据客户端信息为其创建连接对象,然后再服用线程池中的空闲线程;

工作线程的本地内存:

  1. 线程之间相互独立,Mysql在创建每条线程时,都会为其分配这些本地内存;
    在这里插入图片描述

    • thread_stack:线程堆栈,主要用于暂时存储运行的SQL语句及运算数据;
    • sort_buffer:排序缓冲区,执行排序SQL时,用于存放排序后数据的临时缓冲区;
    • join_buffer:连接缓冲区,做连表查询时,存放符合连表查询条件数据的临时缓冲区;
    • read_buffer:顺序读缓冲区,MySQL磁盘IO一次读一页数据,存放顺序IO的数据的临时缓冲区;
    • read_rnd_buffer:随机读缓冲区,当基于无序字段查询数据时,这里存放随机读到的数据;
    • net_buffer:网络连接缓冲区,这里主要是存放当前线程对应的客户端连接信息;
    • tmp_table:内存临时表,当SQL中用到了临时表时,这里存放临时表的结构及数据;
    • bulk_insert_bufferMyISAM批量插入缓冲区,批量insert时,存放临时数据的缓冲区;
    • bin_log_bufferbin-log日志缓冲区;
  2. 存放的信息大部分是一条信息在执行SQL时产生的临时数据,或者是线程独有数据;

Mysql共享内存区域:

  1. 结构图
    在这里插入图片描述

    • Key Buffer:MyISAM表的索引缓冲区,提升MyISAM表的索引读写速度;
    • Query Cache:查询缓存区,缓冲SQL的查询结果,提升热点SQL的数据检索效率;
    • Thread Cache:线程缓存区,存放工作线程运行期间,一些需要被共享的临时数据;
    • Table Cache:表数据文件的文件描述符缓存;当需要对某张表进行操作时,不需要将整个磁盘检索一遍,而可以查找缓存,如何根据其上面记录的地址,去读取操作表数据,提升数据表的打开效率;
    • Table Definition Cache:表结构文件的文件描述符缓存,提升结构表的打开效率;
  2. qureyCahce查询缓存:利用热点探测技术,对于一些频繁执行的查询SQL,直接将结果缓存在内存中,之后再使用相同sql(以sql的哈希值来作为Key)来查询相同数据时,就无需走磁盘,而是直接从查询缓存中获取数据并返回;

    1. 缺点,也是Mysql8.X版本移除查询缓存的原因:
      • 缓存命中率低;
      • 内存占用率高;
      • 增加了查询步骤;
      • 维护成本不下,需要LRU算法淘汰缓存,同时写操作时都需要清空缓存中对应的数据;

存储引擎缓冲区

  1. 虽然MySQL是基于磁盘存储数据的,但每一次操作都需要进行磁盘IO,这样会导致资源开销极大,同时性能也极低,因此几乎所有引擎都在内存中设计了一个缓冲池,用来提升数据库整体的读写性能;

InnoDB的Buffer Poll

结构

结构图

在这里插入图片描述

  1. Data Page:写入缓冲区,主要用来缓冲磁盘的表数据,将写操作转移到内存进行。
  2. Index Page:索引缓冲页,对于所有已创建的索引根节点,都会放入到内存,提升索引效率。
  3. Lock Space:锁空间,主要是存放所有创建出的锁对象。
  4. Dict Info:数据字典,主要用来存储MySQL-InnoDB引擎自带的系统表。
  5. redo_log_bufferredo-log缓冲区,存放写SQL执行时写入的redo记录。
  6. undo_log_bufferundo-log缓冲区,存放写SQL执行时写入的undo记录。
  7. Adaptivity Hash:自适应哈希索引,InnoDB会为热点索引页,创建相应的哈希索引。
  8. Insert Buffer:写入缓冲区,对于insert的数据,会先放在这里,然后定期刷写磁盘。
  9. Lru List:内存淘汰页列表,对于整个缓冲池的内存管理列表。
  10. Free List:空闲内存列表,这里面记录着目前未被使用的内存页。
  11. Flush List:脏页内存列表,这里主要记录未落盘的数据。
内存大小
  1. 可以通过show global variables like "%innodb_buffer_pool_size%";命令查询;Mysql5.6以后版本默认大小为128MB,这是一块连续空间,会在Msql启动时向操作系统生气,一般建议设置为机器内存的60%-80%
数据页Data Page
  1. 定义InnoDB引擎为了读取方便,会将磁盘中的数据划分为一个个的页,每个页默认大小为16kb,然后以页作为内存和磁盘的交互单位,而InnoDB也会以页作为存储单位;所以,当InnoDB拿到申请到的连续内存后,会将整块空间以16kb的尺寸划分成一个个缓冲页;这些空的还未使用的缓冲页称之为空闲页,而已经承载了磁盘数据的缓冲页称之为数据页
  2. 优势
    1. 读数据时:做了一层缓冲,如果所需查询数据在数据页中,就会直接从内存中读取数据并返回;
    2. 写数据时:如果所需修改的数据在数据页中,就会修改数据页数据并标记相应的数据页,然后直接返回,再由后台线程去完成数据的落盘操作;
  3. 存储过程:比如在执行一条查询语句,若内存中不存在数据,因此会走磁盘检索数据,检索数据的过程中,不管此次IO读到的数据是不是目标数据,都会将它们放在内存中;而且如果在内存充足的情况下,InnoDB会试图将所有的表数据全部载入内存;
索引缓冲页Index Page):
  1. mysql在启动时,就会将当前库中所有已经存在的索引的根节点放入到内存缓冲区中,这样对一需要走索引查询的sql就可以通过缓存直接定位到相应的索引根节点,避免了全盘查找索引节点的操作;
  2. Index Page就是专门用来存放载入的索引数据的,存放这个索引数据的数据页称之为索引页;一些访问频率较高的非根节点的索引页也可能会载入Index Page
锁空间Lock Space):
  1. 存储:主要用来存储锁结构的一块内存区域,还会存储一些并发事物链表,如:死锁检测时需要的「事物等待链表、锁的信息链表等」;
  2. 锁空间内存不足时,会导致行锁粗化成表锁,从而来减少锁结构的数量,MySQL 中锁空间大小受 innodb_buffer_pool_size 参数影响;
数据字典dict info):
  1. 用来存储sys_tables、sys_columns、sys_indexes、sys_fields四张系统表数据;
    1. 如我们执行show tables;(查询库中所有表)或者show index feom tableName(查询表中索引)等语句时,其实都是在查询系统表数据;当MySql启动时会将这些表数据放入dict info这块区域中,当我们查询时,就在这块区域中检索数据;也可通过SELECT * FROM information_schema.TABLES命令查看这些表;
    2. sys_tables:存储所有引擎为InnoDB的表信息;
      • ID:一张表的ID号。
      • NAME:一张表的名称。
      • N_COLS:一张表的字段数量。
      • TYPE:一张表所使用的存储引擎、编码格式、压缩算法、排序规则等。
      • SPACE:一张表所位于的表空间。
    3. sys_columns:存储所有用户定义的表字段信息;
      • TABLE_ID:表示一个字段属于那张表;
      • POS:一个字段在一张表中属于第几列;
      • NAME:一个字段的名称;
      • MTYPE:一个字段的数据类型;
      • PRTYPE:一个字段的精度值;
      • LEN:一个字段的存储长度限制;
    4. sys_indexes:存储所有引擎为InnoDB的索引信息;
      • TABLE_ID:表示这个索引属于哪张表;
      • ID:一个索引的ID号;
      • NAME:一个索引的名称;
      • N_FIELDS:一个索引由几个字段组成;
      • TYPE:一个索引的类型,如唯一、联合、全文、主键索引等;
      • SPACE:一个索引的数据所位于的表空间位置;
      • PAGE_NO:这个索引对应的B+Tree根节点位置;
    5. sys_fields:存储所有所有的定义信息;
      • INDEX_ID:当前这个索引字段属于哪个索引;
      • POS:当前这个索引字段,位于索引的第几列;
      • COL_NAME:当前索引字段的名称;
日志缓冲区log buffer
  1. InnoDB中主要存在两个日志缓存区undo_log_buffer、redo_log_buffer,主要的作用就是用来提升日志写入速度;
自适应哈希索引Adaptive Hash Index

可以参考索引中的介绍 【地址】;

写入缓存区change buffer):
  1. 由之前的数据缓存页可以得知,当我们在进行写操作时,如果变更的数据页在缓冲区中存在,则会直接更改缓冲区中的数据并标记,但如果需要变更的数据页不在缓冲区中则任需要去走磁盘操作数据,显然会影响性能;
    而且当我们在进行insert操作时,数据库中不存在这条数据,缓冲区中自然也不存在对应的数据页,按照目前情况插入操作都需要进行磁盘IO;为了解决此问题则出现了写入缓冲;

  2. 写入语句执行流程

    1. 判断要变更的数据是否在数据缓存页中;
    2. 存在则直接更改缓冲区中的数据,完成标记后直接返回;
    3. 不存在,则将需要变更的数据放入到写入缓冲中,然后返回;
  3. 限制:插入的数据字段不能具备唯一约束或唯一索引(只关联自增主键ID除外),原因:一般情况下数据不可能全部载入内存中,所以判断重复值的工作需要依赖磁盘中的表数据来完成;

    1. 自增主键ID会由Mysql-service维护,不会出现重复值,也就不依赖磁盘中的表数据来判断重复值;
  4. 写入缓存区不仅用来存储数据还会用来存储索引;原因是,当我们在执行插入操作时,还需要维护索引;

    由之前索引维护中的介绍可以知道,在执行插入语句时,会先向聚簇索引中插入一条相应的行记录,之后会向非聚簇索引中都插入一个新的索引键,并将值指向聚簇索引中插入的主键值;

    这个过程若不存在缓存,则表中存在多少个非聚簇索引就需要经过多少次写磁盘来维护它们,非常影响性能,所以,InnoDB对于不具备唯一性的索引,都会将要插入的索引键放在写入缓冲区;

  5. 刷入磁盘的时机:采用的是后台线程刷盘操作

    1. 当某条sql需要用到对应的索引键查询数据时;
    2. 当写入缓存区内存不足时;
    3. 距离上次刷盘时间一定的时间间隔后(默认10s);
    4. Mysql-service关闭时;

内存管理

  1. 缓冲页innoDB在启动时,在逻辑上将连续的内存划分为一块块的缓冲页

  2. 缓冲页的控制快

    1. 作用:用于管理缓冲页而设计的一种结构;可以优化在Mysql运行过程中,可用的空闲页变得零散,从而出现的磁盘加载数据时,寻找空闲页的问题;

      在这里插入图片描述

    2. 存储内容:数据页所属的表空间、页好、缓冲页地址、链表节点指针等信息,所有控制块都会放在缓冲池的最前面;

      在这里插入图片描述

    3. 使用InnoDB会为每一个缓冲页都分配一个对应的控制块,后续可以基于控制块去管理每一块缓冲页;

  3. 空闲页管理

    1. InnoDB会以控制块为节点,将所有的空闲缓冲页组成一个空闲链表(Free链表)

      在这里插入图片描述

      头节点的三个值:

      • head:空闲链表的第一个一个控制块;
      • tail:空闲链表的最后一个控制块;
      • count:空闲链表的节点数量;
    2. 当我们需要一块新的缓冲页存储磁盘数据时,不需要去进行遍历操作了,只需要找到空闲链表,然后根据控制块中记录的缓冲页地址,就可以拿到一块空闲缓冲页使用;

  4. 标记页管理

    1. 概念:当某个数据页被修改后,会对这个数据页做一个标记,被标记的数据页称之为标记页也叫脏页;
    2. 与空闲页管理类似,InnoDB也会通过控制块构建一个标记页链表(Flush链表);
    3. 刷盘:当后台线程开始进行刷盘操作时,只需要找到标记页链表,然后将链表节点中的对应缓冲页中的变更数据刷写到磁盘就可以了;
      • 标记页和写入缓冲区的刷盘时间相同,使用当写入缓冲区中也存在需要刷盘的数据时,也会将相应的缓冲页加入Flush链表;
  5. 预读失效问题

    1. 概念:在索引原理那表述过磁盘IO的执行过程,其中利用了局部性原理预读数据的机制,意味着当我们去读取数据时,默认会将其附近的16kb数据(多行数据)一次性载入内存并用一个缓冲页存放,若此时程序只取了这页数据中的一行数据,其他数据并需要读取,这种数据被提前加载进来却并没有被访问的情况称之为预读失效问题

    2. 文件系统的数据读取

      在这里插入图片描述

      当Mysql收到读取请求时,service层会交由引擎层去处理,而引擎层又依赖文件系统提供的I/O读取机制;文件系统中为了保证数据的有序读取,会将请求先放到请求队列中,等数据准备就绪后,会将数据数据放入到响应队列中,最后由相关(Mysql)进程的线程读取数据;

    3. InnoDB的预读机制

      1. 概念InooDB在存储数据时,会以64个数据页作为一个extent;当select操作发生时,一个extent里被读取的数据页达到一定阀值后,会触发InnoDB的预读机制,将剩余的数据页或下一个extenr提前载入到内存中,由后台线程完成;
      2. 两种预读策略
        1. 线性预读:当前extent中的数据页被读取到一定数量时,会触发预读提前读取下一个extent
        2. 随机预读:当前extent中的数据页被大部分载入到内存时,会触发预读将extenr剩下的数据页全部载入内存,默认关闭可通过innodb_random_read_ahead;命令开启;
      3. 阀值:可以通过show variables like 'innodb_read_ahead_threshold';命令查看;默认56,即当一个extent中的数据页被读取56个以上是,线性预读会将下个extent的所有数据页载入内存,而随机预读这是将剩下的8个数据载入内存;
内存淘汰
  1. 概念:内存空间是有限的且成本较高,无法支撑所有数据源源不断的载入内存,所以需要一套淘汰策略来淘汰某些数据页,从而达到频繁访问的数据页可以长期留在内存中,一些很少访问的数据页能够淘汰掉;与空闲页和标记页类似,对于可淘汰的数据页(已使用且未被变更过的缓冲页)也会构成一个LRU淘汰链表,有些数据会在LRUFlush两个链表间来回跳动,数据发生变更LRU -> Flush,变更数据落盘后Flush -> LRU

  2. 末尾淘汰机制:当一个线程来读写数据时,如果命中了缓冲区中的某个数据页,则将该数据页挪到LRU链表的最前面;当需要从磁盘载入数据且内存不足时,则淘汰链表末尾的数据页,并将载入的数据插入到LRU链表的头部;

    1. 存在的问题
      • 利用局部性原理预读失效时,会导致数据页常驻缓冲;
      • 一次查询(扫描)数据量过大时,会导致缓冲区中的热点数据全部被替换,导致缓冲池被污染;
  3. InnoDB的内存淘汰策略:对末尾淘汰策略做了优化;

    1. LRU链表划分为old、young两个区域;占比:young、oldLRU链表中的占比默认为63:37,可以通过innodb_old_blocks_pc这个参数,来手动调整old区在整个LRU链表中的占比;在这里插入图片描述

      从磁盘预读的数据页,只需要加入到old的头部,当这个数据页被访问时,才会将其插入young区,如果预读的页一直没有被访问,就会从old区域移除,这样就不会影响young区域中的热点数据,这样避免了预读失效问题,但还存在缓冲池污染问题;

    2. old区数据并不是访问一次就将其移入young区的,而是经过young区晋升限制,防止old区数据过早的晋升young区,这样就可以避免缓冲池污染问题

      • young区晋升限制:加了一个停留时间,则数据页想要从old区晋升至young区需要在old区存活一定时间,默认为1s,可以通过innodb_old_blocks_time调整;相当于数据页想要晋升需要被访问两次,且两次访问的时间间隔必须大于innodb_old_blocks_time
  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值