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效果截圖
如下圖的三個步驟:

注意:測試空表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