【问题标题】:OracleCommand with OracleDependency waiting forever带有 OracleDependency 的 OracleCommand 永远等待
【发布时间】:2014-09-02 17:56:14
【问题描述】:

注意:关于here的相关问题无解

请记住,我不是 Oracle 或针对 Oracle 编程的专家。这是我的测试环境。我在架构 STVM 中有一个名为 STVM_NOTIFICATION 的表。这是它的样子:

CREATE TABLE STVM_NOTIFICATION
(   
    "ID"              NUMBER              NOT NULL, 
    "PROPERTYNAME"    VARCHAR2(16 BYTE)   NOT NULL, 
    "PROPERTYVALUE"   VARCHAR2(16 BYTE)   NOT NULL, 
    "ACTION"          VARCHAR2(32 BYTE)   NOT NULL, 
    "POSTDATE"        TIMESTAMP (6)       NOT NULL, 
    "SENT"            CHAR(1 BYTE)        NOT NULL,
    ADD CONSTRAINT "PK_ID" PRIMARY KEY ("ID")
)

我创建了以下序列和触发器来为每一行创建唯一标识:

CREATE SEQUENCE STVM_NOTIF_SEQ
  START WITH 1
  INCREMENT BY 1
  CACHE 100;

CREATE OR REPLACE TRIGGER STVM_NOTIF_ID_TRG BEFORE INSERT ON STVM_NOTIFICATION
  FOR EACH ROW
    BEGIN
      :NEW.ID := STVM_NOTIF_SEQ.NEXTVAL;
    END;

然后我为 STVM 设置以下授权:

GRANT CREATE SESSION TO STVM;
GRANT CREATE TABLE TO STVM;
GRANT CREATE VIEW TO STVM;
GRANT CREATE ANY TRIGGER TO STVM;
GRANT CREATE ANY PROCEDURE TO STVM;
GRANT CREATE SEQUENCE TO STVM;
GRANT CREATE SYNONYM TO STVM;
GRANT CHANGE NOTIFICATION TO STVM;

插入表格就可以了。下面是我用来测试通知的 Oracle 文档中关于 OracleDependency 的简单测试应用程序(稍作修改):

namespace SqlDependencyTest
{
    class Program
    {
        private static string oraConnectionString = @"Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.0.164)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)));User Id=STVM;Password=STVM;";
        private static string oraQuery = "SELECT ID FROM STVM_NOTIFICATION";
        private static OracleDependency oraDependency;

        static void Main(string[] args)
        {
            using (OracleConnection oraConnection = new OracleConnection(oraConnectionString))
            {
                try
                {
                    // Open the connection
                    oraConnection.Open();

                    // Create the Select command retrieving all data from the STVM_NOTIFICATION table. 
                    OracleCommand selectCommand = new OracleCommand(oraQuery, oraConnection);
                    // Create an OracleDependency object and set it to track the result set returned by selectCommand. 
                    oraDependency = new OracleDependency(selectCommand);

                    // Setting object-based change notification registration 
                    oraDependency.QueryBasedNotification = false;

                    // When the IsNotifiedOnce property is true, only the first change  
                    // of the traced result set will generate a notification. 
                    // Otherwise, notifications will be sent on each change  
                    // during the selectCommand.Notification.Timeout period. 
                    selectCommand.Notification.IsNotifiedOnce = true;

                    // Set the event handler to the OnChange event. 
                    oraDependency.OnChange += new OnChangeEventHandler(OnChange);

                    // When the select command is executed at the first time, a notification  
                    // on changes of the corresponding result set is registered on the server.
                    //selectCommand.CommandTimeout = 5;
                    OracleDataReader reader = selectCommand.ExecuteReader(CommandBehavior.Default);

                    // Set and execute an insert command. The Dept table data will be changed,  
                    // and a notification will be sent, causing the OnChange event of the 'dependency' object. 
                    OracleCommand insertCommand = new OracleCommand
                        ("INSERT INTO STVM_NOTIFICATION (PROPERTYNAME, PROPERTYVALUE, ACTION, POSTDATE, SENT) VALUES ('Heartbeat', 'NOK', 'REFRESH', SYSDATE, 'N')", oraConnection);
                    insertCommand.ExecuteNonQuery();

                    // Pause the current thread to process the event. 
                    Console.Read();
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception encountered: {0}", e.Message);
                }
                // Always try to both remove the notification registration
                // oraConnection.Close() is autimatically called by .Dispose at the end of our 'using' statement
                finally
                {
                    try
                    {
                        oraDependency.RemoveRegistration(oraConnection);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Exception encountered: {0}", e.Message);
                    }
                }
            }
        }

        // A simple event handler to handle the OnChange event. 
        // Prints the change notification details. 
        private static void OnChange(Object sender, OracleNotificationEventArgs args)
        {
            DataTable dt = args.Details;

            Console.WriteLine("The following database objects were changed:");
            foreach (string resource in args.ResourceNames)
            {
                Console.WriteLine(resource);
            }

            Console.WriteLine("\n Details:");
            Console.Write(new string('*', 80));
            for (int rows = 0; rows < dt.Rows.Count; rows++)
            {
                Console.WriteLine("Resource name: " + dt.Rows[rows].ItemArray[0]);
                string type = Enum.GetName(typeof(OracleNotificationInfo), dt.Rows[rows].ItemArray[1]);
                Console.WriteLine("Change type: " + type);
                Console.Write(new string('*', 80));
            }
        } 
    }
}

这确实有效!但是:至少直到另一个进程在同一个表上执行插入,这是我的观察。我从 SQL Developer 执行了多次插入,问题是可重新创建的,我已经这样做了 10 多次。

一旦另一个进程执行了上述插入,我的应用程序就会挂起以下语句: OracleDataReader reader = selectCommand.ExecuteReader(CommandBehavior.Default);

我可以清楚地看到通知回调正在 DBA_CHANGE_NOTIFICATION_REGS 中注册: net8://(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.0.226)(PORT=64268))?PR=0

连接在 Oracle 服务器上保持打开状态 30 分钟,直到超时:

SELECT USERNAME, PROGRAM, BLOCKING_SESSION_STATUS, BLOCKING_INSTANCE, BLOCKING_SESSION, EVENT FROM V$SESSION
WHERE USERNAME = 'STVM'

USERNAME    PROGRAM                         BLOCKING_SESSION_STATUS BLOCKING_INSTANCE   BLOCKING_SESSION    EVENT
STVM        SQL Developer                   NO HOLDER               (null)              (null)              SQL*Net message from client
STVM        OracleDependencyTest.vshost.exe VALID                   1                   50                  enq: TM - contention

无论何时发生这种情况,唯一的解决方案是终止会话,撤销更改通知并再次授予它。在此之后,我的应用程序将再次工作,直到另一个进程执行插入。

我唯一的线索是事件 enq: TM - contention,但大多数人将此称为表中非索引外键的指标。考虑到我目前只有一个表用于测试目的,所以这里没有很多外键可供使用。

有人对 enq: TM - contention 的相关性有任何想法吗?

我正在使用:

(服务器端)

Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
"CORE   11.2.0.2.0  Production"
TNS for Linux: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production
Oracle.DataAccess

(客户端)

Oracle Call Interface (OCI)                                          11.2.0.1.0
Oracle Client                                                        11.2.0.1.0
Oracle Data Provider for .NET                                        11.2.0.1.0
Oracle JDBC/OCI Instant Client                                       11.2.0.1.0
Oracle JDBC/THIN Interfaces                                          11.2.0.1.0
Oracle SQL Developer                                                 11.2.0.1.0
SQL*Plus                                                             11.2.0.1.0
SQL*Plus Files for Instant Client                                    11.2.0.1.0

更新:经过几天的尝试找出问题所在但没有​​找到问题,我决定继续前进并寻找不同的技术。克里斯蒂安的伟大建议没有产生任何结果,不幸的是,像贾斯汀建议的那样解决可疑交易也没有让我更进一步。我知道有几个 Oracle DBA 在工作,他们最初愿意提供帮助,但我一提到 .NET,他们很快就把我吓跑了。

【问题讨论】:

  • 执行insert 的进程是否正在提交更改?
  • @JustinCave 我刚刚检查过,V$TRANSACTION 中确实还有一条活动记录。如果我删除它,我会看看会发生什么。
  • 我极力希望您不是要修改v$transaction...我希望您的意思是您要在执行的会话中输入commit insert 声明。
  • @JustinCave 这确实是我的计划,但现在我注意到会话处于非活动状态,但事务处于活动状态。事务源自我坐在前面的系统,但我没有运行与数据库的开放连接的进程。我不确定如何继续,无法提交交易...
  • 一个会话是INACTIVE,当它在那个确切时刻没有运行 SQL 语句时——会话在绝大多数时间都是不活动的。如果v$session 中仍有会话,则强烈暗示您与数据库的连接处于打开状态。我想您可能已经杀死了留下孤立会话的客户端进程,但如果您对客户端应用程序进行任何形式的有序关闭,则不会发生这种情况。

标签: c# .net oracle odp.net


【解决方案1】:

您使用的数据库版本中存在可能相关的错误。您可以通过以 SYSDBA 身份执行以下命令来检查是否是这种情况: alter system set events '10867 trace name context forever, level 1';

这将持续到关机。如果问题消失了,您就是遇到了错误。

您不应该打开它,而是应该升级数据库并修补 ODP.NET。

【讨论】:

  • 我会尽快检查。感谢您的建议。
  • 我们能否在单个 asp.net 应用程序中拥有来自 2 个不同数据库的 2 个不同 Oracle 依赖项。
猜你喜欢
  • 2010-12-11
  • 2013-11-24
  • 2012-02-24
  • 1970-01-01
  • 2019-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-08
相关资源
最近更新 更多