
好的,我们将深入剖析 Oracle 数据库核心内存结构——Buffer Cache 的内部工作原理。理解这部分内容对于进行高性能数据库调优和故障诊断至关重要。
1. Buffer Cache 的核心作用与原理
官方/专业解释
Buffer Cache 是系统全局区(SGA)的一个重要组件。它的主要目的是缓存从数据文件中读取的数据块的副本,从而最小化物理 I/O(磁盘读取)。当进程需要访问数据时,它首先在 Buffer Cache 中寻找。如果找到(称为缓存命中),则直接进行内存访问,速度极快;如果未找到(称为缓存未命中),则必须从磁盘将所需数据块读入 Cache,然后才能访问。
通俗解释
把它想象成一个图书馆的阅览区(Buffer Cache),而磁盘就是后面的大书库(Datafiles)。
- 读者(数据库进程)想看书(数据块)。
- 他首先会去阅览区看看这本书是否已经被其他读者放在桌上了。
- 如果找到了(逻辑读),他可以直接拿起来读,非常快。
- 如果没找到(物理读),他必须去后面的大书库(磁盘)找到这本书,然后把它拿到阅览区(读入 Buffer Cache)的某个桌子上,然后才能阅读。
阅览区的桌子是有限的,当新书需要被拿进来时,可能需要把一些很久没人看的旧书(冷数据)放回书库(写入磁盘并腾出空间),这个过程由LRU算法管理。
2. 核心数据结构:HASH 链表与 Cache Buffer Chain (CBC)
为了在巨大的 Buffer Cache 中快速定位一个数据块,Oracle 使用了基于哈希算法的链表结构。
官方/专业解释
-
Hash Bucket(哈希桶):
- Oracle 对每个数据块的唯一标识 DBA (Data Block Address) —— 由
文件号(RFN)和块号(Block#)组成——应用一个哈希函数,得到一个哈希值。 - 这个哈希值对应一个 Hash Bucket。可以将 Hash Bucket 看作一个数组槽位。
- Oracle 对每个数据块的唯一标识 DBA (Data Block Address) —— 由
-
Cache Buffer Chain (CBC,缓存缓冲链):
- 每个 Hash Bucket 背后都链接着一个双向链表,这个链表就是 CBC。
- 链表中每个节点都是一个 Buffer Header,它指向 SGA 中一个具体的缓冲区块(Buffer),并包含该缓冲区块的元数据(如状态、SCN、模式等)。
- 具有相同哈希值的不同数据块(即发生了哈希碰撞)的 Buffer Header 会被链接到同一个 CBC 上。
通俗解释
继续用图书馆的比喻:
- DBA (文件号+块号): 是每本书的唯一索书号。
- 哈希函数: 是一个固定的规则,比如“取索书号的最后两位”。
- Hash Bucket: 是阅览区入口处一排编号为00-99的指示牌。
- Cache Buffer Chain (CBC): 每个指示牌下面挂着一个清单,清单上列出了所有“索书号最后两位”与该指示牌编号相同的书当前放在了哪张桌子上。
- 例如,索书号
12345和98345的最后两位都是45,所以它们都在45号指示牌下的清单上。
- 例如,索书号
当读者(进程)想找书时:
- 查看书的索书号(DBA)。
- 用规则计算最后两位(哈希函数得到 bucket id)。
- 走到对应的指示牌下(定位到 Hash Bucket)。
- 浏览下面的清单(遍历 CBC 链表),对比完整的索书号,找到目标书所在的桌子(Buffer Header 和对应的 Buffer)。
3. Cache Buffer Chain Latch(CBC 闩锁)
官方/专业解释
闩锁(Latch)是一种轻量级、短时间的锁,用于保护共享内存结构的完整性和一致性。CBC Latch 就是用来保护特定的 Cache Buffer Chain 不被并发访问破坏的。
- 一个进程在遍历某个 CBC 链表(即搜索一个 buffer)之前,必须先获得与该 Hash Bucket 相关联的 CBC Latch(通常是以共享模式获得)。
- 如果进程需要修改链表本身的结构(例如,因为缓存未命中而需要将一个新的 Buffer Header 链接到链表上),它就需要以独占模式获取这个 CBC Latch。
通俗解释
CBC Latch 就像是阅览区每个指示牌清单的“一把小锁”。
- 共享模式(Share Mode): 多个读者可以同时拿着清单(持有 latch) 只是浏览和查找(读操作),互不干扰。
- 独占模式(Exclusive Mode): 如果一个管理员需要更新清单(比如添一本新书或划掉一本书),他需要独占这把锁。在他更新清单的短暂瞬间,其他所有读者都不能看这个清单,必须等待。
CBC Latch 争用:
如果有很多很多读者都同时想去看 45 号指示牌下的清单(即频繁访问属于同一个 Hash Bucket 的数据块,称为热点块),他们就会在清单旁边排起长队,等待前一个人看完归还清单(释放 latch)。这个排队现象就是 “CBC Latch 争用”,是常见的性能瓶颈。
4. Buffer Pin(缓冲区块钉锁)
官方/专业解释
Buffer Pin 是一种机制,用于在进程访问或修改缓冲区块中的具体内容期间,防止该缓冲区被移出 Buffer Cache 或被其他进程以不兼容的模式访问。
- 进程在 CBC 链路上找到所需的 Buffer Header 后,会先获取 CBC Latch。
- 然后它“钉住”(Pin)这个缓冲区(增加 Pin 计数)。
- 随后,它就可以释放 CBC Latch 了。
- 现在,它可以在没有 CBC Latch 的情况下安全地读取或修改缓冲区中的内容,因为 Pin 计数保证了缓冲区不会被驱逐。
- 操作完成后,它必须再次获取 CBC Latch,减少 Pin 计数(“解钉”),然后释放 CBC Latch。
通俗解释
Buffer Pin 就像是“在看书时在书上贴一个便签:正在使用,勿动”。
- 读者走到
45号指示牌下,拿到清单和锁(获取 CBC Latch)。 - 他在清单上找到书桌号
A15,然后在那本书上贴了个“正在阅读”的便签(Pin住Buffer)。 - 他把清单和锁还了回去(释放 CBC Latch)。其他读者现在可以查阅清单了,减少了排队。
- 他坐在桌子
A15前安心地阅读(甚至做笔记)很长时间。因为有便签(Pin),图书管理员绝不会在他读的时候把这本书收走。 - 他读完后,需要再次拿到清单和锁(获取 CBC Latch),撕掉便签(解Pin),再把清单和锁还回去(释放 CBC Latch)。
关键点: CBC Latch 保护的是**“清单”(CBC链表),而 Buffer Pin 保护的是“书本身”(Buffer中的内容)**。通过“钉住后释放Latch”的机制,极大地减少了持有Latch的时间,提高了并发性。
5. 逻辑读的完整过程
结合以上所有概念,一个逻辑读(从内存读取)的完整流程如下:
- 计算Hash值: 进程根据要访问的数据块DBA(文件号,块号)计算哈希值,定位到对应的 Hash Bucket。
- 获取CBC Latch(共享模式): 进程获取保护这个CBC链的latch。
- 遍历CBC链表: 进程遍历链表,对比Buffer Header中的DBA,寻找所需的块。
- Pin住Buffer: 找到后,进程增加该Buffer Header上的Pin计数(意味着“我正在用”)。
- 释放CBC Latch: 进程释放latch,其他进程现在可以访问这个链表了。
- 访问缓冲区: 进程根据Buffer Header的指针,读取SGA中缓冲区内的数据。
- 获取CBC Latch(共享模式): 进程再次获取latch。
- 解Pin Buffer: 进程减少Buffer的Pin计数。
- 释放CBC Latch: 进程释放latch。操作完成。
6. 相关查询与管理 SQL 命令
A. 查看Buffer Cache中的热点块(可能引起CBC Latch争用)
SELECT file#, block#,
COUNT(*) AS num_visits,
SUM(tch) AS total_touches
FROM v$bh -- v$bh视图显示了Buffer Cache中所有块的信息
WHERE tch > 10 -- touch count(访问计数)高的块可能是热点块
GROUP BY file#, block#
HAVING COUNT(*) > 1 -- 同一个块有多个副本(例如由于闪回查询)
ORDER BY total_touches DESC;
B. 诊断CBC Latch争用
-- 查看latch的总体等待情况
SELECT latch_name,
gets,
misses,
sleep_count,
round((misses / gets) * 100, 2) miss_ratio
FROM v$latch
WHERE latch_name LIKE 'cache buffer chains%'
AND gets > 0;
-- 查看哪些具体的latch地址(对应哪个Hash Bucket)等待最多
SELECT hladdr, file#, dbablk,
tch, -- touch count
(SELECT object_name FROM dba_objects o
WHERE o.data_object_id = objd) AS object_name
FROM x$bh -- 更底层的视图,需要DBA权限
WHERE tch > 100
ORDER BY tch DESC;
- 如果发现某个
hladdr(latch address) 对应的块tch值非常高,并且file#和dbablk指向同一个块,那么这个块就是热点块源头。你需要分析访问这个块的SQL语句。
C. 查看当前被Pin住的Buffer
SELECT * FROM v$buffer_pool_statistics;
-- Pin的统计信息是间接的。更直接的信息需要转储或使用内部视图。
-- 一个更深入的查询(谨慎用于诊断)
SELECT s.sid, s.username,
b.status, b.pinned_mode -- pinned_mode > 0 表示被pin住
FROM v$session s,
x$bh b,
dba_objects o
WHERE s.saddr = b.pinned_session_saddr -- 连接会话
AND b.obj = o.data_object_id;
D. 管理命令
对于CBC Latch争用,没有直接的“管理命令”可以消除它。解决方案通常是应用层面的:
- 找出访问热点块的SQL: 使用上面的查询找到热点块,然后使用
DBMS_ROWID找到对应的表和行,最终定位到相关SQL。 - 优化SQL: 优化这些SQL,减少对同一个块的频繁访问。方法包括:
- 优化索引,避免全表扫描。
- 避免使用重复率极高的索引(例如在性别字段上建索引)。
- 考虑反向键索引(Reverse Key Index)来打散序列索引产生的热点块。
- 增加PCTFREE或降低块大小,使行分布到更多块上。
- 数据库设计: 对于极其频繁的小表,可以考虑将其固定在Keep Pool中或使用内存表。
欢迎关注我的公众号《IT小Chen》
2065

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



