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

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

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

因此,随后的失败随机一段时间后随后成功

已经尝试过很多东西,要恢复:

环境:

  • Oracle.ManagedDataAcess版本12.1.2400(4.121.2.20150926)(nuget)(服务器上没有安装可以覆盖bin版本的gac引用)
  • Oracle Server Oracle Database 12c企业版12.1.0.2.0版 – 64位生产
  • Windows 2012(Windows Update确定)

经过:

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

附加信息:

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

组态:

服务器sqlnet.ora

 SQLNET.AUTHENTICATION_SERVICES =(NTS)
 NAMES.DIRECTORY_PATH =(TNSNAMES,EZCONNECT)

应用程序Web.config

    
<!---->

一些参考:

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

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

使用Oracle Advanced Security Options管理Oracle客户端

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

更新1

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

我将引用更改回非托管版本,一切都很好,更快,没有bytearray错误,更好的池管理。

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

这里有一些关于这个新错误的引用,你可以看到,看起来像另一个错误(仍然没有任何答案)。

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

EF + ODP.NET + CLOB =值不能为空 – 参数名称:byteArray?

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

  • 汇集管理错误
  • CLOB null / not null bytearray错误
  • 性能下降可能与池错误有关

在禁用池(Pooling = False)后,正如@ bdn02建议的那样,我可以确认它是否有效。 但是我觉得它应该影响性能,我担心将这些代码发布到生产中而没有任何合并(我认为标准值没问题)。

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

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

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

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

根据我对类似错误12570(读者与编写者)的经验,这个错误只有一个原因 – 网络上的某些东西正在终止空闲的tcp连接。 通常,这是防火墙/管理型交换机。 你说你已经排除了防火墙,但我不确定如何。 db本身可能正在终止连接(dba脚本),但我记得这是一个不同的错误。

Ora-12571可能略有不同。 但是,既然你已经确定问题是相同的,那么它是长期建立的游泳池连接,我将继续前进。

你可以做几件事:

  1. 在连接字符串中设置min pool size = 0。 这通常会为我解决问题。 它允许在应用程序空闲时关闭整个池。 虽然如果您的流量剧烈摇摆,可能需要增加decr池大小,以便更快地关闭疯狂冲突所创建的连接。
  2. 在sqlnet.ora中设置Expire_Time。 通过它的名称不明显,此设置发送一个prob数据包,这会导致满足任何tcp空闲监视。 这里唯一的问题是我不完全确定如何使用托管提供程序设置sqlnet设置。 我猜测sqlnet.ora可以进入exe目录,但我也看到一些迹象表明它可以在.config中以( 在这里看一个类似的wallet_override示例)的forms设置因为你只是得到这个在托管提供程序中,我想知道您的非托管客户端sqlnet.ora是否已经具有此设置。

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

不幸的是我创建了它作为ac#forms app,所以我在下面粘贴了表单代码和设计器代码:

Form1.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 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(); 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 { ///  /// Required designer variable. ///  private System.ComponentModel.IContainer components = null; ///  /// Clean up any resources being used. ///  /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code ///  /// Required method for Designer support - do not modify /// the contents of this method with the code editor. ///  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; } } 

我给你一个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第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数据源Data Source="myOracleDB";Pooling="false"完全解决了问题。

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

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

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