JYM大家好,这里是布洛妮娅, 鸭鸭的更文频道😎,本篇文章是对
MYSQL
的一个学习记录和总结!!
一、Redo Log
Redo Log 记录的是物理日志,也就是磁盘数据页的修改。 作用: 用来保证服务崩溃后,仍能把事务中变更的数据持久化到磁盘上。 MySQL事务中持久性就是使用Redo Log实现的。
什么时候写入Redo Log?为什不直接写到磁盘?
- 从磁盘加载数据到内存
- 在内存中修改数据
- 把新数据写到Redo Log Buffer中
- 把Redo Log Buffer中数据持久化到Redo Log文件中
- 把Redo Log文件中数据持久化到数据库磁盘中
直接写磁盘会有产生严重的性能问题:
-
InnoDB在磁盘中存储的基本单元是页,可能本次修改只变更一页中几个字节,但是需要刷新整页的数据,就很浪费资源。
-
一个事务可能修改了多页中的数据,页之间又是不连续的,就会产生随机IO,性能更差。这种方案叫做WAL(Write-Ahead Logging),预写日志,就是先写日志,再写磁盘。
二、Redo Log刷盘规则
写入Redo Log Buffer之后,并不会立即持久化到Redo Log FIle,需要等待操作系统调用fsync()操作,才会刷到磁盘上。
具体什么时候可以把Redo Log Buffer刷到Redo Log FIle中,可以通过innodb_flush_log_at_trx_commit参数配置决定。
三、Buffer Pool
mysql的数据是存储到磁盘的,如果每次请求都从磁盘读取数据的话,性能是很差的.为了提升性能,Innodb设计了Buffer Pool (缓冲池).
有了缓冲池后:
当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。
当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘。
Buffer Pool 以页为单位缓冲数据,可以通过 innodb_buffer_pool_size 参数调整缓冲池的大小,默认是 128 M。
Buffer Pool 的大小是有限的,对于一些频繁访问的数据我们希望可以一直留在 Buffer Pool 中,而一些很少访问的数据希望可以在某些时机可以淘汰掉,从而保证 Buffer Pool 不会因为满了而导致无法再缓存新的数据,同时还能保证常用数据留在 Buffer Pool 中。
MySQL采用了 LRU(Least recently used)算法;
该算法的思路是,链表头部的节点是最近使用的,而链表末尾的节点是最久没被使用的。那么,当空间不够了,就淘汰最久没被使用的节点,从而腾出空间。
简单的 LRU 算法的实现思路是这样的:
当访问的页在 Buffer Pool 里,就直接把该页对应的 LRU 链表节点移动到链表的头部。
当访问的页不在 Buffer Pool 里,除了要把页放入到 LRU 链表的头部,还要淘汰 LRU 链表末尾的节点。
简单的 LRU 算法无法避免下面这两个问题:
1.预读失效
MySQL 在加载数据页时,会提前把它相邻的数据页一并加载进来,目的是为了减少磁盘 IO。
但是可能这些被提前加载进来的数据页,并没有被访问,相当于这个预读是白做了,这个就是预读失效。
2.Buffer Pool 污染
当某一个 SQL 语句扫描了大量的数据时,在 Buffer Pool 空间比较有限的情况下,可能会将 Buffer Pool 里的所有页都替换出去,导致大量热数据被淘汰了,等这些热数据又被再次访问的时候,由于缓存未命中,就会产生大量的磁盘 IO,MySQL 性能就会急剧下降,这个过程被称为 Buffer Pool 污染。
为了解决这两个问题,InnoDB 对 LRU 做了一些优化,我们熟悉的 LRU 算法通常是将最近查询的数据放到 LRU 链表的头部,而 InnoDB 做了两点优化:
将 LRU 链表 分为young 和 old 两个区域,加入缓冲池的页,优先插入 old 区域;页被访问时,才进入 young 区域,目的是为了解决预读失效的问题。
当页被访问且old 区域停留时间超过 innodb_old_blocks_time 阈值(默认为1秒)时,才会将页插入到 young 区域,否则还是插入到 old 区域,目的是为了解决批量数据访问,大量热数据淘汰的问题。
可以通过调整 innodb_old_blocks_pc 参数,设置 young 区域和 old 区域比例。