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事务槽是数据块头中用来标记事务的记录。在这里有个重点是

math?formula=%5Ccolor%7Bred%7D%7B%E6%95%B0%E6%8D%AE%E5%9D%97%7D。想一想,如果

math?formula=%5Ccolor%7Bred%7D%7B%E4%BA%8B%E5%8A%A1%E8%B7%A8%E6%95%B0%E6%8D%AE%E5%9D%97%7D了会怎样。这就是这个死锁的关键点。当然不同表的事务肯定跨数据块了,一个事务即使修改一个表的多条数据也可能跨块了。

math?formula=%5Ccolor%7Bred%7D%7B%E8%BF%99%E9%87%8C%E7%9A%84%E6%83%85%E5%86%B5%E6%98%AF%EF%BC%8Corder%E8%A1%A8%E4%B8%8A%E4%BA%8B%E5%8A%A1%E9%83%BD%E6%98%AF%E9%80%9A%E8%BF%87%E4%B8%BB%E9%94%AE%E6%9D%A5%E6%93%8D%E4%BD%9C%E7%9A%84%EF%BC%8C%E5%AF%B9%E4%BA%8E%E4%B8%80%E6%9D%A1%E6%95%B0%E6%8D%AE%EF%BC%8C%E8%A6%81%E8%B7%A8%E8%B6%8A%E6%95%B0%E6%8D%AE%E5%9D%97%EF%BC%8C%E8%A1%8C%E8%BF%81%E7%A7%BB%E6%88%96%E8%80%85%E8%A1%8C%E8%BF%9E%E6%8E%A5%E4%BC%9A%E6%9C%89%E8%BF%99%E7%A7%8D%E6%83%85%E5%86%B5%E3%80%82%7D

简单说下这两个情况

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

f27bad2620a8?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

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

到此处,

math?formula=%5Ccolor%7Bred%7D%7B%E6%88%91%E4%BB%AC%E8%A6%81%E6%98%8E%E7%99%BDitl%E6%98%AF%E6%95%B0%E6%8D%AE%E5%9D%97%E4%B8%8A%E7%9A%84%E8%B5%84%E6%BA%90%EF%BC%8C%E5%8D%B3%E4%BD%BF%E6%98%AF%E4%B8%80%E4%B8%AA%E4%BA%8B%E5%8A%A1%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%BA%8B%E5%8A%A1%E8%B7%A8%E6%95%B0%E6%8D%AE%E5%9D%97%E4%BA%86%EF%BC%8C%E4%BB%96%E4%B9%9F%E9%9C%80%E8%A6%81%E9%87%8D%E6%96%B0%E7%94%B3%E8%AF%B7itl%E8%B5%84%E6%BA%90%7D,也就是我这里死锁中,假设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的锁

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值