Innodb的Buffer Pool

137 阅读5分钟

由于CPU的速度跟磁盘IO速度相差太大,且innodb是一款基于磁盘存储的引擎,如果每次读取数据都要进行磁盘IO,那么性能会特别的差,所以需要借助内存缓冲数据,提高访问速度。

Buffer Pool 则是innodb用来管理数据页的内存缓冲池。我们可以通过SHOW ENGINE INNODB STATUS语句,查看当前的引擎状态。本次重点关注 BUFFER POOL AND MEMORY 。我们尝试通过这些状态数据来了解innodb的内存管理。

    ----------------------
    BUFFER POOL AND MEMORY
    ----------------------
    Total large memory allocated 140836864
    Dictionary memory allocated 205958
    Buffer pool size   8192
    Free buffers       7777
    Database pages     415
    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 281, created 134, written 432
    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: 415, unzip_LRU len: 0
    I/O sum[0]:cur[0], unzip sum[0]:cur[0]

以上的数据可以分成多个部分,我们分别对他们进行分析。

基本信息

    Total large memory allocated 140836864
    Dictionary memory allocated 205958
  1. Total large memory,表示缓冲池总的分配内存为 140836864 字节,包含用于数据页的内存,以及一些控制信息。
  2. Dictionary memory,用于缓存数据字典。这里分配的内存大小 不包含Total large memory allocated 中。

数据页统计信息

Buffer pool size   8192   (缓冲池的数据页总数)
Free buffers       7777   (缓冲池的空闲数据页总数)
Database pages     415    (缓冲池已使用的数据页总数)
Old database pages 0      (冷数据区的数据页总数,包含在Database pages的统计计数中)
Modified db pages  0      (脏页总数)

上面所有的统计值的单位都是页面数,innodb将申请到的内存分成一个个的页面进行管理,每页的大小默认为16kb,可以通过 innodb_page_size 控制。

mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+

因此,缓冲池的总内存大小为,8192 * 16kb = 134217728字节,与innodb_buffer_pool_size的大小一致。

mysql>  show variables like 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+

改进版LRU算法

innodb在管理内存页的时候,是基于冷热数据分离的改进版LRU算法。传统的LRU算法,没有区分冷热数据,假设有一个全表扫描操作,扫描过程中读取到的数据页会布满整个链表,并且会将热数据淘汰。那么,后续的热点数据请求,则需要重新将对应的数据页从磁盘加载到内存,内存命中率的下降,会导致整体的性能降低。

为了解决上述问题,innodb将数据分为冷热数据两部分,热数据区称为young,冷数据称为old。新读取的数据页先插入到冷数据的头部,如果该数据页在冷数据段停留时间超过设定值,当其再被访问时,再移动到热数据段。

image.png

我们可以通过以下参数,控制冷热数据的比例,以及冷数据转为热数据的时间阈值,

mysql> show variables like 'innodb_old_blocks%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_pct  | 37    |
| innodb_old_blocks_time | 1000  |
+------------------------+-------+
  • innodb_old_blocks_pct,控制冷数据的占比,默认值37(大约38\frac{3}{8},如上图所示,冷数据在链表尾部)
  • innodb_old_blocks_time,控制冷数据转成热数据的停留时间,默认值为1000毫秒

输出中的Pages made young 0, not young 0表示有多少数据页被移动到了热数据区。

脏页

innodb在执行更新操作的时候,因为随机IO对性能的影响比较高,所以innodb不是直接修改的磁盘数据,而是先修改内存中的数据,并将对应数据页标记为脏页,最后由后台线程负责将其写到磁盘。

IO状态

Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0

Pending reads 表示当前正在排队等待读取的请求数,当请求的数据不在缓冲池时,就会发起一个磁盘IO请求,此时该计数会增加,当请求完成,数据加载到缓冲池后,该值减小。

Pending writes 表示当前正在排队等待写入的请求数,上面统计了三种写入场景:

  1. LRU,表示由于需要腾出空间而从LRU链表淘汰脏页的写入
  2. flush list,表示后台线程发起的脏页写入
  3. single page,表示单个页面的独立写入请求(通常是一些特殊操作)

理想情况下这些值应该经常为0或很小的数字,因为这表示的是需要等待完成的磁盘IO,当磁盘压力大的时候,这里的值就会增大。

Page read/created/written

Pages read 281, created 134, written 432
  1. read:表示从磁盘读取的页面总数
  2. created:内存中新创建的页数(常用于排序之类的操作),如果这个值很高表示有大量的CPU计算
  3. written:写入磁盘的内存页数

readcreated 的数据页都是从 free list 里面分配的,只是 read 表示从磁盘数据构造的页面, created 则表示在内存中构造出来的数据页。

数据预读

Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
  1. Pages read ahead,表示预读的速率,当innodb检测到顺序访问模式的时候,会触发预读,将相邻的页面提前读入缓冲区。
  2. evicted without access,表示预读到内存的数据,没有被访问就被淘汰。这个值越高,表示预读效果越差。
  3. random read ahead,表示当innodb监测到某个区连续多个页面被访问后,会将该区其余页面也读进缓冲区(区是一个逻辑概念,一个区包含多个数据页)