MySQL的预读机制带来的隐患:所谓的预读机制,就是当你从磁盘加载一个数据页的时候,可能会连带着把这个数据页相邻的其它数据页也加载到缓存里去。
例如,现在有两个空闲的缓存页,然后在加载一个数据页的时候,连带着把他的一个相邻的数据页也加载到缓存里了,正好每个数据页放入一个空闲的缓存页。但是实际上只有一个缓存页被访问了,另外一个通过预读机制被加载进来的缓存页,其实并没有人访问,但是此时这两个缓存页都是放在LRU链表的前面。这个时候没有空闲的缓存页,如果要加载新的数据,就要把LRU链表队尾的缓存页刷入磁盘,而不是无人访问的那个缓存页。
会触发预读机制的场景:
为了解决简单的LRU链表的问题,MySQL在设计LRU链表的时候,实际上采取的是冷热数据分离的思想;之前的问题都是因为所有缓存页都混在了一个LRU链表中导致的。
真正的LRU链表,会被拆分成热数据和冷数据两个部分,冷热数据的比例是由innodb_old_blocks_pct参数控制的,默认是37,也就是说冷数据占比为37%。这个时候,LRU链表实际上看起来是下面这个样子的:
数据页第一次被加载到缓存的时候,其实是被放在冷数据链表的头部,后面1秒之后,如果你再次访问这个缓存页,那这个缓存页会被移动到热数据链表的头部,这个时间是有innodb_old_blocks_time这个参数控制的,默认1000ms。
也就是说,必须是一个数据页被加载到缓存页之后,在1s之后,你访问这个缓存页,他才会被挪动到热数据区域的链表头部去。
因为假设你加载了一个数据页到缓存去,然后过了1s之后你还访问了这个缓存页,说明你后续很可能会经常访问它,这个时间限制就是1s,因此只有1s后你访问了这个缓存页,他才会给你把缓存页放到热数据区域链表的头部去。
预读机制以及全表扫描加载进来的一大堆缓存页,他们会放在哪里?肯定是放在LRU链表的冷数据区域的前面,假设这个时候热数据区域已经有很多被频繁访问的缓存页了,你会发现热数据区域还是存放被频繁访问的缓存页的,只要热数据区域有缓存页被访问,他还是会被移动到热数据区域的链表头部去。
所以此时预读机制和全表扫描加载进来的一大堆缓存页,此时都在冷数据区域里,跟热数据区域里的频繁访问的缓存页,没有关系。
如果仅仅是全表扫描的查询,此时你肯定是在1s内就把一大堆缓存页加载进来,然后访问了这些缓存页一下,通常这些 *** 作1s内就结束了,所以基于目前的一个机制,可以确定的是,那些缓存页是不会从冷数据区域转移到热数据区域的。
除非在冷数据区域的缓存页,在1s之后还被访问了,那么此时他们就会判定为未来可能会被频繁访问的缓存页,然后移动到热数据区域的链表头部去。
假设此时缓存页不够用了,需要淘汰一些缓存页,此时会怎样?
直接就是可以找到LRU链表中的冷数据区域的尾部的缓存页,他们肯定是之前被加载进来的,而且加载进来1s过后都没人访问过,说明这个缓存页压根儿就没人愿意去访问他,他就是冷数据。所以此时就直接淘汰冷数据区域的尾部的缓存页,刷入磁盘就可以了。
之前提到如果一个缓存页被访问了,就会把他移动到LRU链表的热数据区域首位,这么频繁的移动会导致性能不是很好。所以MySQL对LRU链表热数据区域的访问规则做了优化,只有在热数据区域的后3/4部分的缓存页被访问了,才会被移动到链表头部;链表前面1/4的缓冲页被访问,是不会被移动的。
首先,并不是在缓存页满的时候,才会挑选LRU冷数据区域尾部的几个缓存页刷入磁盘,而是有一个后台线程,每隔一段时间就会把LRU链表的冷数据区域尾部的一些缓存页刷入磁盘,然后清空这几个缓存页,并把他们加入到free链表中。
所以大家会发现,只要有这个后台线程定时运行,可能你的缓存页都还没有用完呢,就给你把一批冷数据的缓存页刷入磁盘,清空出来一批缓存页,那么你就多了一批可以使用的空闲缓存页了。
如果仅仅只是把LRU链表中的冷数据区域的缓存页刷入磁盘,明显是不够的;
LRU链表中的热数据区域里的很多缓存页可能会被频繁的修改,这些数据不可能永远放在内存中,后台线程会在MySQL不繁忙的时候,把flush链表中的缓存页都刷入磁盘中,这样,被修改过的数据就被刷入到磁盘文件中了。
只要flush链表中的一些缓存页被刷入磁盘,那这些缓存页也会从flush链表和lru链表中移除,然后加入到free链表中。
所以,一边不停的加载数据到缓存页中,不停的查询和修改缓存数据,然后free链表中的缓存页不停的在减少,flush链表中的缓存页不停的在增加,lru链表中的缓存页不停的在增加和移动。
另外一边,后台线程不停的把LRU链表的冷数据区域的缓存页及flush链表的缓存页刷入到磁盘,来清空缓存页,然后flush链表和LRU链表中的缓存页不停的在减少,free链表中的缓存页在不停的增加。
如果实在没有空闲的缓存页,那就会把LRU链表冷数据区域尾部的缓存页刷入磁盘,然后清空。
1、首先明确是不是一定要上缓存,当前架构的瓶颈在哪里,若瓶颈真是数据库 *** 作上,再继续往下看。2、明确memcached和redis的区别,到底要使用哪个。前者终究是个缓存,不可能永久保存数据(LRU机制),支持分布式,后者除了缓存的同时也支持把数据持久化到磁盘等,redis要自己去实现分布式缓存(貌似最新版本的已集成),自己去实现一致性hash。因为不知道应用场景,不好说一定要用memcache还是redis,说不定用mongodb会更好,比如在存储日志方面。
3、缓存量大但又不常变化的数据,比如评论。
4、思路是对的,清晰明了,读DB前,先读缓存,如果有直接返回,如果没有再读DB,然后写入缓存层并返回。
5、考虑是否需要主从,读写分离,考虑是否分布式部署,考虑是否后续水平伸缩。
6、想要一劳永逸,后续维护和扩展方便,那就将现有的代码架构优化,按你说的替换数据库组件需要改动大量代码,说明当前架构存在问题。可以利用现有的一些框架,比如SpringMVC,将应用层和业务层和数据库层解耦。再上缓存之前把这些做好。
7、把读取缓存等 *** 作做成服务组件,对业务层提供服务,业务层对应用层提供服务。
8、保留原始数据库组件,优化成服务组件,方便后续业务层灵活调用缓存或者是数据库。
9、不建议一次性全量上缓存,最开始不动核心业务,可以将边缘业务先换成缓存组件,一步步换至核心业务。
10、刷新内存,以memcached为例,新增,修改和删除 *** 作,一般采用lazy load的策略,即新增时只写入数据库,并不会马上更新Memcached,而是等到再次读取时才会加载到Memcached中,修改和删除 *** 作也是更新 数据库,然后将Memcached中的数据标记为失效,等待下次读取时再加载。
大方向两种方案:
1、脚本同步:自己写脚本将数据库数据写入到redis/memcached。这就涉及到实时数据变更的问题(mysql row binlog的实时分析),binlog增量订阅Alibaba 的canal ,以及缓存层数据 丢失/失效 后的数据同步恢复问题。
2、业务层实现:先读取nosql缓存层,没有数据再读取mysql层,并写入数据到nosql。nosql层做好多节点分布式(一致性hash),以及节点失效后替代方案(多层hash寻找相邻替代节点),和数据震荡恢复了。
当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。
不论是脏页还是干净页,都在内存中。
平时很快的更新 *** 作,都是在写内存和日志。
一条 SQL 语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢。
那这时候可能就是在刷脏页到磁盘中了~ flush
(1) InnoDB的redo log写满了。这时候系统会停止所有的更新 *** 作,然后让日志可以继续写。
把这部分数据日志都flush到磁盘上面。
(2) 也可能是系统内存不足,需要新的内存页,那么就淘汰一些内存页,空出来的给别的数据页使用。
先把脏页写到磁盘。
PS:使用内存是为了效率更好,
因为如果内存存在数据页,那么数据就一定正确,直接返回;
如果内存没有数据,才需要去磁盘中取,读入到内存,返回;
(3) MySQL 认为系统“空闲”的时候,反正闲着也是闲着hh
反正有机会就刷点数据
(4)MySQL 正常关闭。这时候,MySQL 会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据,启动速度会很快。
3.1 如果是redo log写满了
尽量避免的。因为出现这种情况的时候,整个系统就不能再接受更新了,所有的更新都必须堵住。更新数为 0。
3.2 内存不够用了
常态,很正常。
3.3 buffer pool
因为innodb用的是buffer pool 管理内存,缓冲池中的内存页有三种状态:第一种是还没有使用的;第二种是使用了并且是干净页;第三种是使用了并且是脏页。
Innodb 的内存策略是尽量使用内存。
我觉得知道一下就好,这个脏页刷的快不快跟磁盘的能力有关。
可以通过innodb_io_capacity 这个参数设置磁盘能力。
InnoDB 的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是 redo log 写盘速度。
平时要多关注脏页比例,不要让它经常接近 75%。
INNODB刷脏页,如果发现旁边也是脏页,那么会连带着一起刷掉。
所以可能会很慢,如果你的查询正好要先flush一个脏页的话。
在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的“连坐”机制,值为 0 时表示不找邻居,自己刷自己的。
找“邻居”这个优化在机械硬盘时代是很有意义的,可以减少很多随机 IO。机械硬盘的随机 IOPS 一般只有几百。
但是SSD 的IO很高,所以可以不用非要有刷写邻居的 *** 作,可以加快响应。
在 MySQL 8.0 中,innodb_flush_neighbors 参数的默认值已经是 0 了。
对比这个LSN跟checkpoint 的LSN,比checkpoint小的一定是干净页
也就是如果内存中比redolog的头部小,那么就是干净页
每个数据页有LSN,重做日志有LSN,checkpoint有LSN。
占用8字节,LSN主要用于发生crash时对数据进行recovery,LSN是一个一直递增的整型数字,表示事务写入到日志的字节总量。
LSN不仅只存在于重做日志中,在每个数据页头部也会有对应的LSN号,该LSN记录当前页最后一次修改的LSN号,用于在recovery时对比重做日志LSN号决定是否对该页进行恢复数据。前面说的checkpoint也是有LSN号记录的,LSN号串联起一个事务开始到恢复的过程。
感谢: https://www.cnblogs.com/drizzle-xu/p/9713378.html
我感觉就是可以理解为是一个long类型的数字,可以根据这个来比较要不要刷写数据,以及是不是干净页面,在恢复数据要拿这个进行比较。
缓存区域,缓存数据和索引在内存中。
innodb使用了一些链表。
lru链表:用来存储内存中的缓存数据。
free链表:用来存放所有的空闲页,每次需要数据页存储数据时,就首先检测free中有没有空闲的页来分配。
flush链表:在内存中被修改但还没有刷新到磁盘的数据页列表,就是所谓的脏页列表,内存中的数据跟对应的磁盘上的数据不一致,属于该列表的页面同样存在于lru列表中,但反之未必。
将脏页flush到磁盘上是直接将脏页数据覆盖到对应磁盘上的数据
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)