Первый запрос с 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, Пусть имя короткое, Это исправило мою проблему, Хотя непонятно, но у меня работает