ODP.NET Oracle.ManagedDataAcess 随机 ORA-12570 错误

本文关键字:ORA-12570 错误 随机 ManagedDataAcess NET Oracle ODP | 更新日期: 2023-09-27 18:34:20

我正在尝试从非托管版本迁移到Oracle.ManagedDataAcess并接收随机ORA-12570 TNS:数据包读取器故障。

我不知道为什么这个错误会开始,但是一旦它开始,每个后续请求都会在大约 10-30 分钟内给出相同的错误,然后它会再次工作 10-30 分钟,依此类推。

所以它是一段时间内后续失败的随机结果,然后是随后的成功

已经尝试了很多事情,恢复:

环境:

  • Oracle.ManagedDataAcess 版本 12.1.2400 (4.121.2.20150926( (nuget( (服务器上未安装可覆盖 bin 版本的 gac 引用(
  • Oracle
  • 服务器 Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64 位生产
  • Windows
  • 2012 (Windows Update ok(

检查:

    防火墙
  • :这不是防火墙问题
  • 计算机
  • 错误:我的计算机、Azure WebApp 和 AWS EC2 实例上也发生了相同的问题
  • 干扰:没有嗅探器运行,透明代理等。
  • 加密
  • :我不使用任何类型的加密(除非默认情况下启用了我不知道的内容(
  • 连接字符串
  • :同一连接字符串与非托管版本完美配合

其他信息:

  • 这是一个生产数据库,非常稳定
  • 应用程序编译为 anycpu,IIS 应用程序池限制为 64 位
  • 我每次都测试完全相同的请求(只是刷新休息 ws、webapi 的 get url(,因此它与数据格式无关

配置:

Server sqlnet.ora

SQLNET.AUTHENTICATION_SERVICES= (新台币(名字。DIRECTORY_PATH= (TNSNAMES, EZCONNECT(

应用程序网络配置(应用程序网络(

<connectionStrings>
<add name="XXXX" connectionString="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=xxx.xxx.com)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=xxx)));User Id=xxxxx;Password=xxxxx;" />
</connectionStrings>
<configSections>
    <section name="oracle.manageddataaccess.client" type="OracleInternal.Common.ODPMSectionHandler, Oracle.ManagedDataAccess, Version=4.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
</configSections>
<oracle.manageddataaccess.client>
    <version number="*">
      <dataSources>
        <!--<dataSource alias="SampleDataSource" descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL))) " />-->
      </dataSources>
      <settings>
        <setting name="SQLNET.AUTHENTICATION_SERVICES" value="NONE"/> <!--NTS-->
        <setting name="sqlnet.crypto_checksum_server" value="rejected"/>
        <setting name="sqlnet.crypto_checksum_client" value="rejected"/>
        <setting name="SQLNET.ENCRYPTION_SERVER" value="rejected"/>
      </settings>
    </version>
</oracle.manageddataaccess.client>

一些参考资料:

https://community.oracle.com/thread/3634263?start=0&tstart=0

ODP.net 托管驱动程序引发 ORA-12570:网络会话:意外的数据包读取错误

具有 Oracle 高级安全选项的托管 Oracle 客户端

IIS 中的 ODP.NET 错误: ORA-12357 网络会话文件结束

更新 1

池化更改后(正如我在这里描述的答案(,我决定发布一个版本来做一些真正的测试。1 天后,用户抱怨性能,我收到另一个错误:值不能为空。参数名称:字节数组

我将引用更改回非托管版本,一切又很好,更快,没有字节数组错误,更好的池管理。

所以我现在只是放弃托管版本,也许我会在 Oracle 下一个版本上再试一次。

如您所见,这里有关此新错误的一些参考资料看起来像另一个错误(仍然没有任何答案(。

https://community.oracle.com/thread/3676588?start=0&tstart=0

EF + ODP.NET + CLOB = 值 不能为空 - 参数名称:字节数组?

到目前为止,不使用的原因:

  • 池化管理错误
  • CLOB 空/非空字节数组错误
  • 性能下降可能与池化错误有关

ODP.NET Oracle.ManagedDataAcess 随机 ORA-12570 错误

根据我遇到类似错误 12570(读取器与写入器(的经验,此错误只有一个原因 - 网络上的某些内容正在终止空闲的 TCP 连接。 通常,这是防火墙/托管交换机。 你说你已经排除了防火墙,但我不确定如何。 数据库本身可能正在终止连接(dba 脚本(,但我记得这是一个不同的错误。

Ora-12571 可能略有不同。 但是,既然您已经确定问题相同,因为它是长期建立的池连接,我会继续前进。

您可以执行以下几项操作:

  1. 在连接字符串中设置最小池大小 = 0。 这通常会为我解决问题。 它允许在应用空闲时关闭整个池。 但是,如果您的流量剧烈波动,则可能需要增加decr池大小,以便更快地关闭由疯狂匆忙创建的连接。
  2. 在sqlnet.ora中设置Expire_Time。 从名称上看不出来,此设置会发送探测数据包,这会导致满足任何 tcp 空闲监视。 这里唯一的问题是我不完全确定如何使用托管提供程序设置 sqlnet 设置。 我猜sqlnet.ora可以进入exe dir,但我也看到一些迹象表明它可以以.config的形式设置(请参阅此处类似的wallet_override示例(因为您仅在托管提供程序中获取此信息,因此我想知道您的非托管客户端sqlnet.ora是否已经具有此设置。

多年来,我已经多次看到这种情况,第一次发生这种情况时,我创建了一个实用程序,该实用程序基本上通过创建不同持续时间的连接来进行二进制搜索以确定确切的超时时间。 如果它始终处于相同的终止时间,您可以猜到某处的某个设置导致了这种情况。 如果它不稳定,那么您可能会有某种基础设施的不稳定。

不幸的是,我将其创建为 c# 窗体应用程序,因此我粘贴了下面的窗体代码和设计器代码:

表格1.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Oracle.ManagedDataAccess.Client;
namespace TimeoutTest
{
  public partial class Form1 : Form
  {
    List<TestConnection> connections;
    Int32 connectionCount;
    Int32 multiplier;
    Int32 initialConnectionWait;
    TestConnection controlConnection = null;
    public Form1()
    {
      InitializeComponent();

    }
    private void BtStart_Click(object sender, EventArgs e)
    {
      connectionCount = Int32.Parse(InConnections.Text);
      multiplier = Int32.Parse(InMultiplier.Text);
      initialConnectionWait = Int32.Parse(InInitialWait.Text);
      DisplayMessage("Starting control connection'r'n");
      controlConnection = new TestConnection();
      controlConnection.ErrorOccured += new EventHandler(controlConnection_ErrorOccured);
      controlConnection.IsControlConnection = true;
      controlConnection.StartTest(2);
      connections = new List<TestConnection>();
      DisplayMessage("Spinning up {0} connections...'r'n", connectionCount);
      for (int i = 0, idleTime=initialConnectionWait; i < connectionCount; i++, idleTime*=multiplier)
      {
        TestConnection connection = new TestConnection();
        connection.Notified += new TestConnection.NotificationEventHandler(connection_Notified);
        connection.ErrorOccured += new EventHandler(connection_ErrorOccured);
        connection.TestCompleted += new EventHandler(connection_TestCompleted);
        connection.StartTest(idleTime);
        connections.Add(connection);
      }
      DisplayMessage("");
    }

    void controlConnection_ErrorOccured(object sender, EventArgs e)
    {
      DisplayMessage("'r'nControl connection error, aborting!!!");
      BtCancel_Click(this, EventArgs.Empty);
    }
    void connection_TestCompleted(object sender, EventArgs e)
    {
      TestConnection currentConnection = (TestConnection)sender;
      
      if (currentConnection == connections.Last())
        DisplayMessage("'r'nAll tests complete.  Done");
      
    }
    void connection_ErrorOccured(object sender, EventArgs e)
    {
      //stop any active connection.
      foreach(TestConnection tc in connections)
      {
        tc.CompletionTimer.Enabled=false;
      }
      TestConnection currentConnection = (TestConnection)sender;
      Int32 upperTime = currentConnection.IdleTime;
      Int32 lowerTime = 0;
      
      Int32 index = connections.IndexOf(currentConnection);
      //if this is not the first connection...
      if(index > 0)
      {
        //...then set the lower time based on the previous connection
        lowerTime = connections[index-1].IdleTime;
      }
      //get the difference between the lower and upper as the new range to work on
      Int32 range = upperTime - lowerTime;
      
      //divide the range over the number of connections to get the new interval
      Int32 interval = range / this.connectionCount;
      connections.Clear();
      //if the interval is too small try to reduce the number of connections
      while (interval < 2 && this.connectionCount > 2)
      {
        this.connectionCount--;
        DisplayMessage("'r'nConnections too high for current resolution.  Reducing to {0} connections.", this.connectionCount);
        interval = range / this.connectionCount;
      }

      if(interval < 2)
      {
        DisplayMessage("'r'nResolution cannot be increased.  Done.");
      }
      else
      {
        DisplayMessage("'r'nRestarting test with min:{0}, max{1}, resolution{2}.", lowerTime, upperTime, interval);

        //create the new connections
        for (int i = connectionCount-1, idleTime = upperTime-interval; i >= 0; i--, idleTime-=interval)
        {
          TestConnection connection = new TestConnection();
          connection.Notified += new TestConnection.NotificationEventHandler(connection_Notified);
          connection.ErrorOccured += new EventHandler(connection_ErrorOccured);
          connection.TestCompleted += new EventHandler(connection_TestCompleted);
          connection.StartTest(idleTime);
          connections.Insert(0,connection);
        }
        this.connectionCount = connections.Count;
      }
    }
    private void BtCancel_Click(object sender, EventArgs e)
    {
      //stop any active connection.
      foreach (TestConnection tc in connections)
      {
        tc.CompletionTimer.Enabled = false;
        tc.Command.Connection.Close();
      }
      DisplayMessage("Stopped running tests.");
    }

    void connection_Notified(object o, Form1.TestConnection.NotificationEventArgs e)
    {
      DisplayMessage(e.Message);
    }
    private void DisplayMessage(String message)
    {
      DisplayMessage("{0}", message);
    }
    private void DisplayMessage(String message, params Object[] args)
    {
      OutStatus.AppendText(String.Format(message, args) + "'r'n");
    }
    
    public class TestConnection
    {
      public Boolean IsControlConnection { get; set; }
      public OracleCommand Command { get; private set; }
      public Timer CompletionTimer { get; private set; }
      public String ConnectionId { get; private set; }
      public Int32 IdleTime
      {
        get
        {
          return CompletionTimer.Interval / 1000;
        }
        set
        {
          CompletionTimer.Interval = value * 1000;
        }
      }
      #region Events and Delegates
      public event EventHandler ErrorOccured;
      public event EventHandler TestCompleted;
      public class NotificationEventArgs : EventArgs
      {
        public NotificationEventArgs(String message)
        {
          this.Message = message;
        }
        public String Message { get; set; }
      }
      public delegate void NotificationEventHandler(object o, NotificationEventArgs e);
      public event NotificationEventHandler Notified;
      private void Notify(String message)
      {
        if (Notified != null)
        {
          Notified(this, new NotificationEventArgs(message));
        }
      }
      public void Notify(String format, params object[] args)
      {
        this.Notify(String.Format(format, args));
      }

      #endregion
      public TestConnection()
      {
        CompletionTimer = new Timer();
        CompletionTimer.Tick += new EventHandler(CompleteTest);
        Command = new OracleCommand(
          "select 'saddr:' || saddr || '-sid:' || sid || '-serial#:' || serial# || '-audsid:' || audsid || '-paddr:' || paddr || '-module:' || module  from gv$session where audsid=Userenv('SESSIONID')");
        Command.Connection = new OracleConnection(Configuration.OracleConnectionString);
      }
      public String StartTest(Int32 idleTime)
      {
        Command.Connection.Open();
        ConnectionId = (String)Command.ExecuteScalar();
        Notify("Started test with idle time={0}, id={1}.", idleTime, ConnectionId);
        IdleTime = idleTime;
        CompletionTimer.Enabled = true;
        return ConnectionId;
      }
      private void CompleteTest(object sender, EventArgs e)
      {
        if (!IsControlConnection)
          CompletionTimer.Enabled = false;
        try
        {
          Command.ExecuteScalar();
          Notify("Test complete on connection with idle time={0}, id={1}.", IdleTime, ConnectionId);
          if (TestCompleted != null)
            TestCompleted(this, EventArgs.Empty);
        }
        catch (OracleException ex)
        {
          if (ex.Number == 12571)
          {
            if (ErrorOccured != null)
            {
              Notify("Found error on connection with idle time={0}, id={1}.", IdleTime, ConnectionId);
              ErrorOccured(this, EventArgs.Empty);
            }
          }
          else
          {
            Notify("Unknown error occured on connection with timeout {0}, Error: {1}, 'r'n{2}",(IdleTime).ToString(), ex, ConnectionId);
          }
        }
        catch (Exception ex)
        {
          Notify("Unknown error occured on connection with timeout {0}, Error: {1}, 'r'n{2}", (IdleTime).ToString(), ex, ConnectionId);
        }
        finally
        {
          if(!IsControlConnection)
            Command.Connection.Close();
        }
      }
    }
    private void InConnections_TextChanged(object sender, EventArgs e)
    {
      Int32.TryParse(InConnections.Text,out connectionCount);
      Int32.TryParse(InMultiplier.Text,out multiplier);
      Int32.TryParse(InInitialWait.Text, out initialConnectionWait);
      OutLongestConnection.Text = (Math.Pow(multiplier,connectionCount-1) * initialConnectionWait).ToString();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
      InConnections_TextChanged(this, EventArgs.Empty);
    }
 }
}

Form1.designer.cs:

namespace TimeoutTest
{
  partial class Form1
  {
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;
    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }
    #region Windows Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      this.BtStart = new System.Windows.Forms.Button();
      this.OutStatus = new System.Windows.Forms.TextBox();
      this.InConnections = new System.Windows.Forms.MaskedTextBox();
      this.label1 = new System.Windows.Forms.Label();
      this.label3 = new System.Windows.Forms.Label();
      this.InInitialWait = new System.Windows.Forms.MaskedTextBox();
      this.InMultiplier = new System.Windows.Forms.MaskedTextBox();
      this.label2 = new System.Windows.Forms.Label();
      this.BtCancel = new System.Windows.Forms.Button();
      this.label4 = new System.Windows.Forms.Label();
      this.OutLongestConnection = new System.Windows.Forms.Label();
      this.SuspendLayout();
      // 
      // BtStart
      // 
      this.BtStart.Location = new System.Drawing.Point(13, 394);
      this.BtStart.Name = "BtStart";
      this.BtStart.Size = new System.Drawing.Size(75, 23);
      this.BtStart.TabIndex = 0;
      this.BtStart.Text = "Start";
      this.BtStart.UseVisualStyleBackColor = true;
      this.BtStart.Click += new System.EventHandler(this.BtStart_Click);
      // 
      // OutStatus
      // 
      this.OutStatus.Location = new System.Drawing.Point(13, 13);
      this.OutStatus.Multiline = true;
      this.OutStatus.Name = "OutStatus";
      this.OutStatus.ReadOnly = true;
      this.OutStatus.ScrollBars = System.Windows.Forms.ScrollBars.Both;
      this.OutStatus.Size = new System.Drawing.Size(766, 375);
      this.OutStatus.TabIndex = 1;
      // 
      // InConnections
      // 
      this.InConnections.Location = new System.Drawing.Point(180, 397);
      this.InConnections.Mask = "00";
      this.InConnections.Name = "InConnections";
      this.InConnections.Size = new System.Drawing.Size(22, 20);
      this.InConnections.TabIndex = 2;
      this.InConnections.Text = "10";
      this.InConnections.TextChanged += new System.EventHandler(this.InConnections_TextChanged);
      // 
      // label1
      // 
      this.label1.AutoSize = true;
      this.label1.Location = new System.Drawing.Point(108, 400);
      this.label1.Name = "label1";
      this.label1.Size = new System.Drawing.Size(66, 13);
      this.label1.TabIndex = 3;
      this.label1.Text = "Connections";
      // 
      // label3
      // 
      this.label3.AutoSize = true;
      this.label3.Location = new System.Drawing.Point(289, 399);
      this.label3.Name = "label3";
      this.label3.Size = new System.Drawing.Size(113, 13);
      this.label3.TabIndex = 5;
      this.label3.Text = "Initial Connection Wait";
      // 
      // InInitialWait
      // 
      this.InInitialWait.Location = new System.Drawing.Point(408, 396);
      this.InInitialWait.Mask = "00";
      this.InInitialWait.Name = "InInitialWait";
      this.InInitialWait.Size = new System.Drawing.Size(21, 20);
      this.InInitialWait.TabIndex = 4;
      this.InInitialWait.Text = "60";
      this.InInitialWait.TextChanged += new System.EventHandler(this.InConnections_TextChanged);
      // 
      // InMultiplier
      // 
      this.InMultiplier.Location = new System.Drawing.Point(262, 396);
      this.InMultiplier.Mask = "0";
      this.InMultiplier.Name = "InMultiplier";
      this.InMultiplier.Size = new System.Drawing.Size(21, 20);
      this.InMultiplier.TabIndex = 2;
      this.InMultiplier.Text = "2";
      this.InMultiplier.TextChanged += new System.EventHandler(this.InConnections_TextChanged);
      // 
      // label2
      // 
      this.label2.AutoSize = true;
      this.label2.Location = new System.Drawing.Point(208, 400);
      this.label2.Name = "label2";
      this.label2.Size = new System.Drawing.Size(48, 13);
      this.label2.TabIndex = 3;
      this.label2.Text = "Multiplier";
      // 
      // BtCancel
      // 
      this.BtCancel.Location = new System.Drawing.Point(13, 436);
      this.BtCancel.Name = "BtCancel";
      this.BtCancel.Size = new System.Drawing.Size(75, 23);
      this.BtCancel.TabIndex = 6;
      this.BtCancel.Text = "Cancel";
      this.BtCancel.UseVisualStyleBackColor = true;
      this.BtCancel.Click += new System.EventHandler(this.BtCancel_Click);
      // 
      // label4
      // 
      this.label4.AutoSize = true;
      this.label4.Location = new System.Drawing.Point(451, 399);
      this.label4.Name = "label4";
      this.label4.Size = new System.Drawing.Size(102, 13);
      this.label4.TabIndex = 7;
      this.label4.Text = "Longest Connection";
      // 
      // OutLongestConnection
      // 
      this.OutLongestConnection.AutoSize = true;
      this.OutLongestConnection.Location = new System.Drawing.Point(559, 399);
      this.OutLongestConnection.Name = "OutLongestConnection";
      this.OutLongestConnection.Size = new System.Drawing.Size(102, 13);
      this.OutLongestConnection.TabIndex = 8;
      this.OutLongestConnection.Text = "Longest Connection";
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(791, 582);
      this.Controls.Add(this.OutLongestConnection);
      this.Controls.Add(this.label4);
      this.Controls.Add(this.BtCancel);
      this.Controls.Add(this.label3);
      this.Controls.Add(this.InInitialWait);
      this.Controls.Add(this.label2);
      this.Controls.Add(this.InMultiplier);
      this.Controls.Add(this.label1);
      this.Controls.Add(this.InConnections);
      this.Controls.Add(this.OutStatus);
      this.Controls.Add(this.BtStart);
      this.Name = "Form1";
      this.Text = "Form1";
      this.Load += new System.EventHandler(this.Form1_Load);
      this.ResumeLayout(false);
      this.PerformLayout();
    }
    #endregion
    private System.Windows.Forms.Button BtStart;
    private System.Windows.Forms.TextBox OutStatus;
    private System.Windows.Forms.MaskedTextBox InConnections;
    private System.Windows.Forms.Label label1;
    private System.Windows.Forms.Label label3;
    private System.Windows.Forms.MaskedTextBox InInitialWait;
    private System.Windows.Forms.MaskedTextBox InMultiplier;
    private System.Windows.Forms.Label label2;
    private System.Windows.Forms.Button BtCancel;
    private System.Windows.Forms.Label label4;
    private System.Windows.Forms.Label OutLongestConnection;
  }
}

按照@bdn02的建议禁用池化(池=假(后,我可以确认它有效。但是我认为它应该会影响性能,并且我担心在没有任何池化的情况下将此代码发布到生产中(我认为标准值还可以(。

所以我尝试了许多配置,看起来不知何故(不是很清楚(oracle的池管理引发了ORA-12570错误,一段时间后,会话关闭,应用程序再次工作。

为了找到启用池化的最佳配置,我创建了一个测试应用程序来启动 50 个线程(每个线程每 50 毫秒执行 1 个测试(,并降低默认池值,直到错误停止。通过这种方式,我能够获得最佳配置,稳定,没有任何错误。

显然,它不适用于每台服务器,但这是我的最终连接字符串配置:

Pooling=true;Min Pool Size=1;Connection Lifetime=180;Max Pool Size=50;Incr Pool Size=5

我给你一个用于检查数据库连接的Powershell脚本。 $baselogpath = "" $filesuffix = "_GetDBConnection" $dbuser ="" $dbpassword ="" $dbalias = ""; $command = new-Object Oracle.DataAccess.Client.OracleCommand($queryString, $connection); $connection.Open(); $count = $command.ExecuteScalar(); $connection.Close();
$message = "Records found: " + $count; $esito = "OK"; } Catch { $message = $_.Exception.Message; $esito = "KO"; } $now = Get-Date $filename = $baselogpath + $now.Year + $now.Month.Tostring("00") + $now.Day.Tostring("00") + $filesuffix + ".log" if (!(Test-Path($filename))) { $fileheader = "Time Esito, Elapsed, Message" $fileheader > $filename } $Time.Stop(); $Elapsed = $Time.Elapsed; $row = $now.Hour.toString("00") + ":" + $now.Minute.toString("00") + ":" + $now.Second.toString("00") + " " + $esito + "," + $Elapsed.Hours.toString("00") + ":" + $Elapsed.Minutes.toString("00") + ":" + $Elapsed.Seconds.toString("00") + "," + $message; $row >> $filename

是否可以尝试使用 Oracle 托管 dll 的托管版本每隔一分钟计划一次此脚本?我会理解问题是否仅在 Web 应用程序上,或者是否与 oracle 托管驱动程序相关。要进行高级测试,您可以安排使用此非托管版本的 oracle.dataaccess 的脚本副本。

祝你好运

我在使用 SQL Server Reporting Services 2016 和 ODAC 12c Release 4 时遇到了同样的间歇性错误:

Error:
An error has occurred during report processing. (rsProcessingAborted) 
Query execution failed for dataset 'TermPrompt'. (rsErrorExecutingCommand) 
ORA-12570: Network Session: Unexpected packet read error 
ORA-12570: Network Session: Unexpected packet read error 
ORA-12537: Network Session: End of file

将池参数Data Source="myOracleDB";Pooling="false"添加到 SSRS 中的 Oracle 数据源完全解决了该问题。

立即重新执行报告工作正常。

我意识到每次创建新连接而不是使用池都存在潜在的性能问题,但在 Oracle 解决此问题之前,我不希望我的用户遇到此错误。

KeepAlive 设置,可以防止丢失连接的池错误,例如 ORA-12571 和类似。(而不是禁用池化(

问题和解决方案:https://github.com/oracle/dotnet-db-samples/issues/58

在打开连接之前设置connection.KeepAlive = true;。您还需要拥有不低于 2.19.100 的 Oracle.ManagedDataAccess.Core 版本(不知道非核心应用程序(。

我在生产版本中遇到了难以理解的偶尔池错误"Oracle 错误 ORA-12571 远程主机强行关闭现有连接"和"ORA-03135:连接失去联系"与 .Net Core 3.0 和设置有所帮助。

我在应用程序异常中收到此错误。内部异常中没有更多有用的细节。更改池化选项无法解决问题,禁用池化也无法解决问题。启用跟踪后,它会在跟踪文件中显示不同的错误"ORA-12537 网络会话文件结束"(未传播到应用程序异常(。该线程表明旧版本的oracle驱动程序是罪魁祸首。我检查了一下,我使用的是 2014 年的版本。升级到 2017/12.2c/12.2.0.1.0 版本解决了此问题。

我多年来一直随机收到此错误,因为我的连接可能会以多种方式丢失.
我已经把对我有用的解决方案放在一起。

1. 保持连接活动

1.1 使用 KeepAlive Property
KeepAlive 属性设置为 true 时,允许定期传输连接上的消息,以防止丢弃空闲连接。默认情况下,KeepAlive时间(连接在发送激活探测器之前将保持空闲的时间(为一分钟。

在 .NET 上设置 KeepAlive

connection.KeepAlive = true;

1.2 在客户端中使用EXPIRE_TIME参数
sqlnet.ora文件中的EXPIRE_TIME参数确定 Oracle 搜索失效连接的频率。将 EXPIRE_TIME 设置为 1 意味着 Oracle 每分钟检查一次连接,这将有助于保持有效连接处于活动状态。

在.NET Framework中,将其设置为web.config或app.config:

<system.web>
...
<oracle.manageddataaccess.client>
    <version number="*">
        <settings>
            <setting name="TNS_ADMIN" value="C:'Pronet'Config"/>
            <setting name="SQLNET.EXPIRE_TIME" value="1"/>
        </settings>
    </version>
</oracle.manageddataaccess.client>
...
<system.web>

In .NET Core:
.NET Core 没有等效的设置。但是,您可以创建一个 sqlnet.ora 文件并在其中包含 EXPIRE_TIME 选项。

SQLNET.EXPIRE_TIME = 1

将此文件保存在项目的TNS_ADMIN文件夹或 bin 文件夹中。

2. 应用失败时重试方法

实施失败时重试方法有助于处理连接断开问题。这涉及以这样一种方式编写代码,即如果初始尝试失败,它将重试连接。以下是如何执行此操作的示例:

2.1 对于独立方法

public string GetData(int retry = 3)
{
    string prop = string.Empty;
    try
    {
        var connectionString = "...";
        using (var conn = new OracleConnection(connectionString))
        {
            string sql = "SELECT NAME FROM USERS WHERE ID = 10";
            conn.Open();
            var cmd = new OracleCommand();
            cmd.Connection = conn;
            cmd.CommandText = sql;
            cmd.CommandType = CommandType.Text;
            var dr = cmd.ExecuteReader();
            if (dr.Read())
            {
                prop = dr["NAME"].ToString();
            }
        }
        return prop;
    }
    catch (OracleException ex)
    {
        //Retry on lost connection errors
        //In tests conducted, the following errors tend to occur when the connection is interrupted between the Open method and the ExecuteReader.
        if (ex.Number == 12570 || ex.Number == 03135 || ex.Number == 12571)
        {
            if (retry == 0)
                throw;
            return GetData(retry - 1);
        }
        throw;
    }
}

2.2 对于使用实体框架的 .NET 框架

您可以将执行策略配置为在发生错误(12571、12570、03135 等(时重试。
下面是实体框架核心中的一个示例:

public class RetryOnErrorStrategy : OracleRetryingExecutionStrategy
{
    private static readonly IList<int> ErrorNumbersToRetry = new List<int>()
    {
        12570, 12571, 03135 //lost connection errors
    };
    public SomeRetryStrategy(ExecutionStrategyDependencies dependencies)
        : base(dependencies, DefaultMaxRetryCount, DefaultMaxDelay, ErrorNumbersToRetry)
    {
    }
}
public class YourDbContext : DbContext
{
    public YourDbContext(string connectionString)
       : base(OracleDbContextOptionsExtensions.UseOracle(new DbContextOptionsBuilder(), connectionString, oracleOptions =>
       {
           oracleOptions.ExecutionStrategy(dependencies =>
               new RetryOnErrorStrategy(
               dependencies));
       }).Options)
    {
    }
    public DbSet<SomeEntity> SomeEntity { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
       ....
    }
    
    ....
}

使用失败时重试方法,您可以执行以下操作:
创建扩展方法,如 ExecuteReaderWithRetry(( 和 ExecuteNonQueryWithRetry((,并使用它们代替原始方法.
创建一个类来封装此逻辑并调用它而不是 Oracle 类。

引用

关于执行策略的回答

Ora-12570 on Azure

GitHub 上的问题