【转】CBC Latch的kcbgtcr: kslbegin excl 揭密

来源:这里教程网 时间:2026-03-03 15:28:27 作者:
希望此文,能给想深入研究的人一点引导。
阅读此文,需要一些基础知识。
首先,对 Oracle要熟。本文是 Cache Buffers Chain Latch相关的,要求对 Buffer Cache、逻辑读相关知识点有深入的理解。
其次,要对 C语言有基本的理解,至少要能达到等级考试二级 C语言的水平。
还有,要有基本的汇编语言基础。不需要太全面。关于这点,可以参考《深入理解计算机系统》第三章,“程序的计算机级表示”。有这一章,寥寥十数页的汇编基础,已经足够了。
 
如果基础不好,建议补一下基础,在 IT行业混,基本的算法、数据结构、计算机原理还是要懂的。
 
逻辑读Cache Buffers Chain Latch(CBC Latch) Miss分析
 
         Oracle有很多运行时信息,比如等待事件、各种资料(如 V$SYSSTATV$UNDOSTAT等)视图,理解这些信息的真正含意是关键的,否则,就如同下面这则笑话。
         B男问 A男,“你换了辆车,从哪儿搞的”。
         A男:“昨天我在山上一个人喝酒,有一个女的开车经过,停在我旁边,脱光衣服,对我说,你可以拿走你想要的,我就把她的车开走了。”
         B男:“你做的对,她的衣服你也穿不上。”
 
         很多时候, Oracle已经脱掉了“她”的衣服,你是否只开走“她”的车。
 
         很可惜,很多时候 Oracle给我们的提示,并非如此易懂,看下面的图:
<ignore_js_op>
 
有网上大神解释残局意思是:
相(想)**就快一点 否则,等我马(妈)一出来就麻烦了 你没士(事),但我有士(事) 以后给我妈锁我在家里就很难再见面( 被困) 如果你不想我死(黑方死)就快闪
 
         还有人说是这个意思:
女孩如果说想**快点,那男孩怎么会走?那个炮是黑方的,女方的,所以棋局的意思是黑方想**,但红方没棋子架炮,打也只能打 ""。翻译过来就是 :女的想**,男的没支点,只好空想。可怜的男孩纸惟有羞惭甩门而去了
 
这个残局的解释,网上多如牛毛,互相冲突的也不少,大家的说法莫衷一是。很多 Oracle问题,不也是一样吗。
继续八卦一下,蓝色的文字描述,“男孩 5秒看懂, 10分钟后摔门而出”。剩下的 955秒在干什么。嗯,这个问题,最好有当时的 AWR报告,看一看他们当时的等待事件,我估计应该剩下的 955秒,应该是在完成 I/O操作。
(注: I/O,著名 IT述语,百度百科中的解释: I/O input/output的缩写)。
 

         笑过之后,我们开始正文,我们有个库的 AWR报告中,有如下的 Latch Miss
cache buffers chains
kcbgtcr: kslbegin excl
0
1,489
1,237
cache buffers chains
kcbchg: kslbegin: bufs not pinned
0
618
657
cache buffers chains
kcbget: pin buffer
0
137
54
cache buffers chains
kcbchg: kslbegin: call CR func
0
73
409
cache buffers chains
kcbrls: kslbegin
0
65
119
………………
………………
         排第一的是 kcbgtcr: kslbegin exclSleep次数远超同类,它是干什么的。
 
长久以来,“ latch miss”就像 Oracle界的 UFO话题,代表着神秘、力量和难以理解。 Oracle明明准备了这个信息,但又不告诉我们这项信息的准确含义。如同一个 MM已经洗完澡、搞的混身香扑扑的,但又紧裹浴袍。
打开浴袍,你就可以“拿走你想要的”,注意,不要只开走她的车,我想 kcbgtcr: kslbegin excl不是让我们开走“她”的车。搞错了女孩子的意思,可是非常危险的。
 
         首先,“秀才认字看半边”, kcbgtcr,这可是大名鼎鼎的逻辑读函数。 kcbgtcr: kslbegin excl是否就是逻辑读导致的 Latch竞争呢?不一定噢。下面让我们开始我们的 Cache Buffers Chains Latch Miss之旅,揭开 Latch Miss的神秘面纱。
不过,这个面纱揭的会有点辛苦。

先从链表说起
         
         还是先从 Cache Buffers Chains(以下简称 CBC)链表说起吧。
         CBC链表长什么样,如下图:
<ignore_js_op>
前面的是 HASH表,有 NBucket,每个 Bucket都对应一条 CBC链表。每个 Bucket中,有两个指针,一个指向链表头,一个指向链表尾。
         要注意一点, CBC 链表中串起来的,都是 BHBuffer HeaderBuffer Handle的简称), BH是在 Buffer Cache的。而 HASH表内存,是在共享池中的。
         如果 Bucket中还没有 CBC链,哪会是什么情况呢?
         如下图:
<ignore_js_op>
Latch函数与Latch Miss
 
         报歉,又要深入进去了。不想当将军的士兵,不是好士兵。
         如果你是 China人,生在新中国,长在红旗下,又幸福的加入到了 IT行业中,成为一名光荣的攻城狮。我希望我们能更多的理解拿破仑的这句话。不想当将军的士兵,不是好士兵。哪么,不好的士兵结局是什么?
         IT或许只是职业,而不是爱好。但很可惜,在中国我们没得选择。这是我一直准备出去的原因,我已经没得选择了,只希望我的下一代将来可以有更多的选择。谁有出去的门路,别忘了兄弟。
 
为了减轻难度,你可以想像在做一个侦探游戏。你就是主角大侦探:福尔摩斯。你有一个助手,当然了就是华生医生。让我们切换到作案现场, Action
 
         华生问:“ Mr。福尔摩斯,罪犯留下的线索只有 kcbgtcr: kslbegin excl。我们如何深入调查。”
         福尔摩斯:“你注意到没,华生,这是一个 CBC Latch。”。
         华生:“大人圣明,料事如神”。
         画外音,“我 Cao,这 TMD谁都知道。”
         华生继续问道:“ Mr。福尔摩斯,我们如何继续调查呢,凶手又是谁呢?”
         福尔摩斯在案发现场仔细看了一圈,华生很不解的看着福尔摩斯,他确信现场已经没什么了。罪犯明显是个很精明的人,除了 kcbgtcr: kslbegin excl,它没有留下任何线索。

         福尔摩斯突然问:“华生,你有没有注意过, kcbgtcr是什么。”
         福尔摩斯这么一问,华生突然感到一道灵光闪过,“是啊,冒号前的 kcbgtcr,一定有它的意义。”但这道灵光又迅速消失。对于 kcbgtcr是什么,华生没有一点印象。
         正在他搜肠刮肚的想着时,福尔摩斯举起手中的 iPad:“它可以告诉我们答案。”
         华生凑近一看,差点吐血,只见福尔摩斯手中的 iPad,赫然显示着 Google的页面,搜索栏中,正是 kcbgtcr
         福尔摩斯悠悠的说道:“其实我先搜了百度,没找到可用的,然后才选择 Google。”
         说着福尔摩斯转身面向镜头,说:“用 Google,我的选择。”
         (植入广告可以打住了, Google也没给我钱)
         只见搜索结果中显示:“ kcbgtcr Oracle数据库最重要的函数之一,其含义为: Kernal Cache Buffer GeT Cosistents Read,也就是数据库的一致性读操作。”
         华生惊呼:“啊,我想起来了,早些年流传江湖的九阴真经之 DSI中,有一篇中好像介绍过这个函数。”
         福尔摩斯说:“没错,是 DSI 405Kcbgtcr就是 Oracle逻辑读的函数。”
         福尔摩斯再次转身面向镜头,植入广告:“如欲对 DSIOracle深入知识系统学习,可加入 QQ群: 127149411”。
         华生说道:“也就是说, kcbgtcr: kslbegin excl是和逻辑读相关的了。但是, CBC Latch本来就和逻辑读相关,我们费了半天脑子,感脚还是没有前进半步啊。”说完华生一脸的沮丧。
         福尔摩斯嘿嘿干笑了两声,“只怕唯必,你忘了,我们前不久研究的最新武器 ----Dtrace。走,回实验室,我们用 Dtrace跟踪一下逻辑读先。”

         场景切换:实验室。
         福尔摩斯先生的测试环境是 Solaris虚拟机。由于 Oracle的工作原理,在众多 OS中大致类似,因此遇到疑难的案子,福尔摩斯先生总是在 Solaris中分析 Oracle原理,再根据原理去解决其他平台的问题。
         华生看了一眼福尔摩斯的 Solaris,说:“福尔摩斯先生,其实 Oracle已经将 Solaris中的 Dtrace移植到了 Oracle Linux平台,我觉得 Linux更亲切,为什么我们不换到 Linux下研究呢?”
         福尔摩斯答道:“ Oracle Linux下的 Dtrace我已经研究过了,它没有一个重要的功能: PID探针。我们无法深入到 Oracle体内。”
         华生噢一声,福尔摩斯继续说道:“我估计 PID探针比较复杂, Oracle LinuxDTrace刚刚推出,功能完善起来,尚需时日。”
         说话间,虚拟机已经启动完毕,福尔摩斯熟练的启动数据库。
         华生问道:“我们要怎样测试呢?”
         福尔摩斯说:“很简单,跟踪逻辑读。”
         说着,福尔摩斯已经将语句将语句执行了:

SQL> select dbms_rowid.ROWID_RELATIVE_FNO(rowid),dbms_rowid.rowid_block_number(rowid),rowid from a2 where rownum=1;
 
DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) ROWID
------------------------------------ ------------------------------------ ------------------
                                  12                                 4356 AAADZuAAMAAABEEAAA
 
         12号文件 4356号块,这就是我们的小白鼠。”福尔摩斯自言自语道,“让我看看你是用哪个 Latch保护。”一边自言自语,一边手没闲着,他已经输入了下一条命令:

SQL> select ba,file#,dbablk,HLADDR ,tch,lru_flag,obj from x$bh where file#=12 and dbablk=4356;
BA                    FILE#     DBABLK HLADDR                  TCH   LRU_FLAG        OBJ
---------------- ---------- ---------- ---------------- ---------- ---------- ----------
0000000382A86000         12       4356 00000003911F4318          5          0      13934
         
         “看,华生, 12号文件 4356号块用 00000003911F4318这个 Latch保护,它当然是个 CBC Latch。”福尔摩斯说道。
         (注: X$BHHLADDR列,记录块对应的 CBC Latch地址)

“我们的测试语句,就用刚才查到的 ROWIDselect * from a2 where rowid=’ AAADZuAAMAAABEEAAA’”,福尔摩斯继续自言自语道,“我们要将它先执行个三、四次,确保它是逻辑读、软软解析。我可不想 Dtrace跟踪出的结果有一大堆硬解析的东西。”         华生在一边说:“是啊,记得跟踪过一次硬解析,好像十分的复杂,比软软解析复杂十倍都不止。”          说话间,福尔摩斯已经将测试语句 select * from a2 where rowid=’ AAADZuAAMAAABEEAAA’在测试会话执行了 56遍。          “可以开始跟踪了。”华生说。          福尔摩斯点点了头,执行了他们常用的 Dtrace脚本,脚本非常简单,也就下面几行: #!/usr/s bin/dtrace -s -n dtrace:::BEGIN {         i=1; } pid$1:::entry {         printf("i=%d PID::entry:==%s:%s:%s:%s %x %x %x %x %x %x",i, probeprov, probemod, probefunc, probename,arg0,arg1,arg2,arg3,arg4,arg5);         i=i+1; }

         (注:脚本的解释,请参考我的七种武器系列,此处不再过多解释。)
         福尔摩斯将脚本命名为all.d,并使用chmod命令,授予了执行权限。如下发布跟踪命令:
bash-3.2# ./all.d -x switchrate=1hz -b 16m 1609 > lread1.log
 
         1609,是测试会话对应的进程号。
         当脚本正确运行后,会显示一行:
dtrace: script './all.d' matched 152981 probes
 
         福尔摩斯和华生常常感叹,Oracle的代码中竟然有15万多个函数,Oracle的代码规模,真是够庞大的。等到你在屏幕上看到“dtrace: script './all.d' matched 152981 probes”这一行信息显示出来后,才能说明all.d脚本已经在运行了。
         脚本运行后,福尔摩斯在1609进程对应的会话中又一次执行了测试语句:select * from a2 where rowid='AAADZuAAMAAABEEAAA';。
         这条语句,将会导致进程申请00000003911F4318地址处的CBC Latch,并在此Latch的保护下,开始逻辑读12号文件、4356号块。
         一切都在按照福尔摩斯的计划进行,他喜欢这种把控一切的感觉。“只要静下心了,你也会喜欢这个游戏的。”他经常这样说。但华生其实一点都不喜欢Dtrace,他的看法是,只要架构做好,底层没必要搞哪么深。但福尔摩斯的说法,“向下挖的越深,是为了向上长的更高。”
         结果已经有了:
 
<ignore_js_op>
 
用Latch地址3911F4318搜索,福尔摩斯很快在跟踪结果中找到Oracle申请CBC Latch的函数:sskgslcas。它的第一个参数值,就是Latch的地址。第二个参数是0,第三个参数是1,第四个参数是0。第四、五、六个参数,华生已经没兴趣再看下去了,瞪着屏幕发呆的福尔摩斯紧皱着眉头,又自言自语:“不对啊,为什么在sskgslcas中找不到Latch Miss信息。”
         华生这一次没有发问,剩着福尔摩斯发呆的工夫,自己Google了一下:
 
         关于Oracle调用Latch的函数,10G中有两个函数:kslgetl、kslgetsl,分别对应普通Latch和共享Latch。 11G中kslgetl还是普通Latch,共享Latch函数变成ksl_get_shared_latch。这些函数的第四个参数,是Latch Miss信息。
 
         原来Oracle在申请Latch时,会顺便记录一个说明信息,这个说明信息就是Latch Miss。通过它,Oracle主要声明是为了什么才要申请Latch。主要的目的,是为了调试Latch相关的问题。
 
--------------------------------------------------------------------------------------------------------------------------------------
注:
Latch函数的第4个参数,是x$ksllw或x$kslwsc视图INDX列的值。这两个视图是V$Latch_miss的底层视图,你可以在V$FIXED_VIEW_DEFINITION中找到它们的对应关系。
sys@OFFERT>desc x$ksllw
Name                                                                                Null?    Type
--------------- -------- --------------------------------------------------------
ADDR                    RAW(8)
INDX                          NUMBER
INST_ID                 NUMBER
KSLLWNAM                VARCHAR2(64):试图获得Latch的位置,V$LATCH_MISSES.LOCATION 列。
KSLLWLBL                VARCHAR2(64): V$视图中没有此列,“Why” meaning for some of “where” may be guessed from ksllwlbl column of x$ksllw.
 
sys@OFFERT>desc x$kslwsc;
Name          Null?    Type
------------- -------- --------------------------------------------------------
ADDR                   RAW(8)
INDX                   NUMBER
INST_ID                NUMBER
KSLNOWTF               NUMBER  v$latch_misses.NWFAIL_COUNT列,no-wait获得Latch失败的次数。
KSLSLEEP               NUMBER  v$latch_misses.SLEEP_COUNT,请求闩导致睡眠的次数  
KSLWSCWSL              NUMBER  v$latch_misses.WTR_SLP_COUNT,等待着睡眠的次数
KSLWSCLTHG             NUMBER  v$latch_misses.LONGHOLD_COUNT,Number of times someone held a latch for the entire duration of someone else's sleep
KSLLASNAM              VARCHAR2(50) : Latch的名字
 
         x$ksllw中有Latch Miss的Location列,但它里面没有Latch的名字。x$kslwsc中有Latch的名字,但它没有Location列。它们两个都有INDX列,就是Latch函数的第四个参数。
--------------------------------------------------------------------------------------------------------------------------------------
 
         了解完了背景知识,华生也不禁迷惑起来,Latch函数的第四个参数,代表Latch Miss的Location列的编号。但是,CBC Latch的申请函数明显不同以往:sskgslcas。而且,第四个参数为0,这肯定不是Latch Miss。怪不得福尔摩斯也迷惑起来呢。
 
         线索到这里就中断了,如果是其他的Latch,只要Dtrace跟踪一下,找到调用Latch函数的第四个参数,就知道Latch Miss是什么了。但现在,CBC Latch的机制明显有了变化。
 
         华生和福尔摩斯对望了一下,华生明显已经有放弃的想法,但福尔摩斯不是轻易认输之人。略停了片刻,福尔摩斯毅然决然一挥手,说:“上mdb。”
         华生道:“mdb,威力太大了吧。这是核子级别的武器,当年日本广岛、长崎死伤太多。如果我们用mdb,得死多少脑细胞啊。我看,不如就此中止吧”
         福尔摩斯说:“就此中止,原凶岂不逍遥法外。我要让他知道,天网恢恢,疏而不漏。脑细胞死了旧的,才能生出新的,如果长期不新陈代谢,反而容易衰老。”
         华生叹了口气,“唉,哪只好如此。你准备如何使用mdb?”
         福尔摩斯一字一顿的说:“反~~~汇~~~编。”
         卡卡嚓,天空一道霹雳闪过,紧接着是哄隆隆的雷声。华生被福尔摩斯的这几个字吓了一大跳,情不自禁的重复道:“反~汇~编!”
         “是的,”福尔摩斯说道,“唯一的线索说明,kcbgtcr函数申请Latch时,导致了kcbgtcr: kslbegin excl处争用,既然从Latch申请函数sskgslcas中,找不到线索,我们一不做二不休,将kcbgtcr函数反汇编,这样,一定能找到线索。”
         “总要有人去做这样的事情的。不能提起DBA,就是Liews,就是Tanel Poder,就是国外某某大佬,相信很多国人也想深入研究,只是不得其门而入,我来找出这扇门在哪里,进与不进,在你自己了。”福尔摩斯悠悠的说的。
         他边说边敲命令:
bash-3.2# mdb -p 1609
Loading modules: [ ld.so.1 libc.so.1 ]
>
         1609号进程,还是刚才哪个进程。在mdb中,他使用kcbgtcr::dis反汇编kcbgtcr:
> kcbgtcr::dis
kcbgtcr:                        pushq  %rbp
kcbgtcr+1:                      movq   %rsp,%rbp
kcbgtcr+4:                      pushq  %rbx
kcbgtcr+5:                      pushq  %r12
kcbgtcr+7:                      pushq  %r13
………………
         反汇编的结果,一共12095行。汇编的结果有1万多行,想来C语言的代码,也要写个千吧行。真是一个复杂的大函数。
         “这么复杂,该如何进行下去呢?”,华生叹道。
         “Easy。”福尔摩斯轻松的说道,“你想想,CBC Latch虽然没有使用普通的Latch函数,而用了sskgslcas,但它也一定有Latch Miss相关信息。不然,案犯不会在罪案现场留下kcbgtcr: kslbegin excl的Latch Miss信息。我们之要……”。
         福尔摩斯买了个关子,故意没再说下去,不过华生毕竟已经跟随福尔摩斯多年,久经沙场,马上也猜到了:“是不是先将Latch地址:00000003911F4318处的内存值设置为1,然后再执行我们的测试语句,估计制造一起CBC Latch等待,用mdb的单步执行,看看Oracle将会如何设置Latch Miss?”
         福尔摩斯点点头:“是的。”
         说话间,他已经开始动手,将Latch地址:00000003911F4318处的内存值设置为1,这一步很简单,使用oradebug即可:
SQL> oradebug setmypid
Statement processed.
SQL> oradebug peek 0x00000003911F4318 4
[3911F4318, 3911F431C) = 00000000
SQL> oradebug poke 0x00000003911F4318 4 1
BEFORE: [3911F4318, 3911F431C) = 00000000
AFTER:  [3911F4318, 3911F431C) = 00000001
SQL>
 
         Oradebug的poke命令,可以设置SGA中某处内存的值。就用它,将内存0x00000003911F4318处置为1。
         注意,还要在mdb中设置断点:
bash-3.2# mdb -p 1609
Loading modules: [ ld.so.1 libc.so.1 ]
> kcbgtcr:b   <-------------------------------在kcbgtcr函数处设置断点
> :c         &szlig;-----------------------------:c,继续执行1609进程正在执行的代码。
 
         在1609进程对应的会话中,执行测试语句:select * from a2 where rowid='AAADZuAAMAAABEEAAA';。
Mdb会让1609停在我们设置的断点处:
> kcbgtcr:b
> :c
mdb: stop at kcbgtcr
mdb: target stopped at:
kcbgtcr:        pushq  %rbp
 
使用::step单步执行:
> ::step
mdb: target stopped at:
kcbgtcr+1:      movq   %rsp,%rbp
 
最终,福尔摩斯发现,进程在kcbgtcr函数偏移0x2788字节处,调用了sskgslcas函数:
> ::step
mdb: target stopped at:
kcbgtcr+0x2788: call   +0x6894f08       <sskgslcas>
 
         “这是加CBC Latch的函数。”福尔摩斯迟疑着说。
         “是啊,我记得加、释放Mutex,也是搞这个函数。”华生不知道福尔摩斯要干什么,在旁边接腔道。
         福尔摩斯继续道:“是啊,也是Mutex的函数,我们从这个函数开始吧,顺带也将Mutex的秘密昭告天下。而且,我们的线索只是一个CBC Latch的Miss信息,我们也只能从sskgslcas开始入手。”
         事已至此,华生也没有再劝说什么,福尔摩斯使用sskgslcas::dis,将sskgslcas也反汇编了一下,没想到sskgslcas函数的内容极为简单:
> sskgslcas::dis
sskgslcas:                      movq   %rsi,%rax
sskgslcas+3:                    lock cmpxchgq %rdx,(%rdi)
sskgslcas+8:                    sete   %al
sskgslcas+0xb:                  movzbq %al,%rax
sskgslcas+0xf:                  ret   

这么简单的函数,相信应该是用C语言的内嵌汇编写的。现在,进程正停在sskgslcas的入口处,福尔摩斯看了一下寄存器的值: > $r %rax = 0x0000000000000000       %r8  = 0x0000000000000000 %rbx = 0x0000000000000000       %r9  = 0x00000003927628d8 %rcx = 0x0000000000000000       %r10 = 0x00000003911a7e88 %rdx = 0x0000000000000001       %r11 = 0xfffffd7ffcd01f00 %rsi = 0x0000000000000000       %r12 = 0x00000003911f52e8 %rdi = 0x00000003911f4318       %r13 = 0x0000000392762790                                 %r14 = 0xfffffd7ffc924720                                 %r15 = 0x0000000000000001         这要要说明一下的是,从左边rdi寄存器开始,rsi、rdx等,依次是调用函数的第一个、第二个、第三个等等参数。 Sskgslcas中第一条指令: sskgslcas:                      movq   %rsi,%rax 将函数的第二个参数传给rax寄存器,从上面展示的结果,第二个参数,也就是rsi的值为0。 Sskgslcas下一条指令: sskgslcas+3:                    lock cmpxchgq %rdx,(%rdi) 它在sskgslcas函数入口处向后3字节, “这说明第一条指令‘movq   %rsi,%rax’占了三个字节。”福尔摩斯向华生解释道。 Lock是锁总线的,cmpxchgq是测试并交换,test&swap,将两个操作封装在一条指令中,由CPU保证这两个操作的原子性。在多芯、多核的系统中,加Lock是必须的。 福尔摩斯又向华生解释:“cmpxchgq会首先用(%rdi)处的内存值和rdx寄存品的值相比较,比较结果将会影响标志寄存器。如果比较结果不相等,会将rdx的值传入内存(%rdi)处。如果比较结果相等,则不再将rdx的值传入内存(%rdi)处” 华生一脸的疑惑,显然还没看懂,福尔摩斯问道:“你不是有二级C语言的基础吗,哪你应该能看懂下面这段C C语言伪码。” 说着,福尔摩斯写下这段伪码: If ( (rdi) != rdx ) {    (rdi) = rdx ; } “嗯,”,看了一几行代码,华生有点懂了。“因为我们已经用oradebug poke,将Latch地址处的内存值设置为1。所以这里Cmpxchgq测试结果不成功,等于Latch没有加上。” “是的。”,福尔摩斯说道,“汇编和C语言不同,汇编要直接操作硬件,现代CPU架构中,任何比较语句,都会引起标志寄存器的改变。所以,无论条件语句中的(rdi) = rdx ;语句是否被执行,(rdi) != rdx这个比较语句,都会改变一些标志寄存器的值。” “因此,”,福尔摩斯顿了一顿,继续说道,“下一条语句就是从标志寄存器中取结果了” 下两条语句是: sskgslcas+8:                    sete   %al sskgslcas+0xb:                  movzbq %al,%rax 华生赶忙Google了一下,sete,地ZF标志寄存器的值传送到al寄存器。这次福尔摩斯没有做过多解释,直接写了行C伪码: Al = ( (rdi) != rdx ) 华生一看就明白了,“sete   %al,相当于将(rdi) != rdx比较的结果,送入al寄存器。” “差不多吧,”,福尔摩斯笑笑答道。 “此处,(rdi)和rdx相等,条件不成立,送进al的值,就是0了。”华生道。 “是的。”福尔摩斯回答说,“而下面一条指令,movzbq,就简单了,将8位的al寄存器值,扩展到64的rax中,然后,就是ret了,按照汇编的惯例,返回值一定要在rax寄存器中,整个来看,这段代码就如同下面这段C语言代码。” 福尔摩斯写下了下面这段代码: Int sskgslcas(rdi,rsi,rdx,rcx) { If (  al = (rdi) != rdx  ) {                    (rdi) = rdx ; } Return al; } 这就是CBC Latch函数和Mutex的伪码了。 华生看了一会儿,在心中默默的想了一遍,就明白了,“sskgslcas的结果在rax寄存器,如果Latch申请成功,rax为1,如果申请不成功,rax为0,是这样的吧。”华生问道。 “是这样的。”福尔摩斯答道,“让我们单步执行,验证一下。” 说着,他在mdb中使用::step,让进程一直执行到ret处: > ::step mdb: target stopped at: sskgslcas+0xf:  ret    查一下此进寄存器的值: > $r %rax = 0x0000000000000000       %r8  = 0x0000000000000000 %rbx = 0x0000000000000000       %r9  = 0x00000003927628d8 %rcx = 0x0000000000000000       %r10 = 0x00000003911a7e88 %rdx = 0x0000000000000001       %r11 = 0xfffffd7ffcd01f00 %rsi = 0x0000000000000000       %r12 = 0x00000003911f52e8 %rdi = 0x00000003911f4318       %r13 = 0x0000000392762790                                 %r14 = 0xfffffd7ffc924720                                 %r15 = 0x0000000000000001 Rax果然为0。申请Latch没有成功。 看到单步执行结果和他们分析的一样,福尔摩斯和华生相视一笑。 “接下来要做什么?”华生问道。 福尔摩斯笑着骂道,“动动脑子想想啊,sskgslcas函数已经分析完了,接下来做啥。” “从sskgslcas调用处,继续分析kcbgtcr。”华生肯定的回答。 “你开窃了,华生。”福尔摩斯开了句玩笑,手中没停,拿过刚才所汇编kcbgtcr的结果,从刚才的位置继续向下看: kcbgtcr+0x2788:                 call   +0x6894f08       <sskgslcas> 根据ROWID逻辑读时的第一次CBC Latch kcbgtcr+0x278d:                 testl  %eax,%eax                     kcbgtcr+0x278f:                 jne    +0x48    <kcbgtcr+0x27d7>    kcbgtcr+0x2791:                 movq   $0x0,0x450(%r13) kcbgtcr+0x279c:                 testl  %r15d,%r15d kcbgtcr+0x279f:                 movl   $0x0,%eax kcbgtcr+0x27a4:                 movl   $0x40,%ecx kcbgtcr+0x27a9:                 movl   %eax,%esi kcbgtcr+0x27ab:                 cmovl.e %ecx,%esi kcbgtcr+0x27ae:                 orl    $0x109,%esi kcbgtcr+0x27b4:                 movl   0x4(%r14),%eax kcbgtcr+0x27b8:                 movl   %eax,%ecx kcbgtcr+0x27ba:                 movq   0xa718147 (%rip),%r8    kcbgtcr+0x27c1:                 movl   (%r8),%r8d                kcbgtcr+0x27c4:                 movq   -0x698(%rbp),%rdi kcbgtcr+0x27cb:                 movl   $0x1,%edx kcbgtcr+0x27d0:                 call   -0x1202260       <kslgess> kcbgtcr+0x27d5:                 jmp    +0x53    <kcbgtcr+0x2828> kcbgtcr+0x27d7:                 movq   0xaf571ca(%rip),%r8                                d7f1bf0->r8 这次华生已经不再畏惧,主动说到,“调用sskgslcas返回,下一条语句是testl %eax,%eax,看来这是在比较Latch的申请是否成功了。” “是的”,福尔摩斯说到,“jne是不相等或非零跳转,这两行语句,相当于。”说着,他又写下了如下的伪码: kcbgtcr+0x278d:                 testl  %eax,%eax                     kcbgtcr+0x278f:                 jne    +0x48    <kcbgtcr+0x27d7>    相当于: if ( eax != 0 ) {         goto <kcbgtcr+0x27d7>; } “Eax不等于零就跳转,”华生惊叫道,“这是判断Latch是否申请成功的,如果Latch申请成功就跳到kcbgtcr+0x27d7处。” 福尔摩斯冲着华生眨了眨眼,道:“瞧,我们离目标越来越近了,好像没也没什么难的,不是吗!”。 “是啊,”华生接着说:“被跳过去的部分,就是当Latch没有申请成功的话,Oracle会执行的代码。” 福尔摩斯道:“是的,我肯定案犯也来过这里,我们要仔细调查这段代码。” 这段代码是: kcbgtcr+0x278f:                 jne    +0x48    <kcbgtcr+0x27d7>    kcbgtcr+0x2791:                 movq   $0x0,0x450(%r13) kcbgtcr+0x279c:                 testl  %r15d,%r15d kcbgtcr+0x279f:                 movl   $0x0,%eax kcbgtcr+0x27a4:                 movl   $0x40,%ecx kcbgtcr+0x27a9:                 movl   %eax,%esi kcbgtcr+0x27ab:                 cmovl.e %ecx,%esi kcbgtcr+0x27ae:                 orl    $0x109,%esi kcbgtcr+0x27b4:                 movl   0x4(%r14),%eax kcbgtcr+0x27b8:                 movl   %eax,%ecx kcbgtcr+0x27ba:                 movq   0xa718147 (%rip),%r8    kcbgtcr+0x27c1:                 movl   (%r8),%r8d                kcbgtcr+0x27c4:                 movq   -0x698(%rbp),%rdi kcbgtcr+0x27cb:                 movl   $0x1,%edx kcbgtcr+0x27d0:                 call   -0x1202260       <kslgess> kcbgtcr+0x27d5:                 jmp    +0x53    <kcbgtcr+0x2828> kcbgtcr+0x27d7:                 movq   0xaf571ca(%rip),%r8                                d7f1bf0->r8 “看,这段代码中调用了一个函数,kslgess。”华生叫到。 “是的,它一定和Latch Miss有关,单步执行到它哪里。”福尔摩斯说道。 接着,他用::step,一路执行下去,直到在调用kslgess处停下: > ::step mdb: target stopped at: kcbgtcr+0x27d0: call   -0x1202260       <kslgess> 用$r显示kslgess的参数: > ::step mdb: target stopped at: kcbgtcr+0x27d0: call   -0x1202260       <kslgess> > $r %rax = 0x0000000003001104       %r8  = 0x000000000000067e %rbx = 0x0000000000000000       %r9  = 0x00000003927628d8 %rcx = 0x0000000003001104       %r10 = 0x00000003911a7e88 %rdx = 0x0000000000000001       %r11 = 0xfffffd7ffcd01f00 %rsi = 0x0000000000000109       %r12 = 0x00000003911f52e8 %rdi = 0x00000003911f4318       %r13 = 0x0000000392762790                                 %r14 = 0xfffffd7ffc924720                                 %r15 = 0x0000000000000001 “看到r8没”,福尔摩斯说,“67e,这个值有问题,我看,它就是Latch Miss。” 未等福尔摩斯吩咐,华生已经将验正语句准备好: set linesize 1000 col KSLLWNAM  for a30 col KSLLWLBL  for a20 col KSLLASNAM for a40 select a.addr,b.addr,a.KSLLWNAM, a.KSLLWLBL,b.KSLLASNAM from x$ksllw a,x$kslwsc b where a.indx=b.indx and a.indx=to_number('&aa','xxxxxxxx'); 执行结果是: Enter value for aa: 67e old   1: select a.addr,b.addr,a.KSLLWNAM,a.KSLLWLBL,b.KSLLASNAM from x$ksllw a,x$kslwsc b where a.indx=b.indx and a.indx=to_number('&aa','xxxxxxxx') new   1: select a.addr,b.addr,a.KSLLWNAM,a.KSLLWLBL,b.KSLLASNAM from x$ksllw a,x$kslwsc b where a.indx=b.indx and a.indx=to_number('67e','xxxxxxxx') ADDR             ADDR             KSLLWNAM                       KSLLWLBL             KSLLASNAM ---------------- ---------------- ------------------------------ -------------------- ---------------------------------------- 000000000CFC1F08 00000003926F9AA0 kcbgtcr: fast path (cr pin)                         cache buffers chains “Oh yeah!”,华生叫道:“67e是kcbgtcr: fast path (cr pin)的代码。” 福尔摩斯道:“别哪么快下结论,验证一下。” 华生马上写了一下PL/SQL程序: declare   v_id1 number; begin   for i in 1..10000000 loop     select id1 into v_id1 from a2 where rowid='AAADZuAAMAAABEEAAA';   end loop; end; / 在两个会话中执行,两个进程疯狂读同一块,一定会有大量CBC Latch竞争,查询V$Latch_misses视图,果然发现kcbgtcr: fast path (cr pin)的SLEEP_COUNT大量增加。 看来,调用kslgess时,r8寄存器的值果然就是latch miss的Location编号。 其他的Latch函数,如kslgetl,它的形式如下: Kslgetl(Latch地址,未知,未知,Where,……) 第四个参数Where,就是Latch Miss。 Sskgslcas不一样,这个函数看来是只用于非常繁忙的CBC Latch和Mutex。它在被调用时没有Latch Miss,如果申请Latch、Mutex失败,再通过kslgess函数登记Latch Miss信息。 忙了半天,终于小有进展。 福尔摩斯说道:“下面分析一下r8寄存器的值从哪里来的。” 关于r8,有下面两行代码: kcbgtcr+0x27ba:                 movq   0xa718147 (%rip),%r8    kcbgtcr+0x27c1:                 movl   (%r8),%r8d             0xaf5d827(%rip),这也能算是一个“PC相关地址”,rip是PC指令寄存器,记录下一条要执行的指令代码的内存地址。0xa718147 (%rip)的值如何求呢? 福尔摩斯没等华生问,就写出了如下的方法 kcbgtcr+0x27ba+0xa718147 华生在mdb中使用dump命令,显示了一下: > kcbgtcr+0x27ba+0xa718147,8::dump -e ceaf9a1:  038001aa b0000000 038001aa b8000000 得到一个内存地址:038001aab0。 下一步“movl   (%r8),%r8d”,就是将此内存地址处的值,传给r8。显示一下此内存地址的值: > 038001aab0,8::dump -e 38001aab0:  0000067e 00000000 0000067f 00000000 正好是67e。也就是kcbgtcr: fast path (cr pin)。 福尔摩斯说:“看华生,有了些进展。我们并不需要反汇编所有代码,只需要找到我们感兴趣的部分就行。” 华生说:“是的,但是这个kcbgtcr: fast path (cr pin),并不是罪案现场的kcbgtcr: kslbegin excl啊。” 福尔摩斯说:“嗯,是的。我们有了进展,但还没有最终找到结果。” 华生说:“不过,也算是有成果了,最普通的逻辑读,或者说一致读,Latch Miss是kcbgtcr: fast path (cr pin)。原凶并不是普通的逻辑读。” 福尔摩斯问道:“想一想,华生,接下来要怎么做!” 华生说:“可以将kcbgtcr所有kslgess找到,查看kcbgtcr: kslbegin excl会在哪儿。” 福尔摩斯说:“对,立即行动。” 华生答:“Yes,Sr。” 华生将kcbgtcr的反汇编代码放入一个文本文件,统计了一下: bash-3.2# cat kcbgtcr.asm|grep kslgess kcbgtcr+0x537:                  call   -0x11fffc7       <kslgess> kcbgtcr+0x27d0:                 call   -0x1202260       <kslgess> kcbgtcr+0x5bb3:                 call   -0x1205643       <kslgess> kcbgtcr+0x61f5:                 call   -0x1205c85       <kslgess> kcbgtcr+0x644e:                 call   -0x1205ede       <kslgess> kcbgtcr+0x9777:                 call   -0x1209207       <kslgess> 不多,只有6处。Kcbgtcr函数只在6个地方用sskgslcas申请CBC Latch。它们分别对应的Latch Miss是: 1、kcbgtcr+0x521:                  movq   0xaf5f9e0(%rip),%r8   ---662 kcbgtcr: fast path 2、kcbgtcr+0x27ba:                 movq   0xa718147 (%rip),%r8   ---- kcbgtcr: fast path (cr pin) 3、kcbgtcr+0x5ba9:                 movq   0xaf5a360(%rip),%r8    ---- 0x663  kcbgtcr: kslbegin excl         4、kcbgtcr+0x61eb:                 movq   0xaf59eae(%rip),%r8   --- 695 kcbgtcr_2 5、kcbgtcr+0x6444:                 movq   0xaf599bd(%rip),%r8  --- 642  kcbgtcr: kslbegin shared 6、kcbgtcr+0x976d:                 movq   0xaf56a0c(%rip),%r8   ---6b1 kcbgtcr: L2 其中,kcbgtcr+0x27ba处的,是刚刚发现的、普通逻辑读处的Latch Miss。案犯现场的目标:kcbgtcr: kslbegin excl,在偏移量kcbgtcr+0x5ba9处。 看到这个结果,福尔摩斯和华生都长出了一口气,kcbgtcr: kslbegin excl终于现形了。但新的问题又来了,kcbgtcr+0x5ba9,Oracle在什么情况下才会执行到这里呢?可以肯定的时,这不是最普通的物理读,因为最普通的物理读,Latch Miss是kcbgtcr+0x27ba处的kcbgtcr: fast path (cr pin)。 福尔摩斯和华生想了很多情况,但都不能找到什么情况下Oracle才会走到kcbgtcr: kslbegin excl这里。最后,福尔摩斯狠下心来,从kcbgtcr的头开始,单步执行,一步步跟踪。 “先把kcbgtcr函数的原理搞清楚”,福尔摩斯说,“不然,我们发现不了Oracle什么时候会执行kcbgtcr: kslbegin excl这里。” “也只好如此了,”华生举目沮丧的说到。 他们开始从kcbgtcr的第一个字节处分析反汇编代码,过程有点无聊,长话短说吧,从kcbgtcr+0x2c0处到kcbgtcr+0x385处,是Buffer Cache中的HASH算法: kcbgtcr+0x2c0:                  movq   0xa723e59(%rip),%r8 kcbgtcr+0x2c7:                  movl   0x80(%r8),%eax kcbgtcr+0x2ce:                  incl   %eax kcbgtcr+0x2d0:                  andl   $0xf,%eax kcbgtcr+0x2d3:                  movq   0xa723e46(%rip),%r8 kcbgtcr+0x2da:                  movl   %eax,0x80(%r8) kcbgtcr+0x2e1:                  movl   (%r14),%eax kcbgtcr+0x2e4:                  movq   0xa723e35(%rip),%r8 kcbgtcr+0x2eb:                  movq   0xa723e2e(%rip),%r9 kcbgtcr+0x2f2:                  movl   0x80(%r9),%ecx kcbgtcr+0x2f9:                  movl   %ecx,%r9d kcbgtcr+0x2fc:                  movl   %eax,(%r8,%r9,8) kcbgtcr+0x300:                  movl   0x4(%r14),%eax kcbgtcr+0x304:                  movq   0xa723e15(%rip),%r8 kcbgtcr+0x30b:                  movq   0xa723e0e(%rip),%r9 kcbgtcr+0x312:                  movl   0x80(%r9),%ecx kcbgtcr+0x319:                  movl   %ecx,%r9d kcbgtcr+0x31c:                  movl   %eax,0x4(%r8,%r9,8) kcbgtcr+0x321:                  movq   0xa714018(%rip),%r8 kcbgtcr+0x328:                  movl   (%r8),%eax kcbgtcr+0x32b:                  movl   %eax,-0x448(%rbp) kcbgtcr+0x331:                  movl   (%r14),%eax kcbgtcr+0x334:                  shll   $0x11,%eax kcbgtcr+0x337:                  addl   0x4(%r14),%eax kcbgtcr+0x33b:                  imull  $0x9e370001,%eax,%eax kcbgtcr+0x341:                  movq   0xa72ed30(%rip),%r8 kcbgtcr+0x348:                  movl   (%r8),%ecx kcbgtcr+0x34b:                  movl   %eax,%edx kcbgtcr+0x34d:                  shrl   %cl,%edx kcbgtcr+0x34f:                  movq   0xa72ed2a(%rip),%r8 kcbgtcr+0x356:                  movl   (%r8),%eax kcbgtcr+0x359:                  andl   %edx,%eax kcbgtcr+0x35b:                  movl   %eax,%r8d kcbgtcr+0x35e:                  shlq   $0x4,%r8 kcbgtcr+0x362:                  movq   0xa72ed1f(%rip),%r9 kcbgtcr+0x369:                  movl   (%r9),%ecx kcbgtcr+0x36c:                  movl   %edx,%eax kcbgtcr+0x36e:                  shrl   %cl,%eax kcbgtcr+0x370:                  movl   %eax,%r9d kcbgtcr+0x373:                  shlq   $0x4,%r9 kcbgtcr+0x377:                  movq   0xa72ed12(%rip),%r10 kcbgtcr+0x37e:                  movq   (%r10),%r10 kcbgtcr+0x381:                  movq   (%r10,%r9),%r12 kcbgtcr+0x385:                  addq   %r8,%r12 这段代码对应的C语言伪码是: int sp1=0x11; int sp2=0x7f; int sp3=0x7; int hash_header=0x394953140; hash(int p1,int p2) {         int uk=p1;         int rdba=p2;         int v1,v2;         int target_addr;         uk=uk<<0x11+rdba;         uk=(0x9e370001*uk)&0x00000000FFFFFFFF;         uk=uk>>sp1;         v1=uk&sp2;         v1=v1<<0x4;         v2=uk>>0xsp3;         v2=v2<<0x4;         target_addr=hash_header+v2+v1; }         通过解析反汇编代码,对于HASH运算消耗的CPU,更加清楚了。         在算法出HASH值后,执行流程会跳到kcbgtcr+0x25b1处,从这里开始,是申请CBC Latch的准备工作。然后在kcbgtcr+0x2788处,就是申请CBC Latch了,在它后面,就是开始搜索HASH链了,在这之后,有如下的代码: kcbgtcr+0x281d:                 movl   0x4(%r14),%eax kcbgtcr+0x2821:                 movl   %eax,%r9d kcbgtcr+0x2824:                 movq   %r9,0x18(%r8) kcbgtcr+0x2828:                 cmpq   (%r12),%r12 kcbgtcr+0x282c:                 movq   $0x0,%r8 kcbgtcr+0x2833:                 movq   %r8,-0x5f0(%rbp)          kcbgtcr+0x283a:                 movl   $0x0,-0x5c8(%rbp)         kcbgtcr+0x2844:                 movl   $0x0,-0x5d0(%rbp)         kcbgtcr+0x284e:                 movl   $0x0,-0x664(%rbp)         kcbgtcr+0x2858:                 je     +0x2fe8  <kcbgtcr+0x5840> 在kcbgtcr+0x281d处,从r14寄存器向下偏移4字节传入eax的值,是被读块的dba。R12寄存器的值,是HASH Bucket中CBC链的链表头。“cmpq   (%r12),%r12”是判断CBC链是否为空。还记得前面所画的CBC链图吗,如果链表是空的,Bucket中记录Bucket本身的地址。也就是Bucket自已指向自己。cmpq   (%r12),%r12正是比较r12指向的内容和r12是否相等。如果相等,Bucket后就没有链表。 如果链表为空,证明块一定不在Buffer Cache,kcbgtcr+0x2858处的“je     +0x2fe8  <kcbgtcr+0x5840>”,会让程序执行流程跳到kcbgtcr+0x5840处。 这充分说明kcbgtcr+0x5840后的代码,是处理物理读的。 如果CBC链不为空,下面的代码就是遍历CBC链,这行代码: kcbgtcr+0x289c:                 movq   (%r8),%r8 这句的意义是将指针指向的值赋给指针,类似于这样的代码:prt_next=*ptr_next 。这句正是用来遍历CBC链表的。 紧接着这三行,将注释直接写在代码后: kcbgtcr+0x2918:                 movl   0x4(%r14),%eax   // 将dba传给eax寄存器,作为目标dba          kcbgtcr+0x291c:                 cmpl   %eax,0xc4(%r13)   // 寄存器r13记录BH地址,向下偏移0xc4字节, 就是BH中的dba,用eax中的目标dba和此dba比较。 kcbgtcr+0x2923:                 jne    +0x1857  <kcbgtcr+0x417a>  // 如果不匹配,跳转。 跳转过去之后,有如下的代码: kcbgtcr+0x4195:                 movq   %r8,-0x5a0(%rbp)                                        kcbgtcr+0x419c:                 movq   -0x5a0(%rbp),%r8                                        kcbgtcr+0x41a3:                 testq  %r8,%r8                                                                        kcbgtcr+0x41a6:                 jne    -0x18a6  <kcbgtcr+0x2900>  //当CBC链没到头时,回以刚才的地方, 继续查找。 kcbgtcr+0x41ac:                 jmp    +0x9     <kcbgtcr+0x41b5> kcbgtcr+0x41ae:                 movq   %r13,-0x608(%rbp)         kcbgtcr+0x41b5:                 cmpq   $0x0,-0x608(%rbp)         kcbgtcr+0x41bd:                 je     +0x1683  <kcbgtcr+0x5840>  //如果CBC链搜索完毕没找到目标dba,                                                                                                                                 跳转到kcbgtcr+0x5840处。 kcbgtcr+0x41c3:                 movzwl 0xbc(%r14),%eax                  也就是说,CBC链中没找到目标时(也就是物理读时),将跳到kcbgtcr+0x5840处。         看看kcbgtcr+0x5840代码吧: kcbgtcr+0x583d:                 popq   %rbx kcbgtcr+0x583e:                 leave   kcbgtcr+0x583f:                 ret                           kcbgtcr+0x5840:                 movl   -0x5b8(%rbp),%eax kcbgtcr+0x5846:                 testl  %eax,%eax 在kcbgtcr+0x583f处,有指令ret,kcbgtcr到这里有可能结束,返回调用者。程序什么时候会跳到kcbgtcr+0x5840处呢,物理读。 纯逻辑读,kcbgtcr函数的执行,会在kcbgtcr+0x583f处终止。物理读则会跳至kcbgtcr+0x5840后开始执行。 经过艰难的调查取证,福尔摩斯和华生终于大概搞清楚了kcbgtcr的执行流程。 华生看了一眼作案现场留下的线索:kcbgtcr: kslbegin excl,轻轻的喃喃自语:“终于抓到你了。” 有没有忘掉kcbgtcr: kslbegin excl在代码的哪里: kcbgtcr+0x5ba9:                 movq   0xaf5a360(%rip),%r8    ---- 0x663  kcbgtcr: kslbegin excl kcbgtcr+0x5ba9,在kcbgtcr+0x5840之后不远的位置,这块代码,只有物理读才有可能走到这里。 所以,原凶就是:“物理读”。 这真是一个奇怪的结果,逻辑读函数中的Latch竞争,原凶竟然是物理读。 调优的方向,应该是针对物理读最多的一些SQL,这样才有助于减少kcbgtcr: kslbegin excl处的Latch争用。 而这调优逻辑读高的SQL,对于减少kcbgtcr: kslbegin excl处的Latch争用,是没有帮助的。 进一步的,可以将kcbgtcr中的Latch Miss分个类: 一、纯逻辑读相关的 1、kcbgtcr+0x521:                  movq   0xaf5f9e0(%rip),%r8   ---662 kcbgtcr: fast path 2、kcbgtcr+0x27ba:                 movq   0xa718147 (%rip),%r8   ---- kcbgtcr: fast path (cr pin) 二、物理读相关的: 3、kcbgtcr+0x5ba9:                 movq   0xaf5a360(%rip),%r8    ---- 0x663  kcbgtcr: kslbegin excl         4、kcbgtcr+0x61eb:                 movq   0xaf59eae(%rip),%r8   --- 695 kcbgtcr_2 5、kcbgtcr+0x6444:                 movq   0xaf599bd(%rip),%r8  --- 642  kcbgtcr: kslbegin shared 6、kcbgtcr+0x976d:                 movq   0xaf56a0c(%rip),%r8   ---6b1 kcbgtcr: L2
 
 
 

更多内容请关注微信公众号:数据与人

相关推荐