1. 程式人生 > >高併發場景下oracle觸發器+序列產生序號的一些現象與思考

高併發場景下oracle觸發器+序列產生序號的一些現象與思考

最近工作上因為在處理系統同步的時候遇到了一些問題,在解決過程中,發現了一些現象,所以在這裡mark一下,我現有的殘缺理論體系還無法支撐做出合理的解釋,在網上找了一下,也沒有找到類似的案例,還望各位大拿指點一二。

話不多說,直接上案例,在pl/sql上做的模擬。

原始案例(故障):

1.先建立一個表test,三個欄位id,name,sno

create table test(id int,name varchar2(10),sno int);
2.建立一個序列,起始1,單步增加1
create sequence ss_test minvalue 1 nomaxvalue increment by 1 start with 1 cache 10000;

3.建立一個觸發器
create or replace trigger tr_test before UPDATE or insert on test for each row
begin
    :new.sno := ss_test.nextval;
end;

也就是說 如果我在test上做新增,修改的話,會將該條記錄的sno值置為序號發生器ss_test的下一個值。

非併發場景下(也就是未發生阻塞):

insert into test(id) values(1);
insert into test(id) values(2);
insert into test(id) values(3);
 
select * from test;
插入三條記錄,觸發器工作,並且sno的值等於當前序列值


沒毛病,好,來試試更新


這時,id為1的記錄sno又累加了,依舊沒毛病

好的,現在換個場景,來到併發狀態,在我們的軟體實際執行狀態下,多個執行緒或程序進行寫表或者更新操作的時候,此時我們在pl/sql中用兩個會話進行模擬,在oracle的事務隔離機制下,如果兩個會話同時更新同一條記錄,那麼後執行的將會進入阻塞狀態,直到另一個會話提交或回滾釋放資源。

會話一:更新id為1的記錄的名字為‘jay’,不進行提交,此時可以看到sno的值已經更新到了5


新建會話,同樣去更新id為1的記錄的name,進入掛起狀態,等待會話一釋放資源


重點來了,提交會話一,會話二馬上就會執行,而sno的值直接跳躍式地成了7


以上就是開發過程中發現的一個現象,還請各位大佬能指點一下這種現象的原因,同時,我在探索過程中,模擬了幾個其它場景,排除了幾個我之間假定的原因,供參考一下。

場景1.當執行的update語句中直接包含對sno的修改時,將不會產生上述現象。

把test表恢復到插入3條記錄的狀態,把序列也恢復到初始化狀態


更新id為1的記錄,將sno置為0,觸發 觸發器,sno又被置為了序列的新值4,未提交


此時,新開一個會話,也更新id為1的記錄,sno置為999,進入阻塞狀態


提交會話一,會話二執行,令人奇怪的是,此時的sno未連續增長,老老實實地變成了5,可見oracle觸發器 在處理update語句中 是否包含:new.sno對應的sno欄位的修改 是兩種機制


場景2.修改觸發器本身,不直接處理test表中的sno值,而是將序列值插入另一張表。

新建一個表test2

create table test2(sno int);
修改觸發器tr_test,也就是在新增和修改test的時候,將序列的新值插入另外一張表中記錄流水號
drop trigger tr_test;
create or replace trigger tr_test before update or insert on test for each row
begin
    insert into test2 values(ss_test.nextval);
end;
test還是和之前一樣插入三條記錄並更新

test值如下:


而test2中也已經插入對應的三條記錄


現在來試試併發下的update,更新id為1的記錄的name,置為‘kk’,不提交,test如下:


test2:4已經寫入


新建會話,同樣更新id為1的記錄設定name = 'xx',阻塞:


提交會話一,會話二馬上得到執行,此時的值也沒有跳躍增長,累加1為5:


場景3:取序列的值去判斷在阻塞狀態時,序列的值是不是已經做了累加,結果是在阻塞的時候,序列中值並沒有累加,問題變得更加有趣了,也就意味著在會話一提交釋放的時候,序列的值直接加了2,應該說是進行了兩次累加。

修改觸發器為開始的狀態:

create or replace trigger tr_test before UPDATE or insert on test for each row
begin
    :new.sno := ss_test.nextval;
end;

test表依舊插入三條記錄:


更新test,id為1的記錄name設定為‘GG’,不提交:

test2的值更新到了4:


更新test設定id為1的記錄name為‘XX’,不提交:


新建會話,同樣更新test中id為1的記錄的name值,此時被阻塞:


回到會話一,取序列ss_test.nextval(不取currval,是因為currval只是當前會話或者說是當前事務中,上一個取nextval返回的值,也就是說還是第一次更新時產生的值:4),此時,取得ss_test.nextval是5,也就是當前值說是接著會話一之間產生的4來的,會話二在阻塞狀態下並沒有讓序列的值進行累加


提交會話一,會話二馬上得到執行:amazing,sno的值直接成了7,也就是在執行時,序列有兩次累加


以上就是故障案例和3個排錯案例。