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事务槽是数据块头中用来标记事务的记录。在这里有个重点是\color{red}{数据块}。想一想,如果\color{red}{事务跨数据块}了会怎样。这就是这个死锁的关键点。当然不同表的事务肯定跨数据块了,一个事务即使修改一个表的多条数据也可能跨块了。\color{red}{这里的情况是,order表上事务都是通过主键来操作的,对于一条数据,要跨越数据块,行迁移或者行连接会有这种情况。}

  • 简单说下这两个情况
    行迁移一般是update后经常出现,比如一个err_mesg字段,初期只有10个字符,后面update为1000个字符,如果这个时候原数据块装不下了,他就会找另外的数据块来存放,而原数据块上放一个新数据块的dba(data block address),指向新的数据块,如下图:


    image.png

行连接一般是insert时出现的,比如一条数据非常大,大到一个块装不下了,oracle会拆分成多个块来存放。可以通过创建块尺寸小的表空间来测试。

到此处,\color{red}{我们要明白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置为1
create 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的锁
  --形成了互相等待,闭环,死锁形成
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259