Первый запрос с ODP.NET всегда медленный

Примечание. Сначала я подумал о публикации этого на DBA Exchange, но, учитывая проблему клиента.NET, я подумал, что лучше сначала спросить здесь.

У меня есть две функции, которые хранятся на моем сервере разработки Oracle 11g, которые вызываются с использованием ODP.NET (с использованием Oracle.ManagedDataAccess в отличие от Oracle.DataAccess).

В SQL Developer эти две функции работают молниеносно (что имеет смысл, они представляют собой простые запросы, выбирая только ~20000 записей), но производительность (измеренная с помощью System.Diagnostics.Stopwatch) была ниже звездной при запуске из моего приложения C# с использованием ODP. Сеть.

Вот результаты: (Игнорировать 'Время преобразования и время составления, они не являются частью процесса запроса)

Connecting time - GET_TVM_ALL: 00:00:00.0553501
Query time - GET_TVM_ALL: 00:00:05.3467058
Conversion time: 00:00:07.6508273
Connecting time - GET_TVM_STATUS_ALL_FUNC: 00:00:00.0006773
Query time - GET_TVM_STATUS_ALL_FUNC: 00:00:00.0256008
Conversion time: 00:00:03.7280097
Composing time: 00:00:00.0157274
Total Elapsed: 00:00:16.7796351

Время выполнения 5 секунд для GET_TVM_ALL смехотворно велико. Еще более удивительным является то, что второй запрос намного, намного быстрее. Это странно, так как это, без сомнения, более сложный запрос на более чем 20-кратном количестве записей.

Я переключил их, и вот результат:

Connecting time - GET_TVM_STATUS_ALL_FUNC: 00:00:00.0573807
Query time - GET_TVM_STATUS_ALL_FUNC: 00:00:05.2981962
Conversion time: 00:00:03.6474905
Connecting time - GET_TVM_ALL: 00:00:00.0007322
Query time - GET_TVM_ALL: 00:00:00.0070785
Conversion time: 00:00:07.2473809
Composing time: 00:00:00.0154049
Total Elapsed: 00:00:16.2268687

Как видите, кажется, что первый запрос всегда медленный, независимо от его содержания. Чтобы доказать это, я сделал глупую фиктивную функцию:

CREATE OR REPLACE FUNCTION GET_DUMMY
RETURN SYS_REFCURSOR
AS
    -- REFCURSOR to return data
    pCursor SYS_REFCURSOR;
    BEGIN
        OPEN pCursor FOR SELECT 1 FROM DUAL;      
        RETURN pCursor;
    END;

Теперь, вызывая это из моего кода, давайте посмотрим:

Connecting time - GET_DUMMY: 00:00:00.0581149
Query time - GET_DUMMY: 00:00:05.4103165
Conversion time: 00:00:00.0005617
Connecting time - GET_TVM_STATUS_ALL_FUNC: 00:00:00.0006580
Query time - GET_TVM_STATUS_ALL_FUNC: 00:00:00.0759243
Conversion time: 00:00:03.7577602
Connecting time - GET_TVM_ALL: 00:00:00.0000489
Query time - GET_TVM_ALL: 00:00:00.0037654
Conversion time: 00:00:07.5071360
Composing time: 00:00:00.0152159
Total Elapsed: 00:00:16.7819147

Итак, это доказывает, что первый запрос, который я выполняю, ВСЕГДА медленный.

Дополнительная информация: я открываю и закрываю новое соединение для каждой функции, которую я вызываю.

Кстати, моя вспомогательная функция:

public static List<T> ExecuteFunction<T>(string strConnection, string strFunction, OracleDbType returnType, List<DataOracleParameter> parameterList) where T : new()
{
    Stopwatch watch = new Stopwatch();

    using (OracleConnection objConnection = new OracleConnection(strConnection))
    {

        // Create the command object and set attributes
        OracleCommand objCommand = new OracleCommand(strFunction, objConnection);
        objCommand.CommandType = CommandType.StoredProcedure;

        // Set the return parameter and type
        OracleParameter returnValue = new OracleParameter();
        returnValue.OracleDbType = returnType;
        returnValue.Direction = ParameterDirection.ReturnValue;
        objCommand.Parameters.Add(returnValue);

        // Set additional parameters
        if (parameterList != null && parameterList.Count > 0)
        {
            foreach (DataOracleParameter parameter in parameterList)
            {
                OracleParameter inputValue = new OracleParameter();
                inputValue.ParameterName = parameter.ParameterName;
                inputValue.OracleDbType = parameter.ParameterType;
                inputValue.Value = parameter.ParameterValue;
                inputValue.Direction = ParameterDirection.Input;
                objCommand.Parameters.Add(inputValue);
            }
        }

        // Create a data adapter to use with the data set
        OracleDataAdapter dataAdapter = new OracleDataAdapter(objCommand);

        // Create and fill the dataset
        DataSet dataSet = new DataSet();

        watch.Start();
        dataAdapter.Fill(dataSet);
        watch.Stop();
        Console.WriteLine("Query time - {0}: {1}", strFunction, watch.Elapsed);

        List<T> valueList = dataSet.Tables[0].ToList<T>();

        return valueList;
    }
}

2 ответа

Сначала я бы предложил вам настроить FetchSize для объекта OracleCommand.

Я столкнулся с этим сегодня, и это напомнило мне о проблеме десятилетней давности с драйвером Oracle от Microsoft. Когда мы использовали параметры, это было медлительно, но если мы конвертировали в литералы, это работало как ожидалось. Параметры, я всегда думал, были лучшей практикой, поэтому это меня очень смущало.

Оказывается, это был адаптер Microsoft, который даже они признали мусором. Переход на ODP.net исправил это.

Перейдем к сегодняшнему дню... Я столкнулся с тем же явлением, используя Oracle Managed ODP.net. Когда я использовал параметры (AKA правильный путь), потребовалось НАВСЕГДА даже запустить запрос на выполнение.

using (OracleCommand cmd = new OracleCommand(sql, conn))
{
    cmd.Parameters.Add("FROM_DATE", fromDate);
    cmd.Parameters.Add("DISTRIBUTOR_ID", distributorId);

    using (OracleDataReader reader = cmd.ExecuteReader()) // Bottleneck here
    {
    }
}

Когда я переключился на литералы (опять же, ужасная практика), он сразу же побежал.

sql = sql.Replace(":DISTRIBUTOR_ID", distributorId.ToString())
    .Replace(":FROM_DATE", string.Format("'{0:dd-MMM-yyyy}'", fromDate));

using (OracleCommand cmd = new OracleCommand(sql, conn))
{
    using (OracleDataReader reader = cmd.ExecuteReader())
    {
    }
}

Разочаровывает... Проблема с управляемым ODP? Fluke? Я не буду использовать это как стандартную практику, но пока я храню литералы в этом конкретном коде. Мое приложение контролирует значения, поэтому оно безопасно для SQL-инъекций.

PS Я знаю, что должен использовать Oracle to_date и явное объявление параметров.

У меня та же проблема, я переименовываю пул приложений iis, Пусть имя короткое, Это исправило мою проблему, Хотя непонятно, но у меня работает

Другие вопросы по тегам