由于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
- Total large memory,表示缓冲池总的分配内存为
140836864字节,包含用于数据页的内存,以及一些控制信息。 - 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。新读取的数据页先插入到冷数据的头部,如果该数据页在冷数据段停留时间超过设定值,当其再被访问时,再移动到热数据段。
我们可以通过以下参数,控制冷热数据的比例,以及冷数据转为热数据的时间阈值,
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(大约,如上图所示,冷数据在链表尾部)
- 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 表示当前正在排队等待写入的请求数,上面统计了三种写入场景:
- LRU,表示由于需要腾出空间而从LRU链表淘汰脏页的写入
- flush list,表示后台线程发起的脏页写入
- single page,表示单个页面的独立写入请求(通常是一些特殊操作)
理想情况下这些值应该经常为0或很小的数字,因为这表示的是需要等待完成的磁盘IO,当磁盘压力大的时候,这里的值就会增大。
Page read/created/written
Pages read 281, created 134, written 432
- read:表示从磁盘读取的页面总数
- created:内存中新创建的页数(常用于排序之类的操作),如果这个值很高表示有大量的CPU计算
- written:写入磁盘的内存页数
read 与 created 的数据页都是从 free list 里面分配的,只是 read 表示从磁盘数据构造的页面, created 则表示在内存中构造出来的数据页。
数据预读
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
- Pages read ahead,表示预读的速率,当innodb检测到顺序访问模式的时候,会触发预读,将相邻的页面提前读入缓冲区。
- evicted without access,表示预读到内存的数据,没有被访问就被淘汰。这个值越高,表示预读效果越差。
- random read ahead,表示当innodb监测到某个区连续多个页面被访问后,会将该区其余页面也读进缓冲区(区是一个逻辑概念,一个区包含多个数据页)