gpt4 book ai didi

.net - OracleDataReader.Read 方法超时

转载 作者:行者123 更新时间:2023-12-04 08:26:33 26 4
gpt4 key购买 nike

ODP.NET OracleCommand 类有一个 CommandTimeout 属性,可用于强制执行命令超时。此属性似乎适用于 CommandText 是 SQL 语句的情况。示例代码用于说明此属性的实际操作。在代码的初始版本中,CommandTimeout 设置为零 - 告诉 ODP.NET 不要强制超时。

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Oracle.DataAccess.Client;

namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
using (OracleConnection con = new OracleConnection("User ID=xxxx; Password=xxxx; Data Source=xxxx;"))
using (OracleCommand cmd = new OracleCommand())
{
con.Open();
cmd.Connection = con;

Console.WriteLine("Executing Query...");

try
{
cmd.CommandTimeout = 0;

// Data set SQL:
cmd.CommandText = "<some long running SQL statement>";
cmd.CommandType = System.Data.CommandType.Text;

Stopwatch watch1 = Stopwatch.StartNew();
OracleDataReader reader = cmd.ExecuteReader();
watch1.Stop();
Console.WriteLine("Query complete. Execution time: {0} ms", watch1.ElapsedMilliseconds);

int counter = 0;
Stopwatch watch2 = Stopwatch.StartNew();
if (reader.Read()) counter++;
watch2.Stop();
Console.WriteLine("First record read: {0} ms", watch2.ElapsedMilliseconds);

Stopwatch watch3 = Stopwatch.StartNew();
while (reader.Read())
{
counter++;
}
watch3.Stop();
Console.WriteLine("Records 2..n read: {0} ms", watch3.ElapsedMilliseconds);
Console.WriteLine("Records read: {0}", counter);
}
catch (OracleException ex)
{
Console.WriteLine("Exception was thrown: {0}", ex.Message);
}

Console.WriteLine("Press any key to continue...");
Console.Read();
}
}
}
}

上述代码的示例输出如下所示:
Executing Query...
Query complete. Execution time: 8372 ms
First record read: 3 ms
Records 2..n read: 1222 ms
Records read: 20564
Press any key to continue...

如果我将 CommandTimeout 更改为 3...
cmd.CommandTimeout = 3;

...然后运行相同的代码会产生以下输出:
Executing Query...
Exception was thrown: ORA-01013: user requested cancel of current operation
Press any key to continue...

调用返回引用游标的存储过程是另一回事。考虑下面的测试过程(纯粹用于测试目的):
PROCEDURE PROC_A(i_sql VARCHAR2, o_cur1 OUT SYS_REFCURSOR)
is
begin

open o_cur1
for
i_sql;

END PROC_A;

下面的示例代码可用于调用存储过程。请注意,它将 CommandTimeout 设置为值 3。
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Oracle.DataAccess.Client;

namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
using (OracleConnection con = new OracleConnection("User ID=xxxx; Password=xxxx; Data Source=xxxx;"))
using (OracleCommand cmd = new OracleCommand())
{
con.Open();
cmd.Connection = con;

Console.WriteLine("Executing Query...");

try
{
cmd.CommandTimeout = 3;

string sql = "<some long running sql>";
cmd.CommandText = "PROC_A";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add(new OracleParameter("i_sql", OracleDbType.Varchar2) { Direction = ParameterDirection.Input, Value = sql });
cmd.Parameters.Add(new OracleParameter("o_cur1", OracleDbType.RefCursor) { Direction = ParameterDirection.Output });

Stopwatch watch1 = Stopwatch.StartNew();
OracleDataReader reader = cmd.ExecuteReader();
watch1.Stop();
Console.WriteLine("Query complete. Execution time: {0} ms", watch1.ElapsedMilliseconds);

int counter = 0;
Stopwatch watch2 = Stopwatch.StartNew();
if (reader.Read()) counter++;
watch2.Stop();
Console.WriteLine("First record read: {0} ms", watch2.ElapsedMilliseconds);

Stopwatch watch3 = Stopwatch.StartNew();
while (reader.Read())
{
counter++;
}
watch3.Stop();
Console.WriteLine("Records 2..n read: {0} ms", watch3.ElapsedMilliseconds);
Console.WriteLine("Records read: {0}", counter);
}
catch (OracleException ex)
{
Console.WriteLine("Exception was thrown: {0}", ex.Message);
}

Console.WriteLine("Press any key to continue...");
Console.Read();
}
}
}
}

上面代码的示例输出如下所示:
Executing Query...
Query complete. Execution time: 34 ms
First record read: 8521 ms
Records 2..n read: 1014 ms
Records read: 20564
Press any key to continue...

请注意,执行时间非常快(34 毫秒)并且没有抛出超时异常。我们在这里看到的性能是因为直到第一次调用 OracleDataReader.Read 方法才会执行引用游标的 SQL 语句。当第一次 Read() 调用从 refcursor 读取第一条记录时,就会导致长时间运行的查询对性能的影响。

我所说明的行为意味着 OracleCommand.CommandTimeout 属性不能用于取消与引用游标关联的长时间运行的查询。我不知道 ODP.NET 中的任何属性可用于在这种情况下限制引用游标 SQL 的执行时间。有人对长时间运行的引用游标 SQL 语句的执行在一定时间后如何短路有任何建议吗?

最佳答案

这是我最终采用的解决方案。它只是 OracleDataReader 类的扩展方法。这个方法有一个超时值和一个回调函数作为参数。回调函数通常(如果不总是)是 OracleCommand.Cancel。

namespace ConsoleApplication1
{
public static class OracleDataReaderExtensions
{
public static bool Read(this OracleDataReader reader, int timeout, Action cancellationAction)
{
Task<bool> task = Task<bool>.Factory.StartNew(() =>
{
try
{
return reader.Read();
}
catch (OracleException ex)
{
// When cancellationAction is called below, it will trigger
// an ORA-01013 error in the Read call that is still executing.
// This exception can be ignored as we're handling the situation
// by throwing a TimeoutException.
if (ex.Number == 1013)
{
return false;
}
else
{
throw;
}
}
});

try
{
if (!task.Wait(timeout))
{
// call the cancellation callback function (i.e. OracleCommand.Cancel())
cancellationAction();

// throw an exception to notify calling code that a timeout has occurred
throw new TimeoutException("The OracleDataReader.Read operation has timed-out.");
}
return task.Result;
}
catch (AggregateException ae)
{
throw ae.Flatten();
}
}
}
}

这是一个如何使用它的示例。
namespace ConsoleApplication1
{
class Program
{
static string constring = "User ID=xxxx; Password=xxxx; Data Source=xxxx;";

static void Main(string[] args)
{
using (OracleConnection con = new OracleConnection(constring))
using (OracleCommand cmd = new OracleCommand())
{
cmd.Connection = con;
con.Open();

Console.WriteLine("Executing Query...");

string sql = "<some long running sql>";
cmd.CommandText = "PROC_A";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add(new OracleParameter("i_sql", OracleDbType.Varchar2) { Direction = ParameterDirection.Input, Value = sql });
cmd.Parameters.Add(new OracleParameter("o_cur1", OracleDbType.RefCursor) { Direction = ParameterDirection.Output });

try
{
// execute command and get reader for ref cursor
OracleDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);

// read first record; this is where the ref cursor SQL gets evaluated
Console.WriteLine("Reading first record...");
if (reader.Read(3000, cmd.Cancel)) { }

// read remaining records
Console.WriteLine("Reading records 2 to N...");
while (reader.Read(3000, cmd.Cancel)) { }
}
catch (TimeoutException ex)
{
Console.WriteLine("Exception: {0}", ex.Message);
}

Console.WriteLine("Press any key to continue...");
Console.Read();
}
}
}
}

这是输出的示例。
Executing Query...
Reading first record...
Exception: The OracleDataReader.Read operation has timed-out.
Press any key to continue...

关于.net - OracleDataReader.Read 方法超时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16614035/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com