zoukankan      html  css  js  c++  java
  • 实战演练丨Oracle死锁案例分析,看完你就懂了

    问题背



    发生Oracle死锁的多个进程执行的都是同一个存储过程,大概代码及顺序如下:


    --1.首先通过主键order_no锁住一条订单

    select t.* from order t where t.order_no='order_no' for update; 

    --2.其次通过主键channel_id锁住一个渠道

    select t.* from channel t where t.channel_id='channel_id' for update; 

    --3.然后通过主键order_no对订单表数据进行修改

    update order t set t.order_status=0,t.finish_time=sysdate where t.order_no='order_no';

    commit;


    死锁情况描述



    session A

    --正在执行语句3,他处于enq: TX - allocate ITL entry等待

    update order t set t.order_status=0,t.finish_time=sysdate where t.order_no='orderno_a';


    session B

    --正在执行语句2,他处于enq: TX - row lock contention等待

    select t.* from channel t where t.channel_id='ch1' for update;


    session C

    正在执行语句2,他处于enq: TX - row lock contention等待

    select t.* from channel t where t.channel_id='ch1' for update;


    可能还会有更多的session处于执行语句2,并等待enq: TX - row lock contention的情况,这里暂时只列3个session,其实2个也够了,也能形成,只是概率很低。


    等待链 



    A被C堵塞,C被B堵塞,B被A堵塞

    等待链分析: 

    A执行到语句3了,说明主键为orderno_a的order数据行锁和ch1的channel数据行锁已经获取到了,而其余的B和C只能等待该ch1数据的行锁释放。 

    B和C都执行到语句2了,说明他们都获取到了各自的order数据行锁,且数据不是orderno_a锁代表的数据。这点毋庸置疑。

    疑问 :A,B,C操作的都是不同的订单数据行,且都获取到了各自的行锁的,为什么在表order上,还会发生A被C堵塞呢。


    要知道为什么有这个疑问,就要先明白,在A执行order的for update时是已经获取了itl资源的,所以在后来真正update数据时是不应该存在这个等待的enq: TX - allocate ITL entry,因为他已经获取这个资源了。


    死锁分析



    要分析这个死锁就要明白等待事件enq: TX - allocate ITL entry所代表的资源itl事务槽的含义。itl事务槽是数据块头中用来标记事务的记录。在这里有个重点是 数据块 。想一想,如果 事务跨数据块 了会怎样。这就是这个死锁的关键点。当然不同表的事务肯定跨数据块了,一个事务即使修改一个表的多条数据也可能跨块了。 这里的情况是,order表上事务都是通过主键来操作的,对于一条数据,要跨越数据块,行迁移或者行连接会有这种情况。


    简单说下这两种情况 

    行迁移一般是update后经常出现,比如一个err_mesg字段,初期只有10个字符,后面update为1000个字符,如果这个时候原数据块找不下了,他就会找另外的数据块来存放,而原数据块上放一个新数据块的dba(data block address),指向新的数据块,如下图: 行连接一般是insert时出现的,比如一条数据非常大,大到一个块装不下了,oracle会拆分成多个块来存放。可以通过创建块尺寸小的表空间来测试。


    到此处, 要明白itl是数据块上的资源,即使是同一个事务中,如果事务跨数据块了,当他要修改这个数据块时,他也需要重新再次在这个新块上申请itl资源 ,也就是我这里死锁中,假设orderno_a数据rowid指向的块为dba_1,行迁移中指向的块为dba_2,在最开始for update时获取的是块dba_1中的itl资源,当最后真正update数据时,为了保护操作,需要获取dba_2上的itl资源。而此时,其余的很多session,比如B,C......N 等等session将块dba_2上的itl资源耗尽了,那么session A就处于等待数据块dba_2上的itl资源的状态,对应于enq: TX - allocate ITL entry。而其他session将等待session A释放渠道表数据的锁。完成了锁的闭环


    到此死锁分析完毕。


    测试



    --创建order表,将PCTFREE置为0,INITRANS置为1create table t_order(mesg varchar2(4000)) PCTFREE 0 INITRANS 1;

    --创建channel表

    create table t_channel(id NUMBER);

    --准备数据,对于order表,至少要有两个块有数据

    --第一个块的数据,有三条,即a,b,c

    insert into t_order select rpad('a',3000,'a') from dual;

    insert into t_itl select rpad('b',1000,'b') from dual;

    insert into t_order select rpad('c',3000,'c') from dual;

    --更改数据b,此时第一个块装不下,将会发生行迁移

    update t_order set mesg=(select rpad('b',3000,'b') from dual) where mesg like 'b%';

    --可以使用以下语句分析行迁移的表,只用作测试,在线生产慎用,可以dump第一个数据块找到,迁移到哪一个dba去了

    create table CHAINED_ROWS (

      owner_name        varchar2(30),

      table_name        varchar2(30),

      cluster_name      varchar2(30),

      partition_name    varchar2(30),

      subpartition_name  varchar2(30),

      head_rowid        rowid,

      analyze_timestamp  date

    );

    analyze table t_order list chained rows;

    select * from CHAINED_ROWS;

    --继续插入数据,将迁移后的数据块数据增加,方便之后for update时消耗这个块的itl资源

    --通常情况,下面插入的数据就是放在b数据迁移后的数据块的

    insert into t_order select rpad('d',1000,'d') from dual;

    insert into t_order select rpad('f',6000,'f') from dual;

    insert into t_order select rpad('g',300,'g') from dual;

    insert into t_order select rpad('h',100,'h') from dual;

    /*开始模拟死锁*/

    --t1时刻

      --session A 

        select * from t_order where mesg like 'b%' for update;

        select * from  t_channel where id=1 for update;

     --t2时刻

      --session B

        select * from t_order where mesg like 'd%' for update;

        select * from  t_channel where id=1 for update;--等待session A 释放

      --其余session

        select * from t_order where mesg like 'f%' for update;

        select * from  t_channel where id=1 for update;--加入该条数据的行锁等待

        select * from t_order where mesg like 'g%' for update;

        select * from  t_channel where id=1 for update;--加入该条数据的行锁等待

        .....

    /*如果这些数据不在b所在的块,可以通过设置where条件为以下内容来指定更改b迁移后的块

    where DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) = 'block_no' 

        and DBMS_ROWID.ROWID_ROW_NUMBER(ROWID) = 1;

    --此时session B与其余session将t_order的第二个块,即d,f,g,h数据所在的块的itl耗尽

    --t3时刻

      --session A 去更改t_order的数据

        update t_order t set t.mesg='bbbbb' where t.mesg like 'b%';

      --此时会等待session B及其他session释放itl资源,而session B及其他session又在等待session A释放channel的锁

      --形成了互相等待,闭环,死锁形成


    出处:linuxidc.com/



    公司简介 | 恩墨学院 | 招聘 | DTCC | 数据技术嘉年华 | 免费课程 | 入驻华为严选商城

      640?wx_fmt=jpeg

    zCloud | SQM | Bethune Pro2 zData一体机 | Mydata一体机 | ZDBM 备份一体机

    640?wx_fmt=jpeg

    Oracle技术架构 | 免费课程 数据库排行榜 | DBASK问题集萃 | 技术通讯 

    640?wx_fmt=jpeg

    升级迁移 | 性能优化 | 智能整合 安全保障 | 

    云和恩墨大讲堂 | 一个分享交流的地方

    长按,识别二维码,加入万人交流社群


    640?wx_fmt=jpeg

    请备注:云和恩墨大讲堂

  • 相关阅读:
    HDU1266 Reverse Number
    codevs1380 没有上司的舞会
    codevs1163 访问艺术馆
    codevs2144 砝码称重 2
    codevs1553 互斥的数
    codevs1230 元素查找
    codevs3118 高精度练习之除法
    codevs1245 最小的N个和
    codevs1063 合并果子
    codevs1052 地鼠游戏
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13312084.html
Copyright © 2011-2022 走看看