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?

questionAnswers(2)

yourAnswerToTheQuestion