【发布时间】:2013-05-12 21:59:46
【问题描述】:
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 毫秒),并且没有抛出超时异常。我们在这里看到的性能是因为引用游标的 SQL 语句在第一次调用 OracleDataReader.Read 方法之前不会执行。当第一次调用 Read() 以从 refcursor 读取第一条记录时,会导致长时间运行的查询对性能造成影响。
我已经说明的行为意味着 OracleCommand.CommandTimeout 属性不能用于取消与引用游标关联的长时间运行的查询。在这种情况下,我不知道 ODP.NET 中有任何属性可用于限制引用游标 SQL 的执行时间。有人对长时间运行的引用游标 SQL 语句的执行如何在一定时间后短路有任何建议吗?
【问题讨论】:
-
我将假设您无权访问在数据读取器上循环的代码(即,在业务类中),或者您不想污染它与性能相关的信息。
-
我确实可以访问在数据阅读器上循环的代码。在这个特定上下文中的问题是,如果正在执行的查询由于某种原因长时间运行(即在几个大表上产生笛卡尔积的“坏”查询),我想在 N 之后短路查询秒。通过“短路”,我的意思是实际上取消了数据库中的查询。 OracleCommand.CommandTimeout 属性似乎可以做到这一点,但它在上面显示的示例中不起作用,其中“命令”正在执行一个返回 ref 游标的 proc。