首页 > 代码库 > mutex----我是搬运工

mutex----我是搬运工

看吕大的书,其中对mutex的介绍让人心动,因此我做一次搬运工。

mutex与latch区别:

mutexlatch

没有等待队列,没有持有队列,抢占机制

使用队列

spin255次,在spin 期间无法获得,转入睡眠,自己醒来

spin 2000次,在spin 期间无法获得,转入睡眠,等待唤醒

使用引用计数器 (reference count),在64 位中占用 8字节,其实前4 SID,后4 为引用计数器,如果引用计数器为 0,说明mutex 为独占模式

latch池中,每一个 latch一块内存,所有 latch组成 latch

植入对象内部

与对象分离

v$mutex_sleep_history.mutex_value查看mutex value

select sid, event, p1raw, p2raw, state, wait_class

  from v$session

 where wait_class <> ‘Idle‘

 order by event;

如果event为library cache:mutex X,那么p2raw就是阻塞进程的mutex的当前mutex_value,其中前端是SID,可以找到阻塞的会话。


mutex类型:

常见类型有:cursor parent、library cache、hash table、cursor pin、cursor stats

在v$mutex_sleep和v$mutex_sleep_history中的mutex_type列对应mutex类型

不同mutex type对应不同的等待事件,通常如下:

hash table、cursor parent、cursor stats类型的mutex对应 cursor:mutex

library cache类型的mutex对应 library cache:mutex

cursor pin类型的mutex对应 cursor:pin

硬解析所有mutex都会出现,软软解析只会cursor pin类型的mutex

library cache:mutex X

11g前使用library cache latch保护hash bucket和其后的链表,11g开始不再用,而是换成library cache:mutex

无论软解析还是硬解析,进程都要独占方式获得library cache:mutex ,然后才能访问hash链,如果遇到竞争,产生等待事件,那这里的等待事件是library cache:mutex X

在v$mutex_sleep或是v$mutex_sleep_history的location列可以看到mutex miss是kglhdgn1  62 或是在awr报告中大mutex sleep summary部分看到location为kglhdgn1  62竞争多,说明在搜索hash bucket后的链表时遇到竞争

搜索hash链的目的是找到父游标句柄,找到父游标句柄后,要再次申请以独占方式持有library cache:mutex,成功后才能访问父游标句柄内的信息。如果在申请时遇到竞争,产生等待事件,这里的等待事件也是library cache:mutex X 。此处理的 mutex miss通常是kgldhgn2 106 。在此处mutex保护下,进程获得父游标句柄上的library cache lock,成功后,mutex被释放。也就是说,此处的mutex是代替以前版本的library cache lock latch(11g前是使用library cache lock latch,11g后换成library cache:mutex)

在子游标句柄,还有其他对象的句柄上都会有同样的mutex和library cache lock

hash table:mutex

找到父游标句柄,也加上了library cache lock,接下来,从父游标句柄中取出父游标堆0的地址,并访问父游标堆0。父游标堆0中包含了子游标句柄地址,这些子游标句柄地址构成了了游标列表,如下图:

技术分享技术分享

访问父游标堆0的目的是在子游标列表中查找子游标句柄,这里当然要持有mutex。此处会持有两种类型的mutex:一种是cursor parent,另一种是hash table

oracle先持有cursor parent类型的mutex,访问父游标堆0中的其他信息,然后释放。再持有hash table类型的mutex,搜索了子游标列表,查找子游标句柄,找到后释放。这两种类型的mutex对应等待事件都是cursor:mutex S

搜索子游标列表,从mutex类型上推测应为hash table:mutex。它只保护子游标列表,如果此类型mutex遇到竞争,说明,某sql语句版本太多。其实hash table:mutex承担了之前版本中的library cache pin和library cache pin latch的作用。

cursor pin

整个解析过程会先访问hash链表,然后访问父游标句柄,父游标堆0,再访问子游标句柄,最后是子游标堆0和包含执行计划的堆6

对子游标堆0和堆6所加的mutex的类型是cursor pin,对应的等待事件是cursor:pin S或cursor:pin S wait on X

堆6的cursor pin类型的mutex比较特殊,这个mutex并不在子游标堆6中,它在父游标堆0中,因为子游标堆6的DS在父游标堆0中。

硬解析时需要独占、共享模式多次持有父游标堆0与子游标堆6上的mutex

软解析时通常不需要访问子游标堆0,可以从父游标堆0中找到子游标堆6,直接访问子游标堆6中的执行计划

软软解析时子游标堆6的DS地址保存在PGA中

技术分享技术分享

通过mutex判断解析问题

硬解析:

需要所有mutex,申请多次shared pool latch,软解析只需要少量的shared pool latch,软软解析不需要shared pool latch

因此:如果shared pool latch竞争激烈,一定是硬解析过多,还有一种情况,版本过多。

同一父游标下多个子游标同时硬解析,会造成library cache lock竞争和保护子游标列表的mutex也会竞争,为hash table型的mutex

因此:如果library cache lock和hash table型的mutex同时出现,硬解析过多

           如果只有hash table型的mutex,说明版本过多

在awr中mutex sleep summary可以查看,并做相应判断。

软解析:

搜索hash bucket后链表时,需要加library cache型的mutex

访问父游标句柄时,也需要加library cache型的mutex

访问父游标堆0、搜索子游标句柄列表时,需要hash table型的mutex

访问子游标句柄时,还是用需要加library cache型的mutex

访问子游标堆6,读取执行计划时,需要cursor pin型的mutex

补充:动态游标,在解析后,如果使用绑定变量,将变量值传入共享池,这一步要独占library cache型mutex

          静态游标,绑定变量不传入共享池,也就不需要library cache型mutex

软软解析:

在PGA cache cursor列表中搜索子游标堆6DS地址,不需要任何mutex、latch

根据得到的子游标堆6的DS地址,访问共享池中子游标堆6,读取执行计划,需要共享模式的cursor pin型mutex,此处可能遇到的等待事件为cursor:pin S

如果使用绑定变量,将绑定变量值传入共享池,这一步要独占library cache型mutex。同样,如果是静态游标,绑定变量不传入共享池,也就不需要library cache型mutex

执行、抓取结束后,需要释放共享cursor pin型mutex,此处也可能遇到等待事件cursor:pin S

总结:

如果只有cursor pin型和library cache型的mutex竞争,那是软软解析

如果还有其他mutex等待,那是软解析

如果还有shared pool latch等待,那是硬解析

解决解析阶段竞争:

硬解析:一般有如下3种原因

没有使用绑定变量

父游标版本过高

共享池小

第一种,没有绑定变量,查询v$sqlarea.sql_text,看相似语句是否过多。修改应用是上上策,cursor_sharing参数不是好方法

第二种,父游标版本过高,查询v$sql_shared_cursor。11g的adaptive cursor sharing(ACS),可能会造成版本过多问题,oltp环境,可以考虑关闭此特性。

第三种,共享池小,可能使用瞬时LRU与周期LRU比值判断,如小,则加大

软解析:两种解决方法

调整应用,减少软解析

调整session_cached_cursors,化软解析为软软解析

总结:

硬解析过多,可以使用绑定变量、加大共享池,化硬解析为软解析

软解析过多,可以调大session_cached_cursors,化软解析为软软解析

软软解析一个小测试:

建立两个连接

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       226

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       242

在每个会话中执行如下:

declare
mcur number;
mstat number;
v_name varchar2(40);
begin
mcur:=dbms_sql.open_cursor;
for i in 1 .. 10000000 loop
dbms_sql.parse(mcur,‘select * from moe where id=1‘,dbms_sql.native);
mstat:=dbms_sql.execute(mcur);
end loop;
dbms_sql.close_cursor(mcur);
end;
/

查看上面两个会话的等待事件:

select sid,event,p1raw,p2raw,p3raw from v$session where sid in (‘226‘,‘242‘);

       SID EVENT           P1RAW            P2RAW            P3RAW
---------- --------------- ---------------- ---------------- ----------------
       226 cursor: pin S   00000000D756DDA2 0000000000000001 0000000400000000
       242 cursor: pin S   00000000D756DDA2 0000000000000002 0000000900000000

解决如下:

在226中执行如下:

declare
mcur number;
mstat number;
v_name varchar2(40);
begin
mcur:=dbms_sql.open_cursor;
for i in 1 .. 10000000 loop
dbms_sql.parse(mcur,‘select /* sess_226 */ * from moe where id=1‘,dbms_sql.native);
mstat:=dbms_sql.execute(mcur);
end loop;
dbms_sql.close_cursor(mcur);
end;
/

在242中执行如下:

declare
mcur number;
mstat number;
v_name varchar2(40);
begin
mcur:=dbms_sql.open_cursor;
for i in 1 .. 10000000 loop
dbms_sql.parse(mcur,‘select /* sess_242 */ * from moe where id=1‘,dbms_sql.native);
mstat:=dbms_sql.execute(mcur);
end loop;
dbms_sql.close_cursor(mcur);
end;
/

查看两个会话的等待:

SQL> select sid,event,p1raw,p2raw,p3raw from v$session where sid in (‘226‘,‘242‘);

       SID EVENT                         P1RAW            P2RAW            P3RAW
---------- ----------------------------- ---------------- ---------------- ----------------
       226 SQL*Net message from client   0000000062657100 0000000000000001 00
       242 SQL*Net message from client   0000000062657100 0000000000000001 00

to be continued...

#end

整理自:oracle内核技术揭密 吕海波著

本文出自 “刚刚出壳的小鸟” 博客,请务必保留此出处http://qhd2004.blog.51cto.com/629417/1597647

mutex----我是搬运工