Przekroczono limit czasu dla metody OracleDataReader.Read
Klasa ODP.NET OracleCommand ma właściwość CommandTimeout, której można użyć do wymuszenia limitu czasu na wykonanie polecenia. Ta właściwość wydaje się działać w sytuacjach, gdy CommandText jest instrukcją SQL. Przykładowy kod służy do zilustrowania tej właściwości w działaniu. W początkowej wersji kodu CommandTimeout jest ustawione na zero - informując ODP.NET, aby nie wymuszał przekroczenia limitu czasu.
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();
}
}
}
}
Przykładowy wynik powyższego kodu pokazano poniżej:
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...
Jeśli zmienię CommandTimeout na coś podobnego do 3 ...
cmd.CommandTimeout = 3;
... a następnie uruchomienie tego samego kodu daje następujące dane wyjściowe:
Executing Query...
Exception was thrown: ORA-01013: user requested cancel of current operation
Press any key to continue...
Wywołanie procedury składowanej, która zwraca kursor ref, to jednak inna sprawa. Rozważ poniższy test testowy (wyłącznie do celów testowych):
PROCEDURE PROC_A(i_sql VARCHAR2, o_cur1 OUT SYS_REFCURSOR)
is
begin
open o_cur1
for
i_sql;
END PROC_A;
Poniższy przykładowy kod można wykorzystać do wywołania zapisanego proc. Zauważ, że ustawia CommandTimeout na wartość 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();
}
}
}
}
Przykładowy wynik powyższego kodu pokazano poniżej:
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...
Zauważ, że czas wykonania jest bardzo szybki (34 ms) i nie został zgłoszony wyjątek limitu czasu. Wydajność, którą tu widzimy, jest taka, że instrukcja SQL dla kursora ref nie jest wykonywana do pierwszego wywołania metody OracleDataReader.Read. Gdy zostanie wykonane pierwsze wywołanie Read () w celu odczytania pierwszego rekordu z refcursora, zostaje poniesione uderzenie wydajności z długiego zapytania.
Zachowanie, które zilustrowałem, oznacza, że właściwości OracleCommand.CommandTimeout nie można użyć do anulowania długotrwałego zapytania związanego z kursorem ref. Nie znam żadnej właściwości w ODP.NET, która może być użyta do ograniczenia czasu wykonania kursora SQL ref w tej sytuacji. Czy ktoś ma jakieś sugestie, w jaki sposób wykonanie długiego działającego polecenia SQL kursora ref może zostać zwarte po pewnym czasie?