天天看點

深入詳解Oracle data change notification

0、什麼是 Oracle  data change notification  ?

      當有多個應用程式或者程序操作同一個資料庫時,其中程序1對Oracle中的某個表Table1進行插入、删除、修改等操作,程序2想在第一個程序操作完成後進行相應的操作。有沒有什麼方法讓程序2擷取到程序1的操作?

      類似程序、多線程的同步機制,或者消息響應機制。在Oracle中也有類似的實作,該機制名稱即為:data change notification。

      Database Change Notification的作用就是:當資料庫中的資料發生變化的時候,自動發出一個通知。

1、支援Oracle版本

Oracle 10gR2 或者以上版本。

2、  data change notification支援的操作

 (1)   資料庫狀态變化 Database status changes : startup and shutdown

 (2)  資料庫對象變化 Database objects changes :

         1) DDL changes : alter or drop actions

         2) DML changes : insert, delete, update actions

 用Database Change Notification有三個步驟:

     1)注冊: 指定資料庫要監聽的查詢。資料庫可以監聽DML(Data Manipulation Language)事件,DDL(Data Definition Language)事件,和global 事件(例如關閉資料庫)。

  2)通知:一旦資料庫中的資料發生變化,資料庫将自動發出通知,我們要在我們的程式中定義事件處理操作。

  3)響應:在我們的程式中,一旦收到通知,我們一般情況下會自動更新data cache,當然我們可以通知使用者資料發生改變,由他來決定是否進行更新。

3、兩種使用方法

3.1 OTL data change notification源碼詳解

示例源碼參考資料:

【1】

http://otl.sourceforge.net/otl4_subscriber.htm

【2】

http://otl.sourceforge.net/otl4_ex585.htm

使用中在subs.subscribe()接口會出現Bug:

bug号及詳情——ORA-24912: Listener thread failed. Listen failed.

Google提供的解決方案:The client needs to be restarted。(但測試不湊效)

錯誤及詳見我的提問:

http://bbs.csdn.net/topics/391054125 http://stackoverflow.com/questions/30847188/when-use-change-notification-interface-the-ora-24912-listener-thread-failed-l

截止2015-6-16,根本原因還沒有找到,即該Demo未測試成功。

2015-6-23,發現錯誤原因:和代碼中通過putenv()設定有關,不使用putenv(),改成直接将檔案、庫包含到工程中,該Bug便不存在。

3.2 ocilib data change notification源碼詳解

http://orclib.sourceforge.net/doc/html/group___ocilib_c_api_subscriptions.html

//代碼解析(VS2010 C++實作,已經驗證過)

#include "stdafx.h"
#include "ocilib.h"
#pragma comment (lib, "ociliba.lib")
 
#ifdef _WINDOWS
#define sleep(x) Sleep(x*1000)
#endif
#define wait_for_events() sleep(5000)
 
void event_handler(OCI_Event *event);
void error_handler(OCI_Error *err);
 
int main(void)
{
OCI_Connection *con;
OCI_Subscription *sub;
OCI_Statement *st;
 
printf("=> Initializing OCILIB in event mode...\n\n");
 
//0.第二個參數為原有的oracle的DLL所在的路徑名稱。
if (!OCI_Initialize(error_handler, "oracle", OCI_ENV_EVENTS))
return EXIT_FAILURE;
 
printf("=> Connecting to usr@db...\n\n");
//1.連接配接 第一個參數格式:【IP:端口/服務名】,第二個參數:登入使用者名,第三個參數:密碼。
con = OCI_ConnectionCreate("100.200.10.50:1521/ts", "tss", "psdts**", OCI_SESSION_DEFAULT);
OCI_SetAutoCommit(con, TRUE);
 
printf("=> Creating statement...\n\n");
st = OCI_StatementCreate(con);
 
printf("=> Creating tables...\n\n");
//2.建立資料表
OCI_ExecuteStmt(st, "create table table1(code number)");
OCI_ExecuteStmt(st, "create table table2(str varchar2(10))");
 
printf("=> Registering subscription...\n\n");
 
//3.注冊通知事件 sub-00 為通知名稱.
sub = OCI_SubscriptionRegister(con, "sub-00", OCI_CNT_ALL, event_handler, 5468, 0);
printf("=> Adding queries to be notified...\n\n");
OCI_Prepare(st, "select * from table1");
OCI_SubscriptionAddStatement(sub, st);
OCI_Prepare(st, "select * from table2");
OCI_SubscriptionAddStatement(sub, st);
 
//等待響應注冊事件
wait_for_events(); //可以Sleep足夠時間一直等待,當有其他程序進行修改表的操作,該處就會有響應。
 
// 以下為測試用,可以不用執行。
// #if 0
// //4.執行對應資料庫alter操作
// printf("=> Executing some DDL operation...\n\n");
// OCI_ExecuteStmt(st, "alter table table1 add price number"); //alter事件
// //等待5s,等待列印輸出.
// wait_for_events();
// 
// 
// //5.執行資料庫的inser,update操作。
// printf("=> Executing some DML operation...\n\n");
// OCI_ExecuteStmt(st, "insert into table1 values(1, 10.5)");
// OCI_ExecuteStmt(st, "insert into table2 values('shoes')");
// OCI_ExecuteStmt(st, "update table1 set price = 13.5 where code = 1");
// OCI_ExecuteStmt(st, "delete from table2 ");
// wait_for_events();
// 
// //6.執行drop資料庫表操作。
// printf("=> Droping tables...\n\n");
// OCI_ExecuteStmt(st, "drop table table1");
// OCI_ExecuteStmt(st, "drop table table2");
// wait_for_events();
// 
// printf("=> Disconnecting from DB...\n\n");
// OCI_ConnectionFree(con);
// printf("=> Stopping the remote database...\n\n");
// OCI_DatabaseShutdown("db", "sys", "sys", 
// OCI_SESSION_SYSDBA,
// OCI_DB_SDM_FULL,
// OCI_DB_SDF_IMMEDIATE);
// #endif
 
printf("=> Unregistering subscription...\n\n");
OCI_SubscriptionUnregister(sub);
printf("=> Cleaning up OCILIB resources...\n\n");
OCI_Cleanup();
printf("=> Done...\n\n");
 
return EXIT_SUCCESS;
}
 
void error_handler(OCI_Error *err)
{
int err_type = OCI_ErrorGetType(err);
const char *err_msg = OCI_ErrorGetString(err);
printf("** %s - %s\n", err_type == OCI_ERR_WARNING ? "Warning" : "Error", err_msg);
}
void event_handler(OCI_Event *event)
{
unsigned int type = OCI_EventGetType(event);
unsigned int op = OCI_EventGetOperation(event);
OCI_Subscription *sub = OCI_EventGetSubscription(event);
printf("** Notification : %s\n\n", OCI_SubscriptionGetName(sub));
printf("...... Database : %s\n", OCI_EventGetDatabase(event));
switch (type)
{
case OCI_ENT_STARTUP:
printf("...... Event : Startup\n");
break;
case OCI_ENT_SHUTDOWN:
printf("...... Event : Shutdown\n");
break;
case OCI_ENT_SHUTDOWN_ANY:
printf("...... Event : Shutdown any\n");
break;
case OCI_ENT_DROP_DATABASE:
printf("...... Event : drop database\n");
break;
case OCI_ENT_DEREGISTER:
printf("...... Event : deregister\n");
break;
case OCI_ENT_OBJECT_CHANGED:
 
printf("...... Event : object changed\n");
printf("........... Object : %s\n", OCI_EventGetObject(event));
 
switch (op)
{
case OCI_ONT_INSERT:
printf("........... Action : insert\n");
break;
case OCI_ONT_UPDATE:
printf("........... Action : update\n");
break;
case OCI_ONT_DELETE:
printf("........... Action : delete\n");
break;
case OCI_ONT_ALTER:
printf("........... Action : alter\n");
break;
case OCI_ONT_DROP:
printf("........... Action : drop\n");
break;
}
 
if (op < OCI_ONT_ALTER)
printf("........... Rowid : %s\n", OCI_EventGetRowid(event));
 
break;
}
 
printf("\n");
}           

//data change notification效果截圖

如下圖的三個步驟:

深入詳解Oracle data change notification

注意:測試空表insert一條記錄的測試情況,需要點下SQLDeveloper右面的“送出”按鈕!

4、兩種思路對比及Bug分析反思

周一下午臨危受命,預期周一晚上完成data change notification的驗證。

但是最終測試發現有RA-24912: Listener thread failed. Listen failed的Bug。

Google及Stackoverflow了N多資料,都沒有解決方案。

一直持續到周二下午4點,在OTL嘗試了N多方法都沒有解決。

試驗思路包括:

1)授權本地使用者具有grant 權限,即能執行成功“grant change notifiation to 使用者名”,已經授權,但Bug依然存在。

2)Stackoverflow老外提供思路,可能和服務有關,重新開機Oracle資料庫,Bug依然存在。

3)從程式subscribe接口分析,但由于第三方接口,沒有在深層列印日志,沒有理清根本原因。

且subscirbe接口為void類型,無傳回值,隻能通過捕獲異常,得到錯誤。且錯誤資訊就隻有監聽失敗。

綜上,既然OTL這條路不通,為何不去嘗試下其他思路。

當在Google輸入"oracle data change notification C++" 關鍵詞,便找到了3.2的實作。

反思:

1、對于不熟悉的領域,不能“一棵樹上吊死”,當嘗試N久一條路不通, 且業界大牛也沒有好的方案的時候,可以考慮換換思路,說不定會柳暗花明。

2、當然,對于第一條路的Bug為什麼存在,作為程式員還是要抽業餘時間追根究底,最終解決掉。

作者:銘毅天下

轉載請标明出處,原文位址:

http://blog.csdn.net/laoyang360/article/details/46524519