Buffer Pin锁阻塞后唤醒机制是FIFO队列吗?
Buffer Pin锁阻塞后唤醒机制是FIFO队列吗
Buffer Busy Waits的原凶,Buffer Pin锁,在多个进程遇到阻塞时,是按照“队列”模式,“先被阻塞者被唤醒”吗?
不是,Buffer Pin锁阻塞后唤醒机制是随机的。(我没有找到规律,至少它不是先进先出的队列模式)。
这个结论会让人吃惊,怀疑是免不了的。不过验证方式非常简单,使用gdb/mdb类调试工具,几分钟就可以测试出来。下面我把测试步骤简单介绍一下。
我使用Solaris环境,11.2.0.1(我在10G下测试过,结果一样,找时间再测测12C),调试器当然就用mdb了。如果是Linux,使用gdb的话命令类似。
步1:查看测试会话进程号:
SQL> select c.sid,spid,pid,a.SERIAL# from (select sid from v$mystat where rownum<=1) c,v$session a,v$process b where c.sid=a.sid and a.paddr=b.addr;
SID SPID PID SERIAL#
---------- ------------------------ ---------- ----------
256 1191 33 4
测试进程是1191号进程。
步2:使用mdb,调试1191进程:
在ktbgcur函数处设置断点。在Update执行时,更新Buffer时一定会调用这个函数。这是用Dtrace发现的。有时需要反汇编的话,用Dtrace发现各种各样的函数名,可以大大缩小反汇编的范围。
步3:在1191进程的会话(256会话)中执行SQL:
00:30:00 SQL> update vage set name='aaaaa' where rowid='AAADf5AAMAAABMjAAA';
进程执行这条语句的流程,会停在ktbgcur处。同时mdb处会有显示,显示结果在步4中。
步4:
上图中,选中部分(底色反转),就是1191号进程执行Update后mdb的输出。1191目前停在ktbgcur函数的入口处(入口处条指令是push %rbp,堆栈指针入栈)。
接着执行图中接下来的命令:sskgslcas:b 。在sskgslcas函数处设置断点。这个函数是加CBC Latch的函数。
再接下来,执行”:c”,让1191进程的执行流前进到sskgslcas处。
然后使用“$r”,显示寄存器,%rdi 寄存器的值: 0x0000000391300de8 ,就是CBC Latch的地址。我显示它的目的是验证一下,看是否找对地方。它应该是要修改Buffer对应原CBC Latch的地址。
验证无误,进程1191正要在目标块上加CBC Latch。再接下来,执行“sskgsldecr:b”,接着,再看下一张图:
底色反转部分是接下去要做的事情,敲入“sskgsldecr:b”命令,它是释放CBC Latch函数。
加CBC Latch的目的是加Buffer Pin锁,我让进程的执行流前进到释放CBC Latch的地方。在释放CBC Lathc后,Buffer Pin锁就一定成功加上了(如果没有人和它竞争的话,当然,这里不会有进程和1191进程抢的)。
接下来,设完断点,:c,前进到断点处,这里是sskgsldecr函数的入口,要执行完sskgsldecr函数,CBC Latch才释放掉的。因此接下来用::step,单步执行。
Sskgsldecr函数非常简单,只有一条语句,lock subq %rsi,(%rdi)。单步前进两次后,看到“kcbgcur+0x4e1c: leaq 0x58(%r13),%r8”,执行流已经从kcbgcur函数中退出。Sskgsldecr函数已经执行完了,CBC Latch已经释放,Buffer Pin锁已经加成功了。
先不要执行出的::quit。开始第5步。
步5:任意开三个会话,修改同一块中行:
我的修改语句是这样的:
00:30:03 SQL> update vage set name='aaaaa' where rowid='AAADf5AAMAAABMjAAB';
注意,1191进程修改的是:“where rowid='AAADf5AAMAAABMjAAA'”,这里修改的是“AAADf5AAMAAABMjAAB”。
我开了三个会话,执行同样的语句,查看等待事件:
SQL> select sid,seq#,event,status,SECONDS_IN_WAIT from v$session where wait_class<>'Idle' order by event,SECONDS_IN_WAIT;
SQL> /
SID SEQ# EVENT STATUS SECONDS_IN_WAIT
---------- ---------- ---------------------------------------- -------- ---------------
10 1428 SQL*Net message to client ACTIVE 0
259 63 buffer busy waits ACTIVE 4
257 95 buffer busy waits ACTIVE 6
248 100 buffer busy waits ACTIVE 9
248会话等待时间久,SECONDS_IN_WAIT是9秒,它先被执行。其次是257会话,它等待时间是6秒,后是259会话,等待时间4秒。
从等待事件上可以证明,这三个会话,执行顺序依次是:先248,再257,后259。
步6:释放Buffer Pin锁
怎么释放呢,很简单,在mdb中敲入“::quit”,退出mdb就行了。退出mdb,进程1191的执行流会正常执行下去,完成行的修改,再加一次CBC Latch,释放Buffer Pin,通知Sleep的进程。
谁会先被唤醒呢,是不是先执行的248会话呢?这可不一定,下图是我这里的测试结果:
排在后面的257先被唤醒,它获得Buffer Pin锁,开始修改行。接下来才是248、259。它们两个被257的行锁阻塞了。
整个步骤写起来麻烦,做起来很快的,分分钟就可以搞定。我试过多次,每次被唤醒的会话不一样,我没有找到规律。
相关文章