概述:
- 锁机制在维护数据库并发性和一致性中扮演重要的角色,它可以防止错误的数据修改或错误的数据结构调整。
- 锁,死锁,阻塞是三个既独立又有关联的概念,会话持有锁时,只有其他会话也想访问相同的锁资源时,才可能产生阻塞(某些情况下并不阻塞,例如DML操作并不阻塞查询),所以有锁是正常的,有阻塞才会影响业务,平常说的“被锁了”,绝大多数是指存在阻塞现象。死锁是指两个或更多会话互相阻塞对方,造成死循环,此时oracle判断死锁现象,会自动杀掉一个会话,解除死锁。
- 锁按类型分有自动锁,显示锁和自定义锁。
本章参考文档:
官方文档《Database Concepts》
-> Data Concurrency and Consistency
http://docs.oracle.com/cd/E11882_01/server.112/e40540/consist.htm - CNCPT020
1.DML锁
- Row locks(TX):是表中的单行锁,每次增、删、改、查或者merge,都会有一个事物获得一个row lock,直至事物commit或者rollback。行级锁主要用来防止两个事物修改一行数据,oracle不像其他数据库使用内存中锁列表管理锁,而是将锁信息放在数据块的头部。
- Table lock(TM):当有增删改或select...for update 操作时会自动被事物获得,DML操作需要获得table locks 来防止DDL操作时的冲突。
2.DDL锁
3.阻塞的查询
- 脚本 1
- 本脚本利用试图v$session 中的blocking_instance 及 blocking_session 字段内容显示阻塞会话和被阻塞会话,阻塞包括各种情况,可能还是dml阻塞,也可能是ddl阻塞或者内部锁阻塞。⚠️oracle 10g中此字段内容经常有误,尽量不使用脚本;11g中这两个字段偶尔为空,此时不一定没有阻塞。
col username for a10
col program for a30
col event for a30
set linesize 120
set pagesize 999
WITH tkf_block_info
AS (SELECT a.inst_id || '_' || a.sid waiting_session,
a.username,
a.program,
a.event,
a.sql_id,
a.last_call_et,
DECODE (a.blocking_instance || '_' || a.blocking_session,
'_', NULL,
a.blocking_instance || '_' || a.blocking_session)
holding_session
FROM gv$session a,
(SELECT inst_id, sid
FROM gv$session
WHERE blocking_session IS NOT NULL
UNION
SELECT blocking_instance, blocking_session
FROM gv$session
WHERE blocking_session IS NOT NULL) b
WHERE a.inst_id = b.inst_id AND a.SID = b.sid)
SELECT LPAD (' ', 3 * (LEVEL - 1)) || waiting_session waiting_session,
username,
program,
event,
sql_id,
last_call_et
FROM tkf_block_info
CONNECT BY PRIOR waiting_session = holding_session
START WITH holding_session IS NULL;
- 脚本 2
col WAIT_EVENT_TEXT for a30
col WAITER_INSTID_SID_SERIAL# for a25
col blocker_instid_sid_serial# for a26
set linesize 120
set pagesize 999
SELECT inst_id || '_' || sid || '_' || SESS_SERIAL# waiter_instid_sid_serial#,
WAIT_EVENT_TEXT,
BLOCKER_INSTANCE_ID
|| '_'
|| BLOCKER_SID
|| '_'
|| BLOCKER_SESS_SERIAL#
blocker_instid_sid_serial#
FROM gv$session_blockers;
col waiting_session for a20
col lock_type for a15
col mode_requested for a10
col mode_held for a10
col lock_id1 for a10
col lock_id2 for a10
set linesize 120
set pagesize 999
with dba_locks_cust as
(SELECT inst_id||'_'||sid session_id,
DECODE (TYPE,
'MR', 'Media Recovery',
'RT', 'Redo Thread',
'UN', 'User Name',
'TX', 'Transaction',
'TM', 'DML',
'UL', 'PL/SQL User Lock',
'DX', 'Distributed Xaction',
'CF', 'Control File',
'IS', 'Instance State',
'FS', 'File Set',
'IR', 'Instance Recovery',
'ST', 'Disk Space Transaction',
'TS', 'Temp Segment',
'IV', 'Library Cache Invalidation',
'LS', 'Log Start or Switch',
'RW', 'Row Wait',
'SQ', 'Sequence Number',
'TE', 'Extend Table',
'TT', 'Temp Table',
TYPE)
lock_type,
DECODE (lmode,
0, 'None', /* Mon Lock equivalent */
1, 'Null', /* N */
2, 'Row-S (SS)', /* L */
3, 'Row-X (SX)', /* R */
4, 'Share', /* S */
5, 'S/Row-X (SSX)', /* C */
6, 'Exclusive', /* X */
TO_CHAR (lmode))
mode_held,
DECODE (request,
0, 'None', /* Mon Lock equivalent */
1, 'Null', /* N */
2, 'Row-S (SS)', /* L */
3, 'Row-X (SX)', /* R */
4, 'Share', /* S */
5, 'S/Row-X (SSX)', /* C */
6, 'Exclusive', /* X */
TO_CHAR (request))
mode_requested,
TO_CHAR (id1) lock_id1,
TO_CHAR (id2) lock_id2,
ctime last_convert,
DECODE (block,
0, 'Not Blocking', /* Not blocking any other processes */
1, 'Blocking', /* This lock blocks other processes */
2, 'Global', /* This lock is global, so we can't tell */
TO_CHAR (block))
blocking_others
FROM gv$lock
),
lock_temp as
(select * from dba_locks_cust),
lock_holder as
(
select w.session_id waiting_session,
h.session_id holding_session,
w.lock_type,
h.mode_held,
w.mode_requested,
w.lock_id1,
w.lock_id2
from lock_temp w, lock_temp h
where h.blocking_others in ('Blocking','Global')
and h.mode_held != 'None'
and h.mode_held != 'Null'
and w.mode_requested != 'None'
and w.lock_type = h.lock_type
and w.lock_id1 = h.lock_id1
and w.lock_id2 = h.lock_id2
),
lock_holders as
(select waiting_session,holding_session,lock_type,mode_held,
mode_requested,lock_id1,lock_id2
from lock_holder
union all
select holding_session, null, 'None', null, null, null, null
from lock_holder
minus
select waiting_session, null, 'None', null, null, null, null
from lock_holder
)
select lpad(' ',3*(level-1)) || waiting_session waiting_session,
lock_type,
mode_requested,
mode_held,
lock_id1,
lock_id2
from lock_holders
connect by prior waiting_session = holding_session
start with holding_session is null;
- 脚本 2
set serveroutput on size unlimited
set feedback off
DECLARE
v_num_sessions INTEGER := 0;
CURSOR cv IS
SELECT
dba_objects.object_name,
locks_t.row#,
locks_t.blocked_secs,
locks_t.blocker_text,
locks_t.blocked_text,
locks_t.blocked_sql_text
FROM (SELECT /*+ NO_MERGE */
blocking_lock_session.username||'@'||blocking_lock_session.machine||'(INST_ID='||blocking_lock_session.inst_id||',SID='||blocking_lock_session.sid||') ['||
blocking_lock_session.program||'/PID='||blocking_lock_session.process||']' as blocker_text,
blocked_lock_session.username||'@'||blocked_lock_session.machine|| '(INST_ID='||blocked_lock_session.inst_id||',SID='||blocked_lock_session.sid||') ['||
blocked_lock_session.program||'/PID='||blocked_lock_session.process||']' as blocked_text,
blocked_lock_session.row_wait_obj#,
blocked_lock_session.row_wait_file#,
blocked_lock_session.row_wait_block#,
blocked_lock_session.row_wait_row#,
DBMS_ROWID.ROWID_CREATE (1,
blocked_lock_session.row_wait_obj#,
blocked_lock_session.row_wait_file#,
blocked_lock_session.row_wait_block#,
blocked_lock_session.row_wait_row#) row#,
blocked_lock_session.seconds_in_wait blocked_secs,
blocked_sql.sql_text blocked_sql_text
FROM gv$lock blocking_lock,
gv$session blocking_lock_session,
gv$lock blocked_lock,
gv$session blocked_lock_session,
gv$sql blocked_sql
WHERE blocking_lock.inst_id=blocking_lock_session.inst_id
and blocked_lock.inst_id=blocked_lock_session.inst_id
and blocked_lock_session.inst_id=blocked_sql.inst_id
and blocking_lock.block = 1
AND blocking_lock.id1 = blocked_lock.id1
AND blocking_lock.id2 = blocked_lock.id2
AND blocked_lock.request > 0
AND blocking_lock.sid = blocking_lock_session.sid
AND blocked_lock.sid = blocked_lock_session.sid
AND blocked_lock_session.sql_id = blocked_sql.sql_id
AND blocked_lock_session.sql_child_number = blocked_sql.child_number
) locks_t,
dba_objects
WHERE locks_t.row_wait_obj# = dba_objects.object_id
AND locks_t.blocked_secs > 10
ORDER BY locks_t.blocked_secs;
BEGIN
FOR cv_rec IN cv LOOP
dbms_output.put_line(
'========= $Revision: 1.4 $ ($Date: 2013/09/16 13:15:22 $) ===========');
v_num_sessions := v_num_sessions + 1;
dbms_output.put_line('Locked object : '||
cv_rec.object_name);
dbms_output.put_line('Locked row# : '||
cv_rec.row#);
dbms_output.put_line('Blocked for : '||
cv_rec.blocked_secs||' seconds');
dbms_output.put_line('Blocker info. : '||
cv_rec.blocker_text);
dbms_output.put_line('Blocked info. : '||
cv_rec.blocked_text);
dbms_output.put_line('Blocked SQL : '||
cv_rec.blocked_sql_text);
END LOOP;
dbms_output.new_line;
dbms_output.put_line('Found '||TO_CHAR(v_num_sessions)||
' blocked session(s).');
END;
/
- 脚本 1
col waiting_session for a20
col lock_type for a15
col mode_requested for a10
col mode_held for a10
col lock_id1 for a10
col lock_id2 for a10
set linesize 120
set pagesize 999
with lock_temp as
(select * from dba_locks),
lock_holder as
(
select w.session_id waiting_session,
h.session_id holding_session,
w.lock_type,
h.mode_held,
w.mode_requested,
w.lock_id1,
w.lock_id2
from lock_temp w, lock_temp h
where h.blocking_others = 'Blocking'
and h.mode_held != 'None'
and h.mode_held != 'Null'
and w.mode_requested != 'None'
and w.lock_type = h.lock_type
and w.lock_id1 = h.lock_id1
and w.lock_id2 = h.lock_id2
),
lock_holders as
(select waiting_session,holding_session,lock_type,mode_held,
mode_requested,lock_id1,lock_id2
from lock_holder
union all
select holding_session, null, 'None', null, null, null, null
from lock_holder
minus
select waiting_session, null, 'None', null, null, null, null
from lock_holder
)
select lpad(' ',3*(level-1)) || waiting_session waiting_session,
lock_type,
mode_requested,
mode_held,
lock_id1,
lock_id2
from lock_holders
connect by prior waiting_session = holding_session
start with holding_session is null;
- 脚本 2 (适用于 oracle 10g以上)
set linesize 120
set pagesize 999
select waiting_session,holding_session,lock_type,lock_id1,mode_held from dba_waiters;
- 脚本 3
drop table lock_holders;
create table LOCK_HOLDERS /* temporary table */
(
waiting_session number,
holding_session number,
lock_type varchar2(26),
mode_held varchar2(14),
mode_requested varchar2(14),
lock_id1 varchar2(22),
lock_id2 varchar2(22)
);
drop table dba_locks_temp;
create table dba_locks_temp as select * from dba_locks;
/* This is essentially a copy of the dba_waiters view but runs faster since
* it caches the result of selecting from dba_locks.
*/
insert into lock_holders
select w.session_id,
h.session_id,
w.lock_type,
h.mode_held,
w.mode_requested,
w.lock_id1,
w.lock_id2
from dba_locks_temp w, dba_locks_temp h
where h.blocking_others = 'Blocking'
and h.mode_held != 'None'
and h.mode_held != 'Null'
and w.mode_requested != 'None'
and w.lock_type = h.lock_type
and w.lock_id1 = h.lock_id1
and w.lock_id2 = h.lock_id2;
commit;
drop table dba_locks_temp;
insert into lock_holders
select holding_session, null, 'None', null, null, null, null
from lock_holders
minus
select waiting_session, null, 'None', null, null, null, null
from lock_holders;
commit;
column waiting_session format a17;
column lock_type format a17;
column lock_id1 format a17;
column lock_id2 format a17;
/* Print out the result in a tree structured fashion */
select lpad(' ',3*(level-1)) || waiting_session waiting_session,
lock_type,
mode_requested,
mode_held,
lock_id1,
lock_id2
from lock_holders
connect by prior waiting_session = holding_session
start with holding_session is null;
drop table lock_holders;
col object_name for a30
set linesize 120
set pagesize 999
SELECT a.inst_id,
a.session_id,
b.object_name,
b.object_type,
a.locked_mode
FROM gv$locked_object a, dba_objects b
WHERE a.object_id = b.object_id AND b.object_name = '<object_name>';
elect /*+ ordered */ w1.sid waiting_session,
h1.sid holding_session,
w.kgllktype lock_or_pin,
w.kgllkhdl address,
decode(h.kgllkmod, 0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
'Unknown') mode_held,
decode(w.kgllkreq, 0, 'None', 1, 'Null', 2, 'Share', 3, 'Exclusive',
'Unknown') mode_requested
from dba_kgllock w, dba_kgllock h, v$session w1, v$session h1
where
(((h.kgllkmod != 0) and (h.kgllkmod != 1)
and ((h.kgllkreq = 0) or (h.kgllkreq = 1)))
and
(((w.kgllkmod = 0) or (w.kgllkmod= 1))
and ((w.kgllkreq != 0) and (w.kgllkreq != 1))))
and w.kgllktype = h.kgllktype
and w.kgllkhdl = h.kgllkhdl
and w.kgllkuse = w1.saddr
and h.kgllkuse = h1.saddr
/
waiting_session 是被阻塞的会话,holding_session是阻塞者,address是被阻塞对象的地址,可通过如下语句查看对象名称
select to_name from v$object_dependency where to_address = '<address>';
通过session id,也可以查看该会话所使用的library对象
select distinct kglnaobj from x$kgllk where
kgllkuse in (select saddr from v$session where sid = <sid>);
- 脚本 1
col object for a30
col owner for a20
col type for a20
set linesize 120
set pagesize 999
select * from gv$access where owner='<username>' and object='<object_name>';
- 脚本 2
COL program FOR a30
COL event FOR a30
COL waiting_obj FOR a25
SET LINESIZE 130
SET PAGESIZE 999
SELECT b.kglnaown || '.' || b.kglnaobj waiting_obj,
c.sid,
c.username,
c.program,
c.event,
c.sql_id
FROM dba_kgllock a, x$kglob b, v$session c
WHERE ( (a.kgllkmod = 0)
OR (a.kgllkmod = 1)
AND ( (a.kgllkreq != 0) AND (a.kgllkreq != 1)))
AND a.kgllkhdl = b.kglhdadr
AND a.kgllkuse = c.saddr;
COL username FOR a15
COL program FOR a30
COL event FOR a30
COL waiting_obj FOR a25
SET LINESIZE 130
SET PAGESIZE 999
SELECT b.kglnaown || '.' || b.kglnaobj waiting_obj,
c.sid,
c.username,
c.program,
c.event,
c.sql_id
FROM x$kglpn a, x$kglob b, v$session c
WHERE ( (a.kglpnmod != 0)
AND (a.kglpnmod != 1)
AND ( (a.kglpnreq = 0) OR (a.kglpnreq = 1)))
AND a.kglpnhdl = b.kglhdadr
AND a.kglpnuse = c.saddr
AND b.kglnaown = 'TEST'
AND b.kglnaobj = 'TESTSL';
4.阻塞的处理
col inst_id for 99
col sid for 9999
col serial# for 99999
col spid for 9999999
col username for a10
col program for a30
col event for a30
set linesize 130
SELECT a.inst_id,
a.sid,
a.serial#,
b.spid, --该回话对应操作系统层面的进程号
a.username,
a.program,
a.event,
a.sql_id
FROM gv$session a, gv$process b
WHERE a.inst_id = b.inst_id
AND a.paddr = b.addr
AND a.inst_id = <inst_id> AND a.sid = <sid>;
- 方法 1
select spid, program from v$process
where program!= 'PSEUDO'
and addr not in (select paddr from v$session)
and addr not in (select paddr from v$bgprocess)
and addr not in (select paddr from v$shared_server);
对于RAC
select INST_ID, spid, program,'kill -9 '|| spid kill9
from gv$process a
where program != 'PSEUDO'
and (INST_ID, addr) not in (select INST_ID, paddr from gv$session)
and (INST_ID, addr) not in (select INST_ID, paddr from gv$bgprocess)
and (INST_ID, addr) not in (select INST_ID, paddr from gv$shared_server)
and a.PNAME is null;
- 方法 2(11g 适用)
set line 9999
col sessionid format a20
col sessionid_killed format a20
col kill_session format a60
SELECT a.INST_ID,
a.SID || ',' || a.SERIAL# || ',' ||
(select spid
from gv$process b
where b.INST_ID = a.INST_ID
and A.creator_addr = b.ADDR --and decode(a.status,'KILLED',A.creator_addr,A.PADDR) = b.ADDR
) sessionid,
a.PADDR,
a.STATUS,
a.PROGRAM,
'alter system disconnect session ''' || sid || ',' || serial# || ''' immediate;' kill_session
FROM gv$session a
WHERE a.USERNAME = 'SYS'
and a.STATUS = 'KILLED';
- 方法 3 (11g 适用)
SELECT a.SID || ',' || a.SERIAL# || ',' ||
(select spid
from gv$process b
where b.INST_ID = a.INST_ID
and A.pid = b.pid) sessionid,
'alter system kill session ''' || sid || ',' || serial# || ''';' kill_session
FROM gV$DETACHED_SESSION a;
- 方法 4
SELECT s.SID, s.username,s.status,
x.ADDR,x.KSLLAPSC,x.KSLLAPSN,x.KSLLASPO,x.KSLLID1R,x.KSLLRTYP,
decode(bitand(x.ksuprflg,2),0,null,1)
FROM x$ksupr x,v$session s
WHERE s.paddr(+)=x.addr
and bitand(ksspaflg,1)!=0
;
5.锁需要注意的地方
delete语句为:
delete from hldrs.kc21 where aaz217=97
kc21表aaz217字段是主键,存在主键索引,字段类型也是number,但经测试,该语句执行需要3分钟。
查看该语句执行计划,确实走的是index unique scan,理论上应该很快结束,使用10046事件跟踪语句执行过程,发现该语句后台不只执行delete的操作,还有一个针对kc24表的全表查询
原因很清楚了,在对kc21表进行delete时,相当于减少了主键字段数据,而由于kc24需要参考该主键,所以要对外键字段进行扫描,检查是否要进行维护,而kc24的外键字段没有索引存在,故只能走全表扫描,kc24表约9gb大小,扫描一次大约要3分钟。
根据以上分析,建议研发在kc24的外键字段上创建索引。经验证,创建索引后删除速度正常。