
好的,我们将深入探讨 Oracle Buffer Cache 中另一个至关重要的内部机制:LRU(Least Recently Used)算法 及其相关的链表结构。这部分内容直接关系到数据库如何高效地利用有限的内存来缓存数据,是理解性能等待事件 “Free Buffer Waits” 的关键。
1. LRU 算法的核心概念
官方/专业解释
LRU(最近最少使用)是一种经典的缓存淘汰算法。其核心思想是:当缓存空间不足时,优先淘汰那些最长时间没有被访问过的数据。Oracle Buffer Cache 使用一种增强型、多列表的 LRU 算法来管理缓冲区。
通俗解释
把它想象成一个 “图书馆的阅览区管理规则”:
阅览区的桌子(Buffer Cache)是有限的。当新书(数据块)需要被拿进来时,图书管理员(Oracle)必须决定把哪本旧书移走。LRU 规则就是:把那个最长时间没人看的书移走。这看起来很合理,因为如果一本书很久没人看,它短期内再被看的概率也可能很低。
2. LRU 链表结构:主链表与辅助链表
Oracle 并没有使用一个单一的 LRU 链表,而是将其分为两个主要部分以提高效率和并发性:主 LRU 链表(Main LRU) 和 辅助 LRU 链表(Auxiliary LRU 或 LRU List)。有时也会被称为 冷端(Cold End) 和 热端(Hot End)。
官方/专业解释
-
辅助 LRU 链表 (Auxiliary LRU / REPL_AUX)
- 内容: 主要包含空闲缓冲区(Free Buffers) 和那些即将被移出内存的、可重用的缓冲区。
- 位置: 链表的尾部(Tail) 被认为是“冷端”,是缓冲区被淘汰的起点。
- 作用: 充当缓冲区重用的主要来源。当服务器进程需要空闲缓冲区时,首先会扫描这个链表。
-
主 LRU 链表 (Main LRU / REPL_MAIN)
- 内容: 包含当前正在被使用的缓冲区(Pin住的)和那些被频繁访问的、“热”的缓冲区。
- 位置: 链表的头部(Head) 被认为是“热端”,是最近被访问过的缓冲区。
- 作用: 保护热点数据,避免其被过早地淘汰出缓存。
- 移动规则(Touch Count):
- 当一个缓冲区被访问(例如逻辑读)时,它的 “访问计数(Touch Count)” 会增加。
- 如果该缓冲区的 Touch Count 超过一个内部阈值,它就有资格从 LRU 链表的当前位置移动到头部(MRU端)。
- 这个机制确保了频繁访问的块会聚集在链表头部,而不常访问的块会逐渐向尾部移动。
通俗解释
阅览区规则升级了:
- 辅助 LRU 链表(冷端): 靠近还书口的区域。这里的书要么是空的桌子(空闲缓冲区),要么是刚被还回来、还没人再次借阅的书(可重用的缓冲区)。管理员首先来这里找可以放新书的空位。
- 主 LRU 链表(热端): 阅览区的核心区域。
- 每当一本书被读者阅读(缓冲区被访问),它就会得到一个“积分”(Touch Count 增加)。
- 积分高的热门书(热点块)会被管理员放到离入口最近、最显眼的位置(链表头部),方便大家下次快速取阅。
- 积分低、没人看的书(冷块)会慢慢被挤到靠近还书口的位置(链表尾部),最终被收走(淘汰)。
这个过程实现了缓冲区的自然淘汰:冷数据从主链表的尾部被转移到辅助链表,最终被重用。
3. 脏链表 (LRUW / Write List)
官方/专业解释
脏链表(LRUW),也称为写列表(Write List),是一个专门用于链接所有脏缓冲区(Dirty Buffers) 的链表。请注意,这与我们之前讨论的检查点队列(CQ) 是不同的链表,目的不同:
- 检查点队列 (CQ): 按首次变脏的时间(Low RBA) 排序,用于实例恢复和增量检查点。
- 脏链表 (LRUW): 主要目的是当服务器进程需要自由缓冲区时,DBWR 可以快速找到哪些缓冲区是脏的并将其写入磁盘,从而腾出空间。
一个脏块的 Buffer Header 会同时存在于 LRU 链表 和 脏链表(LRUW) 上。
通俗解释
继续图书馆的比喻:
- 脏块: 被读者涂改过的书。
- 脏链表 (LRUW): 图书管理员有一个专门的清单,上面只记录所有被涂改过的书。这个清单是为了快速整理阅览区而用的。
- 检查点队列 (CQ): 管理员还有另一个清单,按涂改发生的时间顺序记录这些书。这个清单是为了定期存档和灾难恢复用的。
当阅览区桌子不够了(需要自由缓冲区),管理员会:
- 查看“被涂改书清单”(LRUW)。
- 按照清单,把这些书的内容抄写到永久档案(数据文件)中。
- 把书上的涂改擦干净(缓冲区变干净)。
- 然后把这本书放到“可重用书区”(辅助LRU链表的尾部)。
4. Free Buffer Waits 等待事件
官方/专业解释
当服务器进程需要将一个新数据块读入 Buffer Cache 时,它必须在 LRU 链表上找到一个可用的、空闲的(Non-Dirty)缓冲区。如果它扫描了 LRU 链表一定深度(由 _DB_BLOCK_MAX_SCAN_COUNT 隐含参数控制)后仍然找不到可用的空闲缓冲区,它就会停止搜索,并记录一次 “Free Buffer Waits” 等待事件。此时,该进程会通知 DBWR 进程立即将一些脏缓冲区写入磁盘以腾出空间,并等待 DBWR 完成工作。
通俗解释
一个读者(服务器进程)想从书库拿一本新书到阅览区。
- 他先去“可重用书区”(辅助LRU尾部)找空桌子或可以清走的书。
- 他找啊找,找了一圈,发现所有的桌子上都放着书,而且很多都是被涂改过的(脏块),没有空位。
- 他没办法了,只好举手告诉管理员(DBWR):“我没地方放新书了,你快来把一些涂改过的书抄写归档,然后把桌子腾出来!”
- 然后他就停下来等待(Free Buffer Waits),直到管理员清理出空桌子。
“Free Buffer Waits” 事件的高发通常意味着:
- DBWR 写入速度跟不上脏块产生的速度: 产生脏块太快(大量DML操作),而磁盘I/O速度太慢。
- Buffer Cache 可能太小: 不足以容纳当前的工作集(Working Set)。
5. 日志切换与写脏块
- 官方/专业解释: 日志切换(Log Switch)是当前在线重做日志组写满后,Oracle 切换到下一个可用的日志组的过程。在日志切换时,会触发一个检查点(Checkpoint)。这个检查点会促使 DBWR 将检查点队列(CQ)上的一部分脏块(从队首开始)写入数据文件,从而确保所有在即将被覆盖的重做日志中记录的更改都已物化到数据文件中。这是保证数据库可恢复性的关键机制。
- 通俗解释: 记录游戏操作的“当前记事本”写满了。在换新本子之前,系统必须根据“待抄写清单”(CQ),把本子里记录的最早的一些操作(对应最早的脏块)真正地存档(写入数据文件)。这样,即使游戏崩溃,因为旧操作已经存档,只需要用新本子里的操作来恢复就可以了,旧本子就可以安全地覆盖重用了。
6. 相关查询与管理 SQL 命令
A. 监控 LRU 和 Buffer Cache 效率
-- 查看寻找空闲缓冲区的成本(如果较高,说明寻找困难)
SELECT name, value
FROM v$sysstat
WHERE name IN ('free buffer requested', 'free buffer inspected');
-- 查看Buffer Cache的总体命中率(理想应>90%)
SELECT 1 - (phy.value / (cur.value + con.value)) "Buffer Cache 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';
B. 监控 Free Buffer Waits
-- 查看系统级的Free Buffer Waits等待事件
SELECT event, total_waits, time_waited, average_wait
FROM v$system_event
WHERE event = 'free buffer waits';
-- 结合时间点查看,判断是否在特定时期高发
C. 优化策略与管理命令
-
优化DBWR写入:(如前文所述,是解决Free Buffer Waits的根本)
- 增加DBWR进程数。
- 启用异步I/O。
- 优化存储I/O性能。
ALTER SYSTEM SET db_writer_processes = 4 SCOPE=SPFILE; -
增加Buffer Cache大小:
- 如果命中率低且Free Buffer Waits高,可能是Cache太小。
-- 查看当前大小 SELECT component, current_size/1024/1024 "Size (MB)" FROM v$sga_dynamic_components WHERE component = 'DEFAULT buffer cache'; -- 动态调整(假设使用自动内存管理AMM或ASMM) ALTER SYSTEM SET db_cache_size = 2G; -- 增大Cache -
使用多缓冲池:
- 将频繁访问的小表固定(Pin)在Keep Pool中,避免被淘汰。
- 将偶尔进行全表扫描的大表放入Recycle Pool,避免其污染默认池。
-- 创建Keep Pool ALTER SYSTEM SET db_keep_cache_size = 500M; -- 将表指定到Keep Pool CREATE TABLE my_important_table (...) STORAGE (BUFFER_POOL KEEP); -- 或修改现有表 ALTER TABLE my_important_table STORAGE (BUFFER_POOL KEEP);
总结
| 组件/概念 | 官方角色 | 通俗比喻 | 关键点 |
| :— | :— | :— | :— |
| 主LRU链表 | 管理正在使用和热门的缓冲区 | 阅览区核心区,放热门书 | 通过Touch Count将块移至MRU端 |
| 辅助LRU链表 | 提供空闲和可重用的缓冲区 | 靠近还书口的区域 | 服务器进程寻找空闲缓冲区的起点 |
| 脏链表 (LRUW) | 链接所有脏缓冲区,用于快速腾空 | 被涂改书的专用清单 | 服务于自由缓冲区请求 |
| Free Buffer Waits | 等待DBWR写入脏块以腾出空间 | 读者等待管理员清理桌子 | DBWR写入慢或Cache太小的标志 |
| 日志切换 | 触发检查点,促使DBWR写脏块 | 换记事本前的存档操作 | 保证数据库可恢复性的关键机制 |
通过理解 LRU 链表的工作机制,你可以更深刻地洞察 Buffer Cache 的内部运作,并能够有效地诊断和解决诸如 Free Buffer Waits 这类常见的性能瓶颈,从而确保数据库高效稳定地运行。
欢迎关注我的公众号《IT小Chen》
675

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



