在應用環境中,常常需要保證幾張表相關數據的一致性,為了應對這種需求,開發工程師常常會使用事務,把一些insert,update等語句綁在一起,形成一個事務(Transaction),比如如下的偽代碼示例(事務1):
begin transaction
insert into messgae(id,col1,col2...) values(#id,#col1,#col2...);
update thread set post_count=nvl(post_count,0) + 1 where id=#v_id;
update test_user set sum_reply = sum_reply + 1 where userid=#v_userid;
end transaction
開發工程師為了實現應用數據完整性的需要,把這幾條SQL綁在一起形成一個大事務,並沒有什麼問題。在OLTP業務發展的早期階段,這樣設計並不會遇到任何問題,並可以最大程度的保證應用層數據的完整性。但隨著OLTP業務量的發展,並發訪問量的快速增長,這個大事務開始出現問題,阻塞現象異常嚴重,導致應用服務器頁面響應時間明顯加長,並已影響到業務的正常運行!經過調查發現,在系統中還存在這樣一個更新SQL(事務2):
update thread set view_count=nvl(view_count,0) + 1 where id=#v_id;
而這個因外界訪問量的增大,執行次數異常的高。很明顯,當事務1執行時,有多個語句要執行,執行時間較長,如果此時有大量的事務2相要執行,我所指的是更新相同的id的記錄。那麼即會發生出現大量的enqueue等待。這是采用當前事務策略下,在OLTP環境下,我們不得不面對的問題.
在剛開始出現大量鎖enqueue等待時,針對事務1,事務2執行頻率的不同:事務1低,事務2高。可以采用一定的策略來減少事務2的數據庫執行次數,比如先將此更新放到緩存裡,每隔一定的時間間隔更新到數據庫裡,減少事務1與事務2的碰撞機會。
隨著OLTP的進一步發展,並發訪問量的進一步提高,因事務1本身的事務較大,事務1發生的頻率也會越來越高,那時,事務1會與事務1本身產生阻塞,那時我們怎麼解決?看來解除事務是唯一的方法,這裡有另外一種觀點,完全解除事務,即把事務1中的每一個語句都拆開,示例如下:
begin transaction
insert into messgae(id,col1,col2...) values(#id,#col1,#col2...);
end transaction
begin transaction
update thread set post_count=nvl(post_count,0) + 1 where id=#v_id;
end transaction
begin transaction
update test_user set sum_reply = sum_reply + 1 where userid=#v_userid;
end transaction
這種方案可以看成事務1的極端,事務1的完全對立面,數據的一致性完全沒有保障。如果數據庫出現down機,或者應用服務器down掉,那麼數據很有可能出現不一致的。為了減少發生數據不一致的概率,可以采用折衷的方案在一定程度上解除事務,事務是一個好東西,就看你怎麼用了!對以上事務1的業務進行分析,發現了如下的規律:
insert into messgae(id,col1,col2...) values(#id,#col1,#col2...);這條SQL可以看成"私有"的,不會產生阻塞(這個表上不能有位圖索引)
update thread set post_count=nvl(post_count,0) + 1 where id=#v_id;這條SQL是"公共"的,很容易發生多個session更新同一條記錄,容易產生阻塞
update test_user set sum_reply = sum_reply + 1 where userid=#v_userid;這條SQL更新自己的東西,也可以看成"私有"的,不會產生阻塞
對以上作分析後,產生了另外一種方案:把"公共"的綁成一個事務,把"私有"的綁成一個事務,原來的事務1變成2個事務,示例如下:
begin transaction1_1
update thread set post_count=nvl(post_count,0) + 1 where id=#v_id;
end transaction1_1
begin transaction1_2
insert into messgae(id,col1,col2...) values(#id,#col1,#col2...);
update test_user set sum_reply = sum_reply + 1 where userid=#v_userid;
end transaction1_2