在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  AAAarDAAKAADEmFAAA2        200 AAAarDAAKAADEmFAAB3        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中包含变化数据对应的RowId
dep.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异常。

ORACLE HANDBOOK系列之十四:变化通知(Change Notification)相关推荐

  1. ORACLE HANDBOOK系列之十:字符集、编码以及Oracle的那些事

    第一部分字符集与编码常识 字符集: 人们根据需要把某些字符收集到一处,并赋以名称,于是便有了某某字符集. 编码: 当前面收集的工作完成以后,为了让只认识数字的"愚蠢"的计算机也能够 ...

  2. IT职场人生系列之十四:经验积累

    本文是IT职场人生系列的第十四篇. 任何时候都会发现IT业是个变化迅速的行业,几年前还很时髦的技术,现在已经过时了:几年前还很热门的行业,现在也过时了.这种变化之莫测,别说我们普通人,连IT巨头们都经 ...

  3. ASP.NET企业开发框架IsLine FrameWork系列之十四--框架配置信息大全(中)

    ASP.NET企业开发框架IsLine FrameWork系列之十四--框架配置信息大全(中) 接上文 上文中讲到配置日志模块的第二步,这篇文章继续给大家介绍日志配置方法. Step 3.在</ ...

  4. springboot mybatis ehcache_SpringBoot入门建站全系列(十四)集成Redis缓存

    SpringBoot入门建站全系列(十四)集成Redis缓存 一.概述 本地缓存,就是使用应用内使用本地内存将数据暂缓存储,一般数据库的查询如果不怎么改动,可以用本地缓存暂存. 远程缓存,比如redi ...

  5. 【SQL开发实战技巧】系列(十四):计算消费后的余额计算银行流水累计和计算各部门工资排名前三位的员工

    系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...

  6. 算法系列之十四:狼、羊、菜和农夫过河问题

    算法系列之十四:狼.羊.菜和农夫过河问题 题目描述:农夫需要把狼.羊.菜和自己运到河对岸去,只有农夫能够划船,而且船比较小,除农夫之外每次只能运一种东西,还有一个棘手问题,就是如果没有农夫看着,羊会偷 ...

  7. C++编程入门系列之十四(类与对象:构造函数和析构函数)

    C++编程入门系列之十四(类与对象:构造函数和析构函数) 鸡啄米上一节中给大家讲解了类的声明.成员的访问控制和对象,今天鸡啄米给大家讲C++编程入门时同样必须掌握的构造函数和析构函数.从上一讲开始已经 ...

  8. SVM 支持向量机算法(Support Vector Machine )【Python机器学习系列(十四)】

    SVM 支持向量机算法(Support Vector Machine )[Python机器学习系列(十四)] 文章目录 1.SVM简介 2. SVM 逻辑推导 2.1 Part1 化简限制条件 2.2 ...

  9. 数据与广告系列二十四:效果广告后定向时代如何逆流而上

    作者·黄崇远 『数据虫巢』 全文共4338字 题图ssyer.com " 在效果广告的发展历程中,当前已经处于后定向时代,或者说是弱定向时代,我们是应该顺应潮流还是应该逆流而上?" ...

最新文章

  1. 异步编程模型--使用 IAsyncResult 对象
  2. 查看服务器CPU的个数、CPU的核数、多核超线程数
  3. 如何使用eclipse创建Javaweb项目
  4. blob的真实地址怎么获得_使用Python抓取m3u8加密视频 续:获得index.m3u8 地址
  5. 读书到底为了什么,读研到底值不值?
  6. phoenix创建索引报错“ Mutable secondary indexes must have the hbase.regionserver.wal.codec property”
  7. linux之trap命令
  8. 信息学奥赛一本通(1186:出现次数超过一半的数)
  9. 华为如何造车?动机、底气、战略布局、客户
  10. 多页Excel转换成PDF时如何保存为单独文件
  11. arc和非arc完美支持
  12. 趋势科技防毒墙网络版的卸载方法
  13. c语言入门-程序运行的过程
  14. 【数字信号处理】离散时间信号 ( 离散时间信号 与 连续时间信号 关系 | 序列表示法 | 列表法 | 函数表示法 | 图示法 )
  15. 重磅!2021年国内Java培训机构排名十强出炉啦!
  16. 修身齐家治国平天下 读 战国策 和 编程卓越之道 有感
  17. 三极管PNP NPN 的判别
  18. 文心一言 vs GPT4
  19. Educational Codeforces Round 131 (Rated for Div. 2) A-D题解
  20. 什么是死锁和如何解决死锁

热门文章

  1. 本硕非科班,单模型获得亚军!
  2. 竞赛中如何做特征工程
  3. 剑指offer_第2题_替换空格
  4. AI杀入斗地主领域,快手开发DouZero对标AlphaZero,干掉344个AI获第一
  5. 矩阵乘法的优化及其在卷积中的应用
  6. 延迟开学会影响毕业?这些高校有答案了!
  7. 【每日一算法】行星碰撞
  8. 树莓派发行版apt-get彻底卸载软件包
  9. ptrace原理与性能对比
  10. 刷墙(左蓝右红或同一色)