[English]
作者:
fuyuncat
来源:
www.HelloDBA.com
近日收到读者邮件,对《Oracle 高性能SQL引擎剖析》中6.3.2节的内容提出了一些疑问,尤其对其中使用的视图 v_bufheaders 是如何获得替换链表中buffer header的位置感到迷惑。我在这将比较详细的解释一下该视图及其作用。
我们知道,Buffer Cache是Oracle的内存架构体系中相当重要的一个部分,用于缓存从数据文件上读取的数据块,因而对数据库的性能有相当大的影响。为了能使这块内存能对性能提高最大化,Oracle采用的是LRU算法来管理Buffer Cache中的内存块的分配和释放。关于LRU算法,大多数读者都已经不是很陌生,Oracle的官方文档和其他书籍、文章都有做描述,包括用图解的方式对其作过描述。但是,在一个实际的数据库系统中,我们很难判断出某个数据块处在替换链表中的具体位置(可以system event打印出整个链表到跟踪文件中,但是这个方法比较低效,且可读性不强)。我们这个视图就是用于判断内存块在替换链表、hash链表中的位置以及其他相关信息。
这个视图最核心的基础表是X$BH————存储了Buffer Header的基础信息,而Buffer Header则是Oracle用于管理Buffer Block的数据结构,其中包含了内存块在各个链表中的位置信息。然而这个位置信息并非一个序列号这样的简单数据,而是包含了一对指针:NXT_REPL和PRV_REPL(其它链表类似),分别存储了指向反向链表和正向链表中下一节点中的链表指针的内存地址。这样的描述可能读者比较难以理解。但是如果你了解了什么是X$表,就会比较清楚其含义了。
X$表实际上不是传统的关系表,而是Oracle中的内存结构。Oracle通过特殊SQL引擎组件,使得我们能对这些内存结构中的数据像查询普通关系表一样进行查询。对于X$BH,如果我们想要查询出每个内存块在链表中的位置,则需要让表中记录构建出前后关系,然后用层次查询获得其链表位置信息。
X$BH的结构体定义类似如下(我们这里需要构造出链表信息,因此不关系其他成员的定义):
C++代码
- struct BUFFER_HEADER
- {
- ub2 le_addr
- ...
- CHAIN_NODE us_chain;
- CHAIN_NODE wa_chain;
- ...
- CHAIN_NODE hash_chain;
- CHAIN_NODE repl_chain;
- ...
- };
其中 CHAIN_NODE 也是一个结构体,定义了一组链表的正反向指针。定义如下:
C++代码
- struct CHAIN_NODE
- {
- CHAIN_NODE *prv_prt;
- CHAIN_NODE *nxt_prt;
- };
这样的数据结构,在程序语言中很容易实现遍历和管理:
C++代码
- CHAIN_NODE *startPoint = &(first_node.repl_chain);
- CHAIN_NODE *nextPoint = startPoint->nxt_prt;
- while (nextPoint != startPoint) {
- nextPoint = nextPoint->nxt_prt;
- ...
- }
但是,这样的数据在表中则难以构造出相互关系。因此,我们需要将这些数据做一些转变,将它们转换为关系型数据:
1、每条记录需要有一个键值;
2、指针字段中存储的不是下一指针的内存地址,而是键值。在这个数据结构中,键值的最理想数据则为Buffer Header本身的内存地址。但X$BH中不存在该数据。因此我们需要从已有数据中构造出该数据。如何构造出该数据,就需要了解已有数据和Buffer Header的内存地址之间的关系。这里我们需要参考另外一张X$表:X$KQFCO。
表X$KQFCO描述了每个结构体中的成员(包括嵌套结构体的成员)类型和及该成员在数据结构中的偏移量。例如,假如某个Buffer Header的基础地址为0x30000000,加上偏移量,我们就可以知道每个成员的内存地址。SQL代码
- HELLODBA.COM>select kqfconam, kqfcooff, '0x'||trim(to_char(to_number('30000000','XXXXXXXX')+kqfcooff, 'XXXXXXXX')) addr
- 2 from x$kqfco c,x$kqfta t
- 3 where t.indx = c.kqfcotab and t.kqftanam='X$BH'
- 4 and kqfcodty = 23
- 5 order by kqfcooff;
- KQFCONAM KQFCOOFF ADDR
- ------------------------------ ---------- -----------
- ADDR 0 0x30000000
- HLADDR 0 0x30000000
- LE_ADDR 0 0x30000000
- US_NXT 16 0x30000010
- US_PRV 20 0x30000014
- WA_NXT 24 0x30000018
- WA_PRV 28 0x3000001C
- NXT_HASH 124 0x3000007C
- PRV_HASH 128 0x30000080
- NXT_REPL 172 0x300000AC
- PRV_REPL 176 0x300000B0
- BA 188 0x300000BC
- SET_DS 192 0x300000C0
- OQ_NXT 196 0x300000C4
- OQ_PRV 200 0x300000C8
- AQ_NXT 204 0x300000CC
- AQ_PRV 208 0x300000D0
但是,获得每个成员的内存地址好像还是不能直接帮助我们将内存结构数据转换为关系型数据。
这里,我们还需要了解Oracle的buffer cache管理的其他信息:除了替换链表,还有其他链表用于其他管理方法。例如,用于Latch管理的用户持有链表(US_NXT、US_PRV)和等待链表(WA_NXT、WA_PRV)。和内存块替换管理不同,这些事件并非始终出现在系统中。因此,很多时候,大多数数据块并未被放入相应链表中。也就是说,Buffer Header的相应指针并未指向链表节点。此时,结构体CHAIN_NODE成员指针所指向的内存地址实际上是该结构体本身的内存地址。
SQL代码
- HELLODBA.COM>select us_nxt, us_prv, wa_nxt, wa_prv from x$bh where rownum<=6;
- US_NXT US_PRV WA_NXT WA_PRV
- -------- -------- -------- --------
- 353EFABC 353EFABC 353EFAC4 353EFAC4
- 313F9EB4 313F9EB4 313F9EBC 313F9EBC
- 35BEFEE0 35BEFEE0 35BEFEE8 35BEFEE8
- 30BEAE8C 30BEAE8C 30BEAE94 30BEAE94
- 387E9ED0 387E9ED0 387E9ED8 387E9ED8
- 2EFE940C 2EFE940C 2EFE9414 2EFE9414
- 6 rows selected.
可以看到,NXT指针和PRV指针的内容相同。实际上,这些地址就是当前buffer header数据结构中相应CHAIN_NODE的内存地址,因为NXT是其第一个成员,实际上就是buffer header数据结构中的NXT的内存地址。这种情况下,我们就不难获得buffer header本身的内存地址。以US_NXT为例,其值减去偏移量(16)就是buffer header本身的内存地址。看上例中第一条记录:
Buffer Header Address = US_NXT - 16 = WA_NXT - 24
= 0x353EFABC - 16 = 0x353EFAC4 - 24
= 0x353EFAAC理论上,一个内存块不会同时处在用户持有链表和等待链表链表上,因此我们应该可以通过这两组数据可以获得Buffer Header的内存地址。
同样道理,在替换链表的CHAIN_NODE中的成员指向的是下一节点的Buffer Header的中替换链表结构体CHAIN_NODE的地址。通过减去偏移量,就可以得到其指向的Buffer Header的内存地址。举个例子,假如内存中只有3个内存块,它们不处在用户持有链表上,那么它们之间的数据关系如下(图画得简陋,将就看吧...):
C++代码
- BufferHeader(1) BufferHeader(2) BufferHeader(3)
- [0X30000000] [0X30001000] [0X30002000]
- { { {
- ... ... ...
- +----+ +----+ +----+
- | | | | | |
- ( 16)US_NXT<-+ ( 16)US_NXT<-+ ( 16)US_NXT<-+
- /|\ /|\ /|\
- | | |
- ( 20)US_PRV ( 20)US_PRV ( 20)US_PRV
- ... ... ...
- +---------------------------------------------------------+
- | |
- +--(172)NXT_REPL<------(172)NXT_REPL<------(172)NXT_REPL<-+
- /|\ /|\ /|\
- +-------+ +---------------+ +---------------+
- | | |
- | (176)PRV_REPL (176)PRV_REPL (176)PRV_REPL--+
- | |
- +---------------------------------------------------------+
- ... ... ...
- } } }
US_NXT和US_PRV都指向了本身的US_NXT的地址。而NXT_REPL和PRV_REPL都指向了不同方向的下一Buffer Header的NXT_REPL的地址,并且构成一个回链(实际上,每个Buffer Cache集可能会分为主、辅两个数据链)。
按照这样的转换思路,我们可以为每个Buffer Header设定一个键值,即其内存地址;一个指向下一Buffer Header键值的“字段”。这是一个典型的层次关系数据。但我们还缺少一个关键数据:链表的第一个节点。而这个数据可以从Buffer Cache数据集的数据结构表X$KCBWDS中获得。该数据结构中,存在两个指针:NXT_REPL和PRV_REPL,分别指向了主替换链表的MRU端和LRU端的第一个Buffer Header的NXT_REPL的地址。另外一组指针:NXT_REPLAX和PRV_REPLAX,则分别指向了辅助替换链表的MRU端和LRU端的第一个Buffer Header的NXT_REPL的地址。同样,减去偏移量后就是Buffer Header的内存地址。
有了这些数据后,我们不难通过层次查询获得完整的替换链表。按照这个思路,我们创建了视图V_BUFLIST。
此外,还创建另外一个视图v_bufhashlist反映出内存所处hash链表的位置。再关联其他基础表,创建了视图 v_bufheaders (具体定义参考随书脚本)。
而在X$KCBWDS中,HBUFS表示链表热区中的内存块数量。因为我们获得的序列号是从1开始递增的,因此HBUFS也就可以视为热区最后一块内存块的序列号,其下一节点即插入点的位置。这样,我们就可以获得主、辅链表的头尾节点、插入点等关键内存块的信息,进而可以对Oracle的Buffer Cache替换算法进行深入探索和研究:
SQL代码
- HELLODBA.COM>select /*+no_merge(v)*/
- 2 b.set_id, b.listname, b.indx, headeraddr, obj, dbms_utility.make_data_block_address(dbar
- fil, dbablk) dba, tch
- 3 from v_bufheaders b,
- 4 (select set_id, listname, max(indx) last_id
- 5 from v_buflist
- 6 group by set_id, listname) v
- 7 where b.set_id = v.set_id
- 8 and b.listname = v.listname
- 9 and b.indx >= v.last_id-2
- 10 union
- 11 select set_id, listname, indx, headeraddr, obj, dbms_utility.make_data_block_address(dbarfil,
- dbablk) dba, tch
- 12 from v_bufheaders b
- 13 where b.indx in 1
- 14 or b.indx between b.hbufs and b.hbufs+2
- 15 order by set_id, listname desc, indx;
- SET_ID LISTNAME INDX HEADERADDR OBJ DBA TCH
- ---------- --------- ---------- ------------------ ---------- ---------- ----------
- 9 REPL_MAIN 1 2D7E4F40 248 4196126 1
- 9 REPL_MAIN 2 2D7E5014 233 4206545 1
- 9 REPL_MAIN 4689 337FAA3C 6215 8392894 1
- 9 REPL_MAIN 4690 333E7208 254 4264246 1
- 9 REPL_MAIN 4691 2DFF25BC 6369 8393883 1
- 9 REPL_AUX 1 303F1458 473 8448867 0
- 9 REPL_AUX 2 303F1384 473 8448871 0
- 9 REPL_AUX 704 2EFFA2C8 473 8469751 0
- 9 REPL_AUX 705 2EFFA1F4 473 8469755 0
- 9 REPL_AUX 706 2EFFA120 473 8469759 0
- ...
沿寻这样的思路,读者还可以构造出其它链表来分析其它的Buffer Cache相关的问题。
--- Fuyuncat ---