文章目录
1.1 HASH链表
Buffercache作为磁盘数据块的缓存在Oracle的所有池中是最大的。在生产环境中往往几十G甚至上百G。为了更好的管理Buffercache准确的找到数据文件需要的数据块或者查看这个数据块是否在缓存中,使用的方法就是hash算法。
1.1.1 hash链表与逻辑读
Buffer cache中的bucket数量由参数_db_block_hash_buckets决定,通常情况下这个参数并不需要被修改。下面介绍进程通过hash链表进行逻辑读的过程:
当进程需要读取某个数据块的时候,会根据文件号和块号进行hash计算,得到hash值。假设这个时候得到的hash值是X,那么进程将根据这个值直接定位到BucketX。在找到BucketX后,就可以读取里面的内容。但是hash通常通过字符长度来计算所以无可避免会出现多个数据块hash值相同的情况,也就是hash冲突,所以为了解决这个问题出现了链表。
在每个bucket中都保存了一个链表头指向Cache Buffers Cache链表(简称CBC链表)。如下所示,hash表和Cache Buffer Cache链表。(所谓链表就是,已知一个节点,这个节点知道下一节点的地址,下一节点知道再下一个节点的地址,这三个节点就组成了单向链表。当然如果这三个节点同时也知道上一节点的地址,那么就变成了双向链表。Oracle中所有的链表都是双向链表。链表的意义在于,我只需要知道一个节点的信息就可以知道整个链其他节点的信息。而整个链开头的节点就是链表头,bucket中存放的就是链表头的地址信息,也就是指向链表头的指针。)这个链表中的每个节点都有自己的Buffer Header(BH,BH大小固定,BH中比较重要的是BA,Buffer Address,记录了块在buffer cache中的地址。)记录块的位置和状态等信息,当hash运算出结果并且找到相应的bucket下一步就是在这个bucket中记录的链表头指针找到对应链表逐一比较BH,直到找到需要的为止。
总结一下,进程在buffer cache中搜索buffer的过程:
1)进程根据要访问的块的文件号,块号计算hash值。
2)根据hash值找到hash bucket。
3)搜索bucket后的链表,查找目标BH。
4)找到目标BH,从中取出Buffer的BA。
5)按BA访问Buffer。
如果整个过程没有找到包含目标文件号、块号的BH,那就证明Buffer Cache中不包含的目标块,就只能物理读。
1.1.2 Cache Buffers Chain Latch和Buffer Pin锁
SGA是公共内存,只要需要访问公共内存就需要有某种锁机制进行保护,在Oracle中使用的锁机制就是Latch和Mutex。在上述逻辑读的例子中,搜索Bucket后的链表还有访问BH中的BA都需要Latch的保护(Latch,Cache Buffer Chain Latch简称为CBC Latch)
如上如图所示在Oracle链表前加了一把锁,如果想访问链表就需要先申请获得这把锁。这个锁就是CBC Latch。除了对链表访问的保护,当在链表中找到目标BH时,有时还要对BH进行修改,修改的目的是为了加锁,这里的锁叫做Buffer Pin锁。在修改完BH中的Buffer Pin锁状态后,CBC Latch锁就可以释放。当做完对块的修改后还需要史昂Buffer Pin锁,相应的CBC Latch还需要进行保护。
总结一下获得CBC Latch后进程要完成的工作:
1)搜索链表,查找目标BH。
2)修改BH中Buffer Pin锁的状态。
Buffer Pin锁有多种模式,最常见的有共享(S),独占(X)两种模式,没有加锁的时候,Buffer Pin锁的值为0。如果只是逻辑度,进程会将Buffer Pin锁的状态设置为S模式。如果是DML操作,需要对buffer进行修改,进程将把Buffer Pin设置为X。
关于CBC Latch从上面的图来看并非每个HASH Bucket都有一个CBC Latch来保护,实际上一个CBC Latch需要保护多个HASH Bucket。这样做的目的就是为了节约内存。因为每个Latch都会占用内存。
CBC Latch锁也有两种持有模式,共享和独占。但是它的模式不是依靠读写形式决定的,有时就算是读而持有CBC Latch也可能是独占模式的CBC Latch。它的模式取决于一下几点:
- 对象类型(唯一索引、非唯一索引等)
- 块类型(根块、叶块或表块等)
- 操作(读、修改)
- 访问路径(Access Path)
除了唯一索引以外,大多数情况下,无论读写,访问表块都将以独占模式获得CBC Latch。另外,索引的根块,枝块只要不修改,都是以共享模式获得CBC Latch ,几乎Oracle中所有的锁的原理都是,只要不涉及修改就是共享,和修改有关就需要是独占模式。对于Buffer,虽然只是读其中的数据,但还是会对Buffer Pin做出修改,所以自然会使用独占方式请求CBC Latch。
一般逻辑读CBC Latch状态:
1)首先需要以独占方式获取CBC Latch。
2)在独占模式 CBC Latch的保护下,修改BH中的Buffer Pin锁,将锁的状态改为S(原来是0)。
3)BH中的Buffer Pin锁状态修改完毕,释放独占的CBC Latch。
4)在共享Buffer Pin锁的保护下,到BH中的BA找到Buffer的地址,读取Buffer中的数据。
以上流程只是一般逻辑度的流程。对于索引的根块,枝块等这些块的查询频率肯定远远高于叶块和表块。如果查询走了索引,那么每次查询都会访问索引的根块,然后访问叶块和表块,而不可能每次都访问相同的叶块和表块,但是根块只有一个。所以对于查询频率根块和枝块会比叶块和表块高很多。相对的修改频率又会低于叶块和表块。
在这种情况下,每次查询根块,枝块都以独占模式获取CBCLatch,再以共享模式得到Buffer Pin锁,然后查询Buffer数据。Buffer pin是共享的不会产生等待但是CBC Latch是独占的还是会产生激烈的竞争。
针对上述问题的优化调整:
独占CBC Latch的目的是为了保护修改BH中的Buffer Pin锁的状态。如果不修改Buffer Pin锁那么对于BH就只剩下读操作了。这样CBC Latch也就可以使用共享模式了。
优化调整流程如下:
1)以共享模式获得CBC Latch。
2)在共享CBC Latch保护下,搜索链表。查询BH中的BA,在这个步骤中不需要对Buffer Pin锁的状态进行修改。
3)在共享CBC Latch保护下,根据BH中BA地址,查询Buffer中的数据。
4)查询Buffer数据完毕释放CBC Latch。
整个过程中CBC Latch一直持续到读取buffer结束,也没有修改Buffer Pin。总结一下,当一条查询其where条件为等值条件时,那么所有根块,枝块,叶块,表块全部都是共享CBC Latch。如果不是等值条件则和非唯一索引一样只有根块,枝块是共享CBC Latch且无Buffer Pin锁的方式,叶块和表块还是独占CBC Latch方式。在索引唯一访问路径(INDEX UNIQUE SCAN)下,从根块到表块将全是共享CBC Latch,但是用ROWID直接逻辑读表块(TABLE ACCESS BY USER ROWID)是独占CBC Latch。
1.1.3 Cache Buffers Chain Latch的竞争
一个CBC Latch可以保护多个链表,除了保护链表的访问以外,还要保护BH的读和修改操作,而一个链表中可能会有多个BH,这样一个CBC Latch除了保护多个链表还需要保护数目更多的BH,因此当CBC Latch出现竞争时可能是以下两种情况:
1)多个进程频繁地以不兼容的模式申请获得某一CBC Latch,访问此CBC Latch保护的不同链表和不同BH。——热链竞争
2)多个进程频繁地以不兼容的模式申请获得某一CBC Latch,访问此CBC Latch保护同一链表下的同一BH。——热块竞争
这两种情况,热链竞争的情况相对较多。也最容易解决。问题原因在于:多个进程访问的是不同的BH,但是恰巧这些BH都在同一CBC Latch保护下。处理方法就是对和bucket有关的两个隐藏参数进行修改。即_db_block_hash_buckets和 _db_block_hash_latches这两个参数分别控制HASH Bucket的数量和CBC Latch的数量。这样一来BH和HASH Bucket的对应关系就会被重新计算。原本在同一链表中的BH就有可能被重新规划在不同的链表中。但这是静态参数需要重启数据库不推荐使用。