锁(lock)相关知识总结

概述:

  • 锁机制在维护数据库并发性和一致性中扮演重要的角色,它可以防止错误的数据修改或错误的数据结构调整。
  • 锁,死锁,阻塞是三个既独立又有关联的概念,会话持有锁时,只有其他会话也想访问相同的锁资源时,才可能产生阻塞(某些情况下并不阻塞,例如DML操作并不阻塞查询),所以有锁是正常的,有阻塞才会影响业务,平常说的“被锁了”,绝大多数是指存在阻塞现象。死锁是指两个或更多会话互相阻塞对方,造成死循环,此时oracle判断死锁现象,会自动杀掉一个会话,解除死锁。
  • 锁按类型分有自动锁,显示锁和自定义锁。

本章参考文档:

官方文档《Database Concepts

-> Data Concurrency and Consistency

http://docs.oracle.com/cd/E11882_01/server.112/e40540/consist.htm - CNCPT020


1.DML锁

DML(data manipulation language:数据库操作语言),包括:select、insert、update、delete。执行dml语句会自动获得如下锁:
  •  Row locks(TX):是表中的单行锁,每次增、删、改、查或者merge,都会有一个事物获得一个row lock,直至事物commit或者rollback。行级锁主要用来防止两个事物修改一行数据,oracle不像其他数据库使用内存中锁列表管理锁,而是将锁信息放在数据块的头部。
  • Table lock(TM):当有增删改或select...for update 操作时会自动被事物获得,DML操作需要获得table locks 来防止DDL操作时的冲突。
DML产生的事务不会自动提交。



2.DDL锁

Data Definition Language 是对数据对象定义,改变结构及删除操作。包括:alter 、analyze 、 ASSOCIATE STATISTICS/DISASSOCIATE STATISTICS 、audit/noaudit 、comment 、 create 、 drop 、 flaskback 、 grant/revoke 、purge 、 rename 、truncate 。DDL锁在做DDL事物时自动获得,用户不能显示获得DDL锁,。例如:当用户创建一个存储过程,数据库自动获得所有与存储过程定义有关的对象的DDL锁,这些DDL锁会在存储过程编译完成前防止对象被修改或删除。




3.阻塞的查询

3.1 dml及ddl通用阻塞查询( oracle 11g 以上适用,单机 及 rac 通用)
  • 脚本 1
  • 本脚本利用试图v$session 中的blocking_instance 及 blocking_session 字段内容显示阻塞会话和被阻塞会话,阻塞包括各种情况,可能还是dml阻塞,也可能是ddl阻塞或者内部锁阻塞。⚠️oracle 10g中此字段内容经常有误,尽量不使用脚本;11g中这两个字段偶尔为空,此时不一定没有阻塞。
col waiting_session for a15
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
本脚本利用11g新出现的视图,gv$session_blocks 显示阻塞与被阻塞会话


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;



3.2 DML阻塞查询
3.2.1 RAC及单实例通用

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;

/


3.2.2 单实例专用
单实例专用也适用于RAC中阻塞与被阻塞会话位于同一实例情况
  • 脚本 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;


3.3 DDL阻塞查询

DDL阻塞有很多种情况,最常见的是存储过程,函数,包等执行过程中如果再次编译,后面的会话会hang,过段时间会返回ORA-04021:timeout occurred while waiting to lock object错误。其他情况还有在dml操作的表上建索引时,返回ORA-00054:resource busy and acquire with nowait specified 错误;在有dml操作的表上增加字段的操作, 10g 会返回ORA-00054, 11g会话会hang;rebuild 索引时如果该索引正在进行统计信息收集时包ORA-00054错误,检查当前持有该会话对象锁的会话: 

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>';


接下来主要介绍最常见的ora-04021的报错

3.3.1 单实例专用
单实例专用也适用于rac 中阻塞与被阻塞会话位于同一实例的情况
另:此脚本如果无法执行,可能需要先执行$ORACLE_HOME/rdbms/admin/catblock.sql创建相关视图

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>);


参考文档How to Analyze Library Cache Timeout with Associated: ORA-04021 'timeout occurred while waiting to lock object %s%s%s%s%s.' Errors (文档ID 1486712.1)


3.3.2 RAC环境
对于Oracle 11g 及以上版本,建议使用 3.1 通用阻塞查询中 的语句直接查询
  • 脚本 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
一下查询因用到 x$ ,需要用到 SYS 用户,脚本只在测试环境通过
a.查看waiter及waiting 对象(各实例均执行)
   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 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;



b.根据结果,查看占用对象的会话(各实例均执行)

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.阻塞的处理

遇到阻塞,根据 章节3的方法查询阻塞者,但查到阻塞者的sid后,可进一步检查会话的详细信息,如果经过判断该阻塞的操作是必要的,无法终止,只能等待;如果阻塞者的操作没有被阻塞者的重要或者说可以终止,则让前端用户自行终止或通过后台直接杀掉会话,杀掉会话使用操作系统层的方法相对快速。

4.1 查看会话信息

根据实例号(inst_id)和会话号(sid)

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>;


4.2数据库层面杀会话
根据查询的sid和serial#,登录到对应实例,使用如下命令杀会话:
alter system disconnect session '<sid>,<serial#>' immediate;

(另 在oracle 10g之前,还有以下命令杀会话:
alter system kill session '<sid>,<serial#>' ;
但该命令要等事物结束或回滚完成,有时会看会话状态(v$session 里的status 列值)是killed,但该会话长时间不结束,顾不推荐此命令杀。)

在oracle 11g之后,上述命令增加了可选的实例号
alter system disconnect session '<sid>,<serial#>,<@inst_id>' immediate;


4.3操作系统层面杀会话
根据查询得到的spid,可以登录到相应的主机上,使用操作系统命令杀该会话对应的进程。
unix&linux 环境里使用如下命令:
kill -9 <spid>
windows 环境里使用如下命令
orakill <ORACLE_SID><SPID>



4.4 会话被标记为KILLED 状态的处理
使用 alter system kill session m命令处理后,pmon会清理会话资源,但是,如果客户端无法再次发送请求(例如死掉或重启),则该会话信息人被保留,但这时已无法通过v$session的paddr和v$session的addr做关联得出操作系统进程号(paddr已经变化,但 11g 里的v$session新增 creator_addr字段可以代替paddr),故需要通过以下方法去操作系统的进程):
  • 方法 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.锁需要注意的地方

5.1 死锁
关于阐述死锁的样例

如图上所见,当两个会话需要修改的数据块被对方持有,即会造成死锁,谁也无法完成,此前 oracle 的解决办法是杀掉其中一个会话,让另一个完成。死锁出现后 oracle 会自行处理,并在警告日志中记录错误信息ORA-00060,并且会产生trace文件,根绝trace文件内容查看是什么会话执行什么sql导致的死锁出现。
死锁的两种常见原因:
1.外键无索引
2.并发DML多,SQL执行慢
另外如果使用了位图索引经常有并发update操作也可能产生死锁。

5.2 外键
强烈建议外键加上索引,因为外键无索引的时候,会增加阻塞的概率,且可能导致死锁,归根结底还是因为加大了锁的面积。
但更新父表的主键(根据关系型数据库的原则,主键应当是不可变的)时,由于外键上没有索引,所以字表会被锁住;当删除了父表中的一行,整个子表也会被锁住,且外键无索引是死锁最大的原因,另外外键无索引还会导致主表dml效率降低。
案例:
 删除父表一行数据,子表外键无索引导致delete速度巨慢

delete语句为:

delete from hldrs.kc21 where aaz217=97

kc21aaz217字段是主键,存在主键索引,字段类型也是number,但经测试,该语句执行需要3分钟。

   查看该语句执行计划,确实走的是index unique scan,理论上应该很快结束,使用10046事件跟踪语句执行过程,发现该语句后台不只执行delete的操作,还有一个针对kc24表的全表查询


原因很清楚了,在对kc21表进行delete时,相当于减少了主键字段数据,而由于kc24需要参考该主键,所以要对外键字段进行扫描,检查是否要进行维护,而kc24的外键字段没有索引存在,故只能走全表扫描,kc24表约9gb大小,扫描一次大约要3分钟。

根据以上分析,建议研发在kc24的外键字段上创建索引。经验证,创建索引后删除速度正常。








猜你喜欢

转载自blog.csdn.net/fc_barceiona/article/details/79137445