
第一部分:Oracle Buffer Cache 深度解析
一、作用 (Purpose)
Buffer Cache是系统全局区(SGA)中最大且最关键的内存组件之一。它的核心作用是缓存从数据文件中读取的数据块副本,从而减少物理I/O(磁盘读写),极大提升数据库的性能。
- 核心思想:基于时间局部性(最近访问的数据很可能再次被访问)和空间局部性(访问一个数据块,很可能访问其相邻数据块)原理,通过内存缓存来弥补内存与磁盘之间巨大的速度鸿沟。
- 直接好处:
- 对于读:后续对相同数据的请求可以直接从内存获取,速度是纳秒级,而非毫秒级的磁盘读取。
- 对于写:数据的修改(DML)首先在Buffer Cache中进行,数据库后台进程(DBWn)再择机批量将脏块写入磁盘,实现了延迟写和批量写,极大减少了磁盘I/O次数和写入延迟。
二、详细管理机制 (Management Mechanism)
Oracle通过一套极其精巧的链表(LinkedList)和算法来管理数以万计的Buffer。
核心概念:Buffer的状态
每个Buffer(即一个数据块在内存中的映像)都处于以下三种状态之一:
- 空闲(Free):未被使用,可以覆盖。
- 干净(Clean):内容与磁盘上的数据块一致。
- 脏(Dirty):内容已被修改,与磁盘上的数据块不一致。
为了高效管理这些Buffer,Oracle引入了以下关键链表:
1. HASH链表 (Hash Bucket Chains)
- 作用:快速定位一个数据块是否已经在Buffer Cache中。
- 原理:
- Oracle将Buffer Cache划分为多个Hash Bucket。
- 当需要访问某个数据块时(通过
DBA:Data Block Address,即文件号+块号计算得到),会用一个Hash函数计算其Hash值,定位到特定的Hash Bucket。 - 每个Bucket后面挂着一个链表,链接着所有映射到这个Bucket的Buffer Header(Buffer的控制结构,包含DBA、状态、指针等信息)。
- 通过遍历这个链表,即可快速找到指定的数据块是否已在内存中。
- 类比:Java中的
HashMap或Python中的Dictionary,通过Key(DBA)快速查找Value(Buffer地址)。
2. LRU链表 (Least Recently Used - LRU & LRUW)
LRU链表实际上是两个链表:主链(LRU List) 和辅链(LRUW List,也叫写列表)。它们共同实现了Buffer的淘汰和写出机制。
-
LRU主链 (LRU List):
- 作用:管理干净块和尚未被写入磁盘的脏块的访问频率,用于决定当需要空闲Buffer时,谁最先被覆盖。
- 机制:这是一个“冷热分离”的链表。
- 热端(MRU - Most Recently Used):最近被访问的Buffer会被移动到链表的MRU端。
- 冷端(LRU端):长时间未被访问的Buffer会逐渐向LRU端移动。
- 工作流程:当需要一个空闲Buffer来存放新从磁盘读入的数据块时,服务器进程会从LRU链表的冷端开始扫描,寻找可用的Buffer。
- 如果找到一个干净的Buffer,则直接使用它。
- 如果找到一个脏的Buffer,则会将其从LRU主链摘下,并挂到LRUW辅链上,然后继续寻找。
-
LRUW辅链 (LRUW List - Write List):
- 作用:链接所有脏Buffer,供DBWn(数据库写进程)批量写出到数据文件。
- 机制:当服务器进程在LRU主链上扫描到脏Buffer时,就会将其移入LRUW链。DBWn进程会定期或被触发时,扫描LRUW链,将脏块批量写入磁盘。写入成功后,这些Buffer的状态就变为干净,并被重新挂回LRU主链的MRU端。
3. 检查点队列链表 (Checkpoint Queue)
- 作用:保证数据一致性和实现增量检查点(Incremental Checkpoint)。这是一个按脏块第一次被修改的时间(Low RBA - Redo Block Address)顺序排列的链表。
- 机制:
- 每当一个Buffer第一次变脏时,它的Buffer Header就会被挂到检查点队列的尾部。
- 这个队列的顺序非常重要,它代表了数据块产生重做日志的顺序。
- 当发生检查点(Checkpoint) 时,CKPT进程会通知DBWn将检查点队列中一定位置(Target RBA)之前的所有脏块写入磁盘。
- 写入后,检查点位置(CKPT的
RBA指针)会向前推进。这保证了在实例恢复时,只需要从最后一个检查点位置开始应用重做日志,大大缩短了恢复时间。
- 与LRUW的区别:
- LRUW:目的是为了释放内存空间(给读让路),其顺序与访问频率相关。
- 检查点队列:目的是为了数据恢复,其顺序与修改发生的顺序严格一致。
三、数据访问过程举例 (Example: Data Access Flow)
让我们通过一个SELECT * FROM emp WHERE empno=7788;语句,将上述原理串联起来:
- 解析SQL:服务器进程解析SQL,确定要访问
EMP表。 - 计算DBA:根据
empno=7788的行所在的数据文件号和块号,计算其DBA。 - Hash查找:用
DBA计算Hash值,找到对应的Hash Bucket链表,遍历链表。 - 缓存命中(Cache Hit):
- 如果在链表中找到了对应的Buffer Header,说明数据已在内存中。
- 服务器进程直接从Buffer中读取数据。
- 并将这个Buffer移动到LRU主链的MRU热端。
- 返回结果给用户。整个过程无物理I/O。
- 缓存未命中(Cache Miss):
- 如果在Hash链表中没找到,说明数据不在内存中,必须从磁盘读取。
- 寻找空闲Buffer:服务器进程从LRU主链的冷端开始扫描。
- 如果找到一个干净Buffer,直接使用。
- 如果找到一个脏Buffer,将其从LRU主链摘下,挂到LRUW辅链上,然后继续扫描。
- 触发DBWn:如果扫描的脏Buffer数量达到一个阈值(
_DB_BLOCK_MAX_SCAN_COUNT),服务器进程会通知DBWn进行增量写出,以腾出更多空闲Buffer。 - 读取磁盘:找到空闲Buffer后,服务器进程发起物理I/O,将数据块从磁盘读入这个Buffer。
- 更新链表:
- 更新Buffer Header中的DBA等信息。
- 将其挂到对应的Hash Bucket链表上。
- 将其挂到LRU主链的MRU热端。
- 因为只是读,没有修改,所以它是干净的,不会进入检查点队列。
- 返回结果给用户。
如果是UPDATE操作:在找到Buffer后,修改数据前,会先生成重做日志(Redo Entry)。修改Buffer中的数据,使其变为脏块。此时,这个Buffer会被:
- 移动到LRU主链的MRU热端(因为它刚被访问)。
- 如果它是第一次被修改,它的Buffer Header还会被加入到检查点队列的尾部。
第二部分:争用、等待事件与排查解决
当多个进程并发访问Buffer Cache时,对链结构的操作(如扫描LRU、链表的移动)都需要 latch来保护。 latch是轻量级的串行化锁,高并发下容易引发争用。
1. 缓存繁忙等待 (Cache Buffer Chains Latch)
- 场景:频繁访问同一个"热"数据块(例如小表的全表扫描、索引根块/分支块、频繁修改的行)。
- 原理:多个进程同时访问同一个Hash Bucket链表,争用保护这个链表的CBC latch。
- 等待事件:
latch: cache buffers chains - 排查SQL:
SELECT * FROM ( SELECT o.OBJECT_NAME, o.OBJECT_TYPE, bh.HLADDR, bh.FILE#, bh.DBABLK, bh.TCH, bh.STATUS FROM V$BH bh, DBA_OBJECTS o WHERE bh.OBJD = o.DATA_OBJECT_ID AND bh.HLADDR = '&latch_address' -- 从等待事件中获取P1RAW或ADDR ORDER BY bh.TCH DESC ) WHERE ROWNUM <= 10;HLADDR:latch地址,对应等待事件的P1RAW或ADDR。TCH:访问次数(Touch Count),TCH高的块就是热块。
- 解决方案:
- 优化SQL:避免低效的SQL访问大量数据。减少对热块的访问。
- 使用缓存:对于小但极热的标准(如编码表),可以考虑应用层缓存。
- 反向索引:对于序列增长的索引,将热点分散到不同的索引叶块上。
- 增大
_SPIN_COUNT:轻微争用时,增加latch的自旋次数可能有效(需谨慎)。 - Hash Bucket太少:极少数情况下,因Bug或版本问题,可尝试调整
_DB_BLOCK_HASH_BUCKETS(极不推荐,需Oracle Support协助)。
2. 缓冲区忙等待 (Buffer Busy Waits)
- 场景:多个进程试图以不兼容的模式访问同一个数据块(例如,一个进程在读,另一个进程要写)。
- 原理:这是对Buffer本身的争用,而不是保护链表的latch。Buffer Header中有状态标志。
- 等待事件:
buffer busy waits - 排查SQL:
SELECT o.OBJECT_NAME, o.OBJECT_TYPE, w.P1 FILE_ID, w.P2 BLOCK_ID, w.P3 REASON_CODE FROM V$SESSION_WAIT w, DBA_OBJECTS o, V$BH bh WHERE w.EVENT = 'buffer busy waits' AND bh.FILEID = w.P1 AND bh.BLOCK# = w.P2 AND bh.OBJD = o.DATA_OBJECT_ID;P3(REASON_CODE)解释了等待原因(例如,200-读时等待另一个会话的读结束)。
- 解决方案:
- 热点块:解决方案与CBC latch争用类似,核心是分散热点。
- 增加
FREELISTS/ASSM:对于DML频繁的表,确保有足够的空闲列表来管理块内的空间,减少事务间的块竞争。 - 减小块大小:考虑使用更小的数据块大小(如4K vs 8K),但这是一把双刃剑。
- PCTFREE:适当增大
PCTFREE,减少行迁移和链化,也能减少块内的竞争。
3. 空闲缓冲区等待 (Free Buffer Waits)
- 场景:服务器进程需要从磁盘读数据块到Buffer Cache,但在LRU链表上找不到足够的空闲(Free)或可覆盖(Clean)的Buffer。
- 原理:
- Buffer Cache太小,不足以容纳工作集。
- DBWn写出速度太慢,无法及时将脏Buffer刷入磁盘以腾出空间。
- 等待事件:
free buffer waits - 排查:检查
V$SYSTEM_EVENT中该等待事件的总等待时间。同时检查I/O系统的性能(V$FILESTAT)。 - 解决方案:
- 增大Buffer Cache:这是最直接的方法 (
DB_CACHE_SIZE)。 - 优化DBWn写出:
- 增加
DB_WRITER_PROCESSES(DBWn进程数),特别是多CPU系统。 - 启用异步I/O(如果OS和存储支持),提升DBWn的写入效率。
- 检查存储性能,确保I/O子系统没有瓶颈。
- 增加
- 检查点优化:确保增量检查点正常推进,
V$INSTANCE_RECOVERY中的TARGET_MTTR是合理的。
- 增大Buffer Cache:这是最直接的方法 (
4. 写完成等待 (Write Complete Waits)
- 场景:一个进程需要修改一个数据块,但这个数据块当前正被DBWn进程写入磁盘(即正在I/O过程中)。
- 原理:进程必须等待这个写操作完成才能继续修改,否则会破坏数据一致性。
- 等待事件:
write complete waits - 解决方案:与
free buffer waits类似,核心是优化DBWn的写入性能和减少不必要的密集写操作(如过度的检查点)。
第三部分:常用监控与诊断SQL
-
查看Buffer Cache命中率:
SELECT (1 - (phy.VALUE / (cur.VALUE + con.VALUE))) * 100 "Hit Ratio %" FROM V$SYSSTAT cur, V$SYSSTAT con, V$SYSSTAT phy WHERE cur.NAME = 'db block gets' AND con.NAME = 'consistent gets' AND phy.NAME = 'physical reads';- 通常应 > 90%,但需结合具体业务看。100%不一定是最优,可能意味着内存浪费。
-
查看等待事件排名:
SELECT EVENT, TOTAL_WAITS, TIME_WAITED_MICRO FROM V$SYSTEM_EVENT WHERE EVENT LIKE '%buffer%' ORDER BY TIME_WAITED_MICRO DESC; -
查看最热的数据块(Top N热点块):
SELECT o.OBJECT_NAME, bh.DBABLK, bh.TCH, bh.STATUS FROM V$BH bh, DBA_OBJECTS o WHERE bh.OBJD = o.DATA_OBJECT_ID AND o.OBJECT_NAME = '&YOUR_TABLE_NAME' ORDER BY bh.TCH DESC; -
监控检查点进度:
SELECT TARGET_MTTR, ESTIMATED_MTTR, CKPT_BLOCK_WRITES FROM V$INSTANCE_RECOVERY;
总结
Oracle Buffer Cache是一个复杂而精妙的系统,它通过Hash链表实现快速查找,通过LRU/LRUW链表实现缓冲区的淘汰和写出,通过检查点队列保证数据的一致性和快速恢复。理解其内部机制,能够帮助我们精准地定位和解决数据库性能瓶颈,特别是与内存和I/O相关的等待事件。调优的本质,就是在内存(缓存)、I/O(磁盘)和CPU(latch争用)之间找到一个最佳的平衡点。
欢迎关注我的公众号《IT小Chen》
681

被折叠的 条评论
为什么被折叠?



