LRU队列
LRU分为LRU和LRUW,他们两个分别分为主辅两个链表。也就是说一组LRU包含四个链表,主LRU,辅LRU,主LRUW,辅LRUW。其中主LRU和辅LRU用于在Buffer cache中寻找可以覆盖的buffer cache块。主LRUW和辅LRUW的作用和 检查点队列类似或者说是二者合力而为,是DBWR用来写脏块的。
1.主LRU和辅LRU链表
作用:物理读时,服务器进程将数据块从数据文件读入Buffer Cache中,那么进程应该将数据块读进buffer cache的哪个地方呢?读入的一定是最不常用的Buffer cache。覆盖掉最不常用的就是LRU的本质作用。
那么LRU是如何找到这个最不常用的Buffer呢?
LRU会将Buffer Cache中所有的Buffer都串联在一起。
LRU又分成两条链表,主LRU和辅LRU。其中主LRU又分成冷端和热端两个部分。每个Buffer都会有一个访问计数TCH,TCH以3s为一个阶段,每个阶段只要又进程访问,它的数值就会加1。如果3s内访问多次,那么也只会加1。Buffer在冷端还是在热端主要靠这个TCH。
如图所示,每个字母的下面都有一个数字就是TCH,这里面每个node都指向buffer cache中的某个buffer,图中有6个buffer为主LRU链表两个buffer为辅助LRU链表。一般情况下,辅助LRU会占LRU buffer总数量的25%,主LRU存放剩下的75%(但不是硬性规定可以变化)。主LRU又分为两段冷端和热端上图已经标明。注意Buffer Cache中所有的buffer都存放在LRU中但是不一定都在主,辅LRU中还有LRUW。
主辅LRU数量的比例可以通过如下的方式进行查看:
SYS@proe>select CNUM_SET,CNUM_REPL,ANUM_REPL from x$kcbwds;
CNUM_SET CNUM_REPL ANUM_REPL
---------- ---------- ----------
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
20186 20186 17401
20180 20180 17378
20180 20180 17388
CNUM_SET CNUM_REPL ANUM_REPL
---------- ---------- ----------
20183 20183 17392
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
CNUM_SET CNUM_REPL ANUM_REPL
---------- ---------- ----------
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
32 rows selected.
注意:在oracle中任何链表都有专门的latch对其进行保护,一组LRU链表,包括主LRU,辅LRU,主LRUW和辅LRUW,它们被称为一个Workset(工作组),被一个latch保护称为:cache buffers lru chain。
上面的语句执行结果共有32行说明有32个工作组,但是只有4行存在数据其他都是0这代表了只有4个工作组被使用。相应的cache buffers lru chain的数量也是32个只有4个被使用,其余的都是空闲状态。结果中的列的含义如下:CNUM_SET列是工作组中Buffer的总数量;CNUM_REPL是工作组内所有LRU中的Buffer数量,通常这两个值是相等的。ANUM_REPL是辅助LRU中Buffer的数量。使用CNUM_REPL减去ANUM_REPL就是主LRU中Buffer的数量。
可以使用如下方式去计算:
#计算主LRU中Buffer的数量
SYS@proe>select sum(CNUM_REPL)-sum(ANUM_REPL) from x$kcbwds;
SUM(CNUM_REPL)-SUM(ANUM_REPL)
-----------------------------
11211
#计算辅助LRU中buffer的占比
SYS@proe>select round(sum(ANUM_REPL)/sum(CNUM_SET)*100,2) from x$kcbwds;
ROUND(SUM(ANUM_REPL)/SUM(CNUM_SET)*100,2)
-----------------------------------------
86.11
2.物理读时访问LRU链表情况
服务器进程要读5号文件的80号块,但是buffer cache中并不存在。
整个过程如下:
进程先搜索hash表,搜索结果没找到,5号文件的80号块并不存在buffer cache中。进程发起物理读将其读入buffer cache中。物理读开始,进程首先要获得cache buffers lru chain latch然后进程从辅助LRU的尾端搜索可以覆盖的Buffer。覆盖标准是,不是脏块同时TCH值小于2。服务器进程在找到辅助LRU中buffer H,TCH为1说明这个块在某个3s中被若干次访问。然后辅助LRU中的H被服务器进程认为可覆盖,会被从辅助链表移动到主链表的冷端头部。如下所示,
这个时候辅助LRU链表上就只存在一个块了。H被移到了冷端头,然后cache buffers lru chain latch被释放,LRU链的相关操作就结束了,服务器进程会真正的进行一次物理读将需要的数据从物理文件上读入Buffer Cache中。
整个过程需要注意:进程是从辅助链表开始搜索LRU的,找到覆盖的buffer后会将它移动到主LRU的冷端头。辅助LRU可以为空,上图中如果再发生一次物理读G对应的buffer就会被覆盖,同时G也会移动到主链的冷端头,当然这种情况只会发生在物理读繁忙的时候。也就是下图所示的情况,第二次物理读。
当辅助LRU为空时,发生物理读是从哪里搜索可用块呢?从主LRU的冷端尾部。也就是上图中的F处但是这个F并不符合块覆盖的原则,因为它的TCH大于2。所以这个块并不能被覆盖。并且它会被移动到主LRU的热端头同时将其TCH置为0,如下所示
这样原来的热端尾的C变成了冷端头,然后进程会继续从冷端尾向冷端头去扫描,冷端尾目前是E块,但是它是一个脏块也不可以覆盖,但是它不会被移动而是被放入LRUW中这里就说为跳过。接下来扫描到的是D,它将会被覆盖。D会被移动到冷端头,也就是C之前,如下所示。
D所对应的Buffer会被新的物理读所覆盖,因此D又被称为“牺牲者”,LRU的规则就是选择牺牲者的规则。以后如果再有物理读,那么被覆盖的Buffer将依次是H、G、C、D循环往复,但是处于热端的块不会被使用。只会循环使用冷端的。
总结:
1)进程从辅助LRU链表尾开始搜索牺牲者。
2)如果辅助LRU链表为空,或者辅助LRU上没有可用块,都是脏块。将会从LRU主链的冷端尾开始搜索牺牲者。
3)找到可以覆盖的牺牲者后,将它移动到主LRU的冷端头,它所对应的buffer被新的物理读所覆盖。
4)脏块会被跳过而不是移动。
5)在搜索过程中TCH大于或等于2的buffer会被移动到热端头部。
3.辅助LRU为空后的处理方式
上面也说过辅助LRU在物理读很频繁的时候可能为空。为空后如何处理呢?不可能会一直为空的,相关的进程就是SMON。
SMON和PMON,CKPT,DBWR,LGWR一样都是每3s醒来一次,SMON每次醒来都会申请cache buffers lru chain latch锁资源,然后检查主LRU和辅LRU的长度,如果辅助LRU中的Buffer小于25%,SMON会从主LRU冷端尾搜索TCH小于2的非脏块。将其移动到辅助LRU中保持辅助LRU25%的占比。所以在一般情况下,才会有主和辅LRU长度是3比1的关系。对于物理读很多的场景辅助LRU的占比可能会稍低一些。
OracleLRU分为主LRU和辅LRU的目的是加快搜索LRU链表的速度。主LRU中存在各种各样的Buffer,而SMON会每3秒一次将主LRU中非脏块,TCH小于2的可覆盖buffer移动到辅LRU上,进程需要时在辅LRU上寻找牺牲者速度一定会快很多。缩短了搜索LRU的时间,也就减少了cache buffers lru chain latch的持有时间。
除此之外,数据库在刚启动的时候或者刚刚lflush了Cache Buffer后,所有的Buffer都会在辅助LRU中。测试方法如下
SYS@proe>alter system flush buffer_cache;
System altered.
SYS@proe>select CNUM_SET,CNUM_REPL,ANUM_REPL from x$kcbwds where cnum_set > 0;
CNUM_SET CNUM_REPL ANUM_REPL
---------- ---------- ----------
20186 20186 20186
20180 20180 20180
20180 20180 20180
20183 20183 20183
#过一会再查看
SYS@proe>select CNUM_SET,CNUM_REPL,ANUM_REPL from x$kcbwds where cnum_set > 0;
CNUM_SET CNUM_REPL ANUM_REPL
---------- ---------- ----------
20186 20186 20154
20180 20180 20149
20180 20180 20148
20183 20183 20150
注意更新后立即查看,可以看到辅助LRU存放的buffer数量和全部的是一致的,也就是所有的Buffer都在辅助LRU中。
物理读时服务器会先扫描辅助LRU,将找到的牺牲者移动到主LRU冷端头进行覆盖。因此当数据库在正常运转的时候,辅助LRU中的Buffer数将不断减少,因为不断有物理读,所以辅LRU上的buffer会不断被移动到主LRU上,主LRU的长度也会相应不断增长。而SMON进程会在辅助LRU的buffer占比低于25%时,将主LRU中的Buffer移动到辅助LRU上维持一个平衡。