ORACLE HANDBOOK系列之十四:變化通知(Change Notification)

來源:互聯網
上載者:User

在App開發的過程中,有些資料訪問頻率很高但是資料變化不大,我們一般會讓它駐留記憶體以提高訪問效能,但是此種機制存在一個問題,那就是如何監測資料的變化,Oracle 10g中引入的 Change Notification的引入能很好的解決這個問題。簡單來說,Change Notification即Oracle可以在你指定的表資料發生變化時,給出一個通知。我們結合ODP.NET作一個樣本。首先建立一張樣本表tab_cn,並插入資料,我們希望在資料發生變化時,App能夠收到通知。

create table tab_cn(id number, val number);insert into tab_cn values(1,100);insert into tab_cn values(2,200);insert into tab_cn values(3,300);commit;SQL> select t.*, rowid from morven.tab_cn t;        ID        VAL ROWID---------- ---------- ------------------         1       100  AAAarDAAKAADEmFAAA         2        200 AAAarDAAKAADEmFAAB         3        300 AAAarDAAKAADEmFAAC

除此之外,還要賦予資料庫使用者(本例中是morven)change notification許可權:

grant change notification to morven;

下面則是相應的C#代碼(為簡單代碼,異常處理之類的就不貼出來了):

OracleDependency dep;OracleConnection conn;//public MainWindow(){    InitializeComponent();    //設定App的監聽連接埠,即使用哪個連接埠接收Change Notification。    OracleDependency.Port = 49500;    string cs = "User Id=morven;Password=tr;Data Source=mh";    conn = new OracleConnection(cs);    conn.Open();}//private void btReg_Click(object sender, RoutedEventArgs e){OracleCommand cmd = new OracleCommand("select * from tab_cn", conn);//綁定OracleDependency執行個體與OracleCommand執行個體dep = new OracleDependency(cmd);//指定Notification是object-based還是query-based,前者表示表(本例中為tab_cn)中任意資料變化時都會發出Notification;後者提供更細粒度的Notification,例如可以在前面的sql語句中加上where子句,從而指定Notification只針對查詢結果裡的資料,而不是全表。dep.QueryBasedNotification = false;//是否在Notification中包含變化資料對應的RowIddep.RowidInfo = OracleRowidInfo.Include;//指定收到Notification後的事件處理方法    dep.OnChange += new OnChangeEventHandler(OnNotificaton);    //是否在一次Notification後立即移除此次註冊cmd.Notification.IsNotifiedOnce = false;//此次註冊的逾時時間(秒),超過此時間,註冊將被自動移除。0表示不逾時。cmd.Notification.Timeout = 0;//False表示Notification將被存於記憶體中,True表示存於資料庫中,選擇True可以保證即便資料庫重啟之後,訊息仍然不會丟失    cmd.Notification.IsPersistent = true;    //    OracleDataReader odr = cmd.ExecuteReader();    //    this.rtb1.AppendText("Registration completed. " + DateTime.Now.ToLongTimeString() + Environment.NewLine);} private void btUnreg_Click(object sender, RoutedEventArgs e){    //登出    dep.RemoveRegistration(conn);    this.rtb1.AppendText("Registration Removed. " + DateTime.Now.ToLongTimeString() + Environment.NewLine);} private void OnNotificaton(object src, OracleNotificationEventArgs arg){    //可以從arg.Details中獲得通知的具體資訊,比如變化資料的RowId    DataTable dt = arg.Details;    //......    this.rtb1.Dispatcher.BeginInvoke(        DispatcherPriority.Normal,        new Action(() =>        {            this.rtb1.AppendText("Notification Received. " + DateTime.Now.ToLongTimeString()+"  Changed data(rowid): "+arg.Details.Rows[0]["rowid"].ToString() + Environment.NewLine);        }));}

點擊此App的Register按鈕,然後在資料庫側通過下面語句更新tab_cn表:

Update tab_cn set val=1000 where id=1;Commit;

此時App收到Notification,並能具體得到變化資料行所對應的RowId。隨後我們登出此次註冊。輸出參見:

Change Notification與Oracle Connection的關係

在實際測試中,無論我們是Connection.Close()還是在資料庫中手工Kill相應的Session或者是在OS層Kill相應的進程(線程),Notification仍然正常工作。

也就是說,除了初始化時,以及RemoveRegistration時依賴於相應的Connection,其它時候,它們並沒有依賴關係。

重複註冊

如果代碼有漏洞,就可能造成重複註冊的問題,此時在dba_change_notification_regs視圖中就能看到多條重複記錄(regid不同),曾經遇到過出現100000+記錄的情況。

上面的App中,如果我多次點擊Register按鈕,就會導致重複註冊,重複註冊的後果之一是,資料的一次改變,App會收到多條相同的通知。

重複註冊的另一個後果嚴重得多,會導致相應的表(本例中是tab_cn)更新之後的commit出現延時。當重複註冊10000時, update tab_cn表的一記錄後, commit花費一分鐘左右時間。同時也會影響資料庫shutdown或者startup的速度,因為這兩個動作都會發出notification(通知的內容為空白)。

個人覺得Oracle應該從內部杜絕這種情況,因為重複註冊的意義何在實在有待商榷。下面我稍微修改代碼,嘗試避免重複註冊的問題。

if (dep == null || !dep.IsEnabled){    OracleCommand cmd = new OracleCommand("select * from tab_cn", conn);    dep = new OracleDependency(cmd);    dep.QueryBasedNotification = false;    dep.RowidInfo = OracleRowidInfo.Include;    dep.OnChange += new OnChangeEventHandler(OnNotificaton);    //    cmd.Notification.IsNotifiedOnce = false;    cmd.Notification.Timeout = 0;    cmd.Notification.IsPersistent = true;    //    OracleDataReader odr = cmd.ExecuteReader();    this.rtb1.AppendText("Registration completed. " + DateTime.Now.ToLongTimeString() + Environment.NewLine);}

我在這裡添加了一個判斷。首先是判斷OracleDependency執行個體是否為空白(即第一次點擊Register按鈕),其次判斷OracleDependency.IsEnabled,此屬性在以下幾種情況時為False,1)已經初始化但command尚未執行、2)註冊時設定的Timeout到期、3)或者被RemoveRegistration登出了,注意RemoveRegistration並不會導致OracleDependency執行個體Dispose。修改後的代碼只有在使用者第一次點擊Register或者之前點擊過Unregister的情況下,才允許註冊。

清除dba_change_notification_regs記錄

上面我們用了OracleDependency.RemoveRegistration方法來登出某一個註冊,但是如果App還沒來得及登出就崩潰退出,這種情況下沒有手工清除dba_change_notification_regs記錄的方法,不過正常情況下,當你更新相應的資料表(本例中的tab_cn)並commit後,Oracle會自動清除記錄,因為Oracle已經監測到這些註冊已經失效了,但是有時候並不會立即完全清除,遇到過有延時的,Oracle似乎是一批一批地清除。

多個App註冊同一連接埠

前面我們提到了,同一個App中,我們可以進行多次註冊,但對於不同的App,如果都向同一連接埠(本例中的49500)進行註冊,則會發生ORA-24912: Listener thread failed. Listen failed異常。

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.