Klasa ODP.NET OracleCommand ma właściwość CommandTimeout, której można użyć do wymuszenia limitu czasu wykonywania polecenia. Ta właściwość wydaje się działać w sytuacjach, w których CommandText jest instrukcją SQL. Przykładowy kod służy do zilustrowania tej właściwości w akcji. W początkowej wersji kodu parametr CommandTimeout jest ustawiony na zero, co nakazuje ODP.NET nie wymuszanie limitu czasu.Limit czasu dla metody OracleDataReader.Read
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âadowa dla powyższego kodu jest pokazany 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 coś jak 3 ...
cmd.CommandTimeout = 3;
... wtedy działa tak samo kod generuje następujące wyjście:
Executing Query...
Exception was thrown: ORA-01013: user requested cancel of current operation
Press any key to continue...
Wywołanie procedury przechowywanej, która zwraca kursor ref jest już inna sprawa. Rozważ poniższą procedurę testową (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 kod przykładowy może być użyty do wywołania zapisanego procesu. Należy zauważyć, że ustawia CommandTimeout do wartości 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ładzie wyjście z kodem powyżej 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...
odnotowuje, że wykonanie jest bardzo szybki (34 ms), a wyjątek przekroczenia limitu czasu nie został zgłoszony. Wydajność, którą tu widzimy, jest spowodowana tym, że instrukcja SQL dla kursora ref nie jest wykonywana przed pierwszym wywołaniem metody OracleDataReader.Read. Kiedy pierwsze wywołanie Read() zostanie wykonane w celu odczytania pierwszego rekordu z refcursor, zostanie wygenerowane uderzenie wydajności z długo działającego zapytania.
Opisane przeze mnie zachowanie oznacza, że nie można użyć właściwości OracleCommand.CommandTimeout w celu anulowania długo działającego zapytania związanego z kursorem ref. Nie jestem świadomy żadnej właściwości w ODP.NET, która może być użyta do ograniczenia czasu wykonywania SQL kursora ref w tej sytuacji. Ktoś ma jakieś sugestie, w jaki sposób wykonanie długo działającego instrukcji SQL z korektorem może być zwarte po pewnym czasie?
mam zamiar założyć, że albo nie masz dostępu do kodu, który jest zapętlenie nad czytnikiem danych (czyli w klasie biznes) lub że nie chce zanieczyszczać go z wydajnością powiązane informacje. –
Mam dostęp do pętli kodu nad czytnikiem danych. Problem w tym konkretnym kontekście polega na tym, że jeśli wykonywane zapytanie jest z jakiegoś powodu długotrwałe (np. "Złe" zapytanie, które produkuje produkt kartezjański na kilku dużych tabelach), chciałbym sprowadzić zapytanie po N sekundy. Przez "zwarcie" mam na myśli faktycznie anulowanie zapytania w db. Właściwość OracleCommand.CommandTimeout pozornie to robi, ale nie działa w przykładzie pokazanym powyżej, gdzie "polecenie" wykonuje proces, który zwraca kursor ref. – dnickels