Buffer Pin锁阻塞后唤醒机制是FIFO队列吗?

2020-06-22 00:00:00 函数 执行 会话 进程 释放

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号进程执行Updatemdb的输出。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_WAIT9秒,它先被执行。其次是257会话,它等待时间是6秒,后是259会话,等待时间4秒。

从等待事件上可以证明,这三个会话,执行顺序依次是:先248,再257,后259

6:释放Buffer Pin

怎么释放呢,很简单,在mdb中敲入“::quit”,退出mdb就行了。退出mdb,进程1191的执行流会正常执行下去,完成行的修改,再加一次CBC Latch,释放Buffer Pin,通知Sleep的进程。

谁会先被唤醒呢,是不是先执行的248会话呢?这可不一定,下图是我这里的测试结果:

排在后面的257先被唤醒,它获得Buffer Pin锁,开始修改行。接下来才是248259。它们两个被257的行锁阻塞了。

整个步骤写起来麻烦,做起来很快的,分分钟就可以搞定。我试过多次,每次被唤醒的会话不一样,我没有找到规律。

相关文章