InnoDB是基于磁盘存储的,其中的存储记录按照页的方式进行管理,可将其视为基于磁盘的数据系统。由于磁盘读写速度与CPU计算速度之间的鸿沟,InnoDB对数据库的读写操作都要通过缓存来实现。在数据库进行读取操作时,先将从磁盘读取到的数据放到缓存池中,这个过程称为将页“FIX”到缓存池,下一次再读取相同的页时,会先读取缓存中的页,没有命中才读取磁盘。在写操作时,先修改缓存中的页,再以一定的频率刷到磁盘中。
1. 缓存池
Mysql通过innodb_buffer_pool_size
设置InnoDB的缓存池大小:
# 查看
show variables like 'innodb_buffer_pool_size';
# 修改
set innodb_buffer_pool_size=1000000;
缓存池中存储的数据有:索引页、数据页、undo页、插入缓存、自适应哈希索引、锁信息、数据字典
为了减少数据库内部的资源竞争,Mysql 1.0 之后的版本允许有多个缓存实例,每个页根据hash值分布到不通的缓存实例中(另一个因素是Mysql 1.0 之后,InnoDB使用了AIO,支持多线程读写)。
# 查看InnoDB的缓存池实例数量
show variables like 'innodb_buffer_pool_instance';
2. 缓存策略
数据库中的缓存池通过LRU算法进行管理,将最频繁使用的页放到LRU列表的头部,最少使用的放在尾部,当缓存池内存不足,不能存放新页时,淘汰尾部的页。
缓存池中的页有默认的16kb(由lru管理)和压缩的1kb、2kb、4kb、8kb(由unzip_lru管理)。
InnoBD对LRU算法做了优化:在LRU列表中加入了midpoint位置,当读取到新页时,将新页存放到midpoint位置。又innodb_old_blocks_pct
控制,默认为37,即LRU列表中前37%的为new列表,之后的为old列表。
show variables like 'innodb_old_blocks_pct';
另外,innoDB通过innodb_old_blocks_time
来进一步管理LRU列表,新读到的页要在这个时间之内再次被读取才会插入到LRU列表中。
show variables like 'innodb_old_blocks_time';
这样做的好处是一些偶然的读取操作不会将LRU列表中的热点数据移出,尤其是一些大的读取操作,如定时任务进行全表读取。
为unzip_lru列表分配内存和一般的分配内存有所不同(因为页的大小不同,而mysql默认的页为16kb)。比从缓存池中申请一个4kb的页,要进行以下步骤:
- 检查4kb的unzip_lru列表是否有可用的空闲页
- 若有,直接使用
- 若无,检查8kb的unzip_lru列表是否有可用的空闲页
- 若有,将页分为2个4kb的页,存放到4kb的unzip_lru列表中
- 若无,从LRU列表中申请一个16kb的页,分为1个8kb的页和两个4kb的页,分布存放到相应的unzip_lru列表中
**写入缓存:**执行写如操作后,新插入或被修改的页(脏页)会同时存在LRU列表和Flush列表中,LRU列表用来管理缓存池中页的可用性,Flush列表用来管理将页刷如磁盘,二者互不影响。
查看缓存池状态:(非实时)
mysql> show engine innodb status;
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 449278
Buffer pool size 8191 #缓存池中的页数,占用大小:8191*16KB
Free buffers 7089 #Free列表中的页数
Database pages 1095 #LRU列表中的页数
Old database pages 423 #旧LRU SUBLIST的页数
Modified db pages 0 #脏页
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0 #插入LRU列表的新页,尚未插入的页
0.00 youngs/s, 0.00 non-youngs/s #频率
Pages read 948, created 147, written 320
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: 1095, unzip_LRU len: 0 #LRU列表的页数和unzip_LRU列表的页数,前者包含后者
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
3. 重做日志缓存
InnoDB先将undo log放到重做日志缓存,再刷到重做日志文件。重做日志缓存的大小由innodb_log_buffer_size
控制,默认为8M。一般不用设置的太大,因为master thread每秒都会将重做日志刷到文件。
show variables like 'innodb_log_buffer_size';
将重做日志刷入到文件的情况:
- master thread每秒刷一次
- 每次事务提交刷一次
- 重做日志缓存池剩余内存不足1/2时刷一次