热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

InnoDB存储引擎——内存

上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度

这里写图片描述

上图是InnoDB存储引擎的结构。

1、缓冲池

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的性能。

缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。在数据库进行读取页的操作时,首先将从磁盘读取的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页是,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。

对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘中。但是,页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为Checkpoint的机制刷新回磁盘。这是为了提高数据库的整体性能。

可以通过下面的方式查看MySql数据库服务器中InnoDB存储引擎的缓冲池设置的大小,从结果可以看出大小为134MB左右。

mysql> show variables like 'innodb_buffer_pool_size';
+-------------------------+-----------+

| Variable_name | Value |
+-------------------------+-----------+

| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+

1 row in set (0.00 sec)

缓冲池中缓存的数据类型有:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息,数据字典信息等。

下面是InnoDB存储引擎中内存的结构:
这里写图片描述

InnoDB存储引擎中允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。这样可以减少数据库内部的资源竞争,增加数据库的并发处理能力。可以通过设置参数innodb_buffer_pool_instances来进行配置,下面可以看到该值默认是1。

mysql> show variables like 'innodb_buffer_pool_instances';
+------------------------------+-------+

| Variable_name | Value |
+------------------------------+-------+

| innodb_buffer_pool_instances | 1 |
+------------------------------+-------+

1 row in set (0.00 sec)

2、LRU List、Free List和Flush List

缓冲池是一个很大的内存区域,其中存放了各种类型的页。那么InnoDB存储引擎是怎么对其进行管理的呢?

  • LRU List

数据库中的缓冲池是通过LRU(Latest Recent Used,最近最少使用)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾部的页。

InnoDB存储引擎中,缓冲池中页的大小默认是16KB,同样使用LRU算法对缓冲池进行管理。不过,InnoDB存储引擎对传统的LRU算法做了一些优化。
在InnoDB存储引擎中,LRU列表中还加入了midpoint位置。也就是说,新读取到的页,不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。
这个算法在InnoDB存储引擎中称为midpoint insertion strategy。在默认配置下,该位置为LRU列表长度的5/8处。midpoint位置由参数innodb_old_blocks_pct控制,下面可以查看该值:

mysql> show variables like 'innodb_old_blocks_pct';
+-----------------------+-------+

| Variable_name | Value |
+-----------------------+-------+

| innodb_old_blocks_pct | 37 |
+-----------------------+-------+

1 row in set (0.00 sec)

innodb_old_blocks_pct默认是37,表示新读取的页插入到LRU列表尾部的37%的位置(其实就是距离尾部3/8的位置,距离首部5/8的位置)。把midpoint之后的LRU列表称为old列表,之前的列表称为new列表。可以简单地理解为new列表中的页都是最为活跃的热点数据。

为什么不用朴素的LRU算法,直接将读取的页放入到LRU列表的首部呢?
如果直接将读取的页放入到LRU的首部,会影响缓冲池的效率。比如,索引或数据的扫描操作需要访问表中的许多页,但是这些页仅仅是在本次查询时需要,它们并不是活跃的热点数据。所以,如果把这些非热点数据页直接放入LRU列表的首部,那么非常可能会把之前的热点数据页重LRU列表中移除,那么,在下一次需要读取该页的时候,InnoDB存储引擎需要再次访问磁盘。

InnoDB存储引擎引入另一个参数innodb_old_blocks_time,该参数用来表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端,也就是LRU列表的new列表。如果增加innodb_old_blocks_time的值,那么会使LRU列表的热点数据不那么快被换出。

mysql> show variables like 'innodb_old_blocks_time';
+------------------------+-------+

| Variable_name | Value |
+------------------------+-------+

| innodb_old_blocks_time | 0 |
+------------------------+-------+

1 row in set (0.00 sec)

mysql> set global innodb_old_blocks_time=1000;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'innodb_old_blocks_time';
+------------------------+-------+

| Variable_name | Value |
+------------------------+-------+

| innodb_old_blocks_time | 1000 |
+------------------------+-------+

1 row in set (0.00 sec)

如果活跃的热点数据比较多,可能大于63%,除了上面增加innodb_old_blocks_time的值外,还可以减少innodb_old_blocks_pct的值来减少热点数据被换出的概率。减少innodb_old_blocks_pct的值使得midpoint的位置后移,new列表的长度增加。

mysql> 
mysql> show variables like 'innodb_old_blocks_pct';
+-----------------------+-------+

| Variable_name | Value |
+-----------------------+-------+

| innodb_old_blocks_pct | 37 |
+-----------------------+-------+

1 row in set (0.00 sec)

mysql> set global innodb_old_blocks_pct=20;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'innodb_old_blocks_pct';
+-----------------------+-------+

| Variable_name | Value |
+-----------------------+-------+

| innodb_old_blocks_pct | 20 |
+-----------------------+-------+

1 row in set (0.00 sec)
  • Free List

LRU列表是用来管理已经读取的页,但是当数据库刚刚启动时,LRU列表是空的,即没有任何的页。这时页都存放在Free列表中,也就是说,用Free列表来管理所有的空闲页。

当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。如果Free列表中没有可用的空闲页,则根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。

当从LRU列表的old部分加入到new部分时,称此时发生的操作为page made young,而因为innodb_old_blocks_time的设置为导致页没有从old部分移动到new部分的操作称为page not made young。

通过下面的命令可以查看LRU列表和Free列表的使用情况和运行状态:

mysql> show engine innodb status\G

下面是部分输出:

----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 137363456; in additional pool allocated 0
Dictionary memory allocated 256619
Buffer pool size 8191
Free buffers 7734
Database pages 457
Old database pages 0
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 457, created 0, written 0
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 457, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

可以看出当前Buffer pool size共有8191个页,即8192*16K,共128M的缓冲池。
Free buffers 表示当前Free列表中页的数量为:7734
Database pages表示LRU列表中页的数量为:457
(Free buffers 和Database pages的总和可能不等于Buffer pool size,因为缓冲池中的页还可能会分配给自适应哈希索引、Lock信息、Insert Buffer等页,这部分的页不需要LRU算法进行维护,因此不存在于LRU列表中。)

Pages made young显示了LRU列表中页移动到前端的次数。
innodb_old_blocks_time的设置为导致页没有从old部分移动到new部分的操作称为page not made young。因为没有设置innodb_old_blocks_time,所以not young等于0。
youngs/s, non-youngs/s分别表示每秒这两类操作的次数。

No buffer pool page gets since the last printout,表示没有数据从缓冲池中获取。如果有的话,会显示变量Buffer pool hit rate,表示缓冲池的命中率。

上面的结果不是当前的状态,而是过去36秒内的平均值。下面的语句是命令输出的前面的一行提示:

Per second averages calculated from the last 36 seconds

从InnoDB 1.2版本开始,还可以通过INNODB_BUFFER_POOL_STATUS来观察缓冲池的运行状态,如下面的命令:

mysql> use information_schema;
Database changed
mysql> SELECT * FROM INNODB_BUFFER_POOL_STATS\G
*************************** 1. row ***************************
POOL_ID: 0
POOL_SIZE: 8191
FREE_BUFFERS: 7734
DATABASE_PAGES: 457
OLD_DATABASE_PAGES: 0
MODIFIED_DATABASE_PAGES: 0
PENDING_DECOMPRESS: 0
PENDING_READS: 0
PENDING_FLUSH_LRU: 0
PENDING_FLUSH_LIST: 0
PAGES_MADE_YOUNG: 0
PAGES_NOT_MADE_YOUNG: 0
PAGES_MADE_YOUNG_RATE: 0
PAGES_MADE_NOT_YOUNG_RATE: 0
NUMBER_PAGES_READ: 457
NUMBER_PAGES_CREATED: 0
NUMBER_PAGES_WRITTEN: 0
PAGES_READ_RATE: 0
PAGES_CREATE_RATE: 0
PAGES_WRITTEN_RATE: 0
NUMBER_PAGES_GET: 7653
HIT_RATE: 0
YOUNG_MAKE_PER_THOUSAND_GETS: 0
NOT_YOUNG_MAKE_PER_THOUSAND_GETS: 0
NUMBER_PAGES_READ_AHEAD: 0
NUMBER_READ_AHEAD_EVICTED: 0
READ_AHEAD_RATE: 0
READ_AHEAD_EVICTED_RATE: 0
LRU_IO_TOTAL: 0
LRU_IO_CURRENT: 0
UNCOMPRESS_TOTAL: 0
UNCOMPRESS_CURRENT: 0
1 row in set (0.00 sec)

还可以通过INNODB_BUFFER_PAGE_LRU表来观察每个LRU列表中每个页的具体信息。

mysql> select * from INNODB_BUFFER_PAGE_LRU\G

下面是命令的输出的最后两行:

*************************** 456. row ***************************
POOL_ID: 0
LRU_POSITION: 455
SPACE: 0
PAGE_NUMBER: 658
PAGE_TYPE: INDEX
FLUSH_TYPE: 0
FIX_COUNT: 0
IS_HASHED: NO
NEWEST_MODIFICATION: 0
OLDEST_MODIFICATION: 0
ACCESS_TIME: 3374755154
TABLE_NAME: ch9/operate
INDEX_NAME: op_id
NUMBER_RECORDS: 4
DATA_SIZE: 36
COMPRESSED_SIZE: 0
COMPRESSED: NO
IO_FIX: IO_NONE
IS_OLD: NO
FREE_PAGE_CLOCK: 0
*************************** 457. row ***************************
POOL_ID: 0
LRU_POSITION: 456
SPACE: 0
PAGE_NUMBER: 654
PAGE_TYPE: INDEX
FLUSH_TYPE: 0
FIX_COUNT: 0
IS_HASHED: NO
NEWEST_MODIFICATION: 0
OLDEST_MODIFICATION: 0
ACCESS_TIME: 3374755155
TABLE_NAME: ch9/trigger_time
INDEX_NAME: GEN_CLUST_INDEX
NUMBER_RECORDS: 4
DATA_SIZE: 132
COMPRESSED_SIZE: 0
COMPRESSED: NO
IO_FIX: IO_NONE
IS_OLD: NO
FREE_PAGE_CLOCK: 0
457 rows in set (0.00 sec)
  • unzip_LRU列表

InnoDB存储引擎从1.0.x版本开始支持压缩页的功能,即将原本16KB的页压缩为1KB,2KB,4KB,8KB。而由于页的大小发生了变化,LRU列表也有了一些变化。对于非16KB的页,是通过unzip_LRU列表进行管理的。
通过下面命令可以查看:

mysql> show engine innodb status\G

下面是部分输出:

----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 137363456; in additional pool allocated 0
Dictionary memory allocated 256619
Buffer pool size 8191
Free buffers 7734
Database pages 457
Old database pages 0
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 457, created 0, written 0
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 457, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

可以看到LRU列表有457个页,而unzip_LRU列表中没有页。需要注意的是,LRU中的页包含了unzip_LRU列表中的页。

对于压缩页的表,每个表的压缩比率可能不一样。有的表页大小为8K,有的可能是2K。那么unzip_LRU是怎样从缓冲池中分配内存的呢?
首先,在unzip_LRU列表中对不同压缩页大小的页进行分别管理。其次,通过伙伴算法进行内存的分配。例如,对需要从缓冲池中申请页为4K的大小,其过程如下:
1)检查4K的unzip_LRU列表,检查是否有可用的空闲页;
2)若有,则直接使用;
3)否则,检查8K的unzip_LRU列表;
4)若能够得到空闲页,将页分成2个4KB的页,存放到4K的unzip_LRU列表;
5)若不能得到空闲页,从LRU列表中申请一个16K的页,将页分为1个8K的页,2个4K的页,分别存放到对应的unzip_LRU列表中。

可以通过INNODB_BUFFER_PAGE_LRU表来看哪些页的COMPRESSED_SIZE不等于0。如果不等于0,则表示该页是unzip_LRU列表的页。

  • Flush列表
    在LRU列表中的页被修改之后,称该页为脏页,即缓冲池中的页和磁盘上的页的数据发生了不一致。这时数据库会通过Checkpoint机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。需要注意的是,脏页既存在于LRU列表中,也存在于Flush列表中。LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新回磁盘,二者互不影响。

通过下面命令查看:

mysql> show engine innodb status\G

下面是命令的部分输出:

----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 137363456; in additional pool allocated 0
Dictionary memory allocated 256619
Buffer pool size 8191
Free buffers 7734
Database pages 457
Old database pages 0
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 457, created 0, written 0
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 457, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

其中Modified db pages 显示的是脏页的数量。

3、重做日志缓冲

InnoDB存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区,然后将其按一定频率刷新到重做日志文件中。
重做日志缓冲一般不需要设置很大,因为一般情况下每一秒钟会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。该值可由配置参数innodb_log_buffer_size控制,默认是8MB。

mysql> show variables like 'innodb_log_buffer_size';
+------------------------+---------+

| Variable_name | Value |
+------------------------+---------+

| innodb_log_buffer_size | 8388608 |
+------------------------+---------+

1 row in set (0.00 sec)

通常情况下,8MB的重做日志缓冲池足以满足绝大部分的应用,因为重做日志在下列三种情况下会做重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中。
1)Master Thread每一秒将重做日志缓冲刷新到重做日志文件;
2)每个事务提交时会将重做日志缓冲刷新到重做日志文件;
3)当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件;

4、额外的内存池

额外的内存池通常被DBA忽略。
在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。
比如,分配了缓冲池,但是每个缓冲池中的帧缓冲还有对应的缓冲控制对象,这些对象记录了一些诸如LRU、锁、等待等信息,而这个对象的内存需要从额外内存池中申请。因此,在申请了很大的InnoDB缓冲池时,也应考虑相应地增加这个值。


推荐阅读
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
  • Oracle10g备份导入的方法及注意事项
    本文介绍了使用Oracle10g进行备份导入的方法及相关注意事项,同时还介绍了2019年独角兽企业重金招聘Python工程师的标准。内容包括导出exp命令、删用户、创建数据库、授权等操作,以及导入imp命令的使用。详细介绍了导入时的参数设置,如full、ignore、buffer、commit、feedback等。转载来源于https://my.oschina.net/u/1767754/blog/377593。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • 我们有(据我所知)星型模式SQL数据库中的数据文件。该数据库有5个不同的文件,扩展名为 ... [详细]
  • 本文介绍了一个误删Oracle数据文件导致数据库无法打开的问题,并提供了解决方式。解决方式包括切换到mount状态、离线删除报错的数据文件等。 ... [详细]
  • 本文介绍了在使用Laravel和sqlsrv连接到SQL Server 2016时,如何在插入查询中使用输出子句,并返回所需的值。同时讨论了使用CreatedOn字段返回最近创建的行的解决方法以及使用Eloquent模型创建后,值正确插入数据库但没有返回uniqueidentifier字段的问题。最后给出了一个示例代码。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • MongoDB用户验证auth的权限设置及角色说明
    本文介绍了MongoDB用户验证auth的权限设置,包括readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase、cluster相关的权限以及root权限等角色的说明和使用方法。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
author-avatar
MiSsGrAce低调人生称_854
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有