Поставщик данных Oracle для.NET: истекло время ожидания запроса на подключение

У нас есть веб-служба C# WCF, размещенная в Windows 2008 SP2/IIS 7, для доступа к базе данных Oracle. Обычно доступ к данным работает нормально, но во время нагрузочного тестирования часто происходит тайм-аут, и регистрируется исключение:

Error occurred when processing XXXXXXXX Web Service
Oracle.DataAccess.Client.OracleException Connection request timed out at Oracle.DataAccess.Client.OracleException.HandleErrorHelper(Int32 errCode, OracleConnection conn, IntPtr opsErrCtx, OpoSqlValCtx* pOpoSqlValCtx, Object src, String procedure, Boolean bCheck)
   at Oracle.DataAccess.Client.OracleException.HandleError(Int32 errCode, OracleConnection conn, IntPtr opsErrCtx, Object src)
   at Oracle.DataAccess.Client.OracleConnection.Open()
   at MyWorkspace.WorkForceDataAccess.CheckStaffIdInRSW()
   at MyWorkspace.MyClass.MyFunction(MyDataType MyData)

Для запроса базы данных мы используем что-то вроде этого:

OracleConnection orConn = new OracleConnection();
orConn.ConnectionString = "user id=xxx; password=xxx; Connection Timeout=600; Max Pool Size=150; data source= (DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = MYHOST.MYDOMAIN.com)(PORT = 1771)) (CONNECT_DATA =(SERVER = DEDICATED)(SERVICE_NAME = MYSERVICE.MYDOMAIN.com)))";
orConn.Open();

using (var cmd = new OracleCommand("MY_UTIL.check_StaffIdInRSW", orConn) { CommandType = CommandType.StoredProcedure })
{
    cmd.Parameters.Add("P_Staff_Id", OracleDbType.Int32);
    cmd.Parameters["P_Staff_Id"].Direction = ParameterDirection.Input;
    cmd.Parameters["P_Staff_Id"].Value = Convert.ToInt32(MyDataObject.StaffId);

    cmd.Parameters.Add("P_retvalue", OracleDbType.Int32);
    cmd.Parameters["P_retvalue"].Direction = ParameterDirection.Output;

    cmd.ExecuteNonQuery(); // Execute the function

    //obtain result
    returnVal = int.Parse(cmd.Parameters["P_retvalue"].Value.ToString());
}

Я довольно уверен, что хранимая процедура, которая вызывается, не занимает все время. Это довольно простая процедура, которая быстро проверяет, существует ли P_Staff_Id в таблице, и возвращает результат.

Кроме того, это происходит только во время нагрузочного тестирования. Во время нормальной работы все в порядке, но во время больших нагрузок с 1 сообщением в секунду это происходит после некоторой плавной работы.

В качестве обходного пути я добавил "Время ожидания подключения =600; Максимальный размер пула =150" в строку подключения, но это не устранило проблему.

У нас на сервере разработки работает одно и то же приложение, и оно отлично работает. Мы никогда не сталкивались с этой проблемой там.

Любые предложения относительно того, что попробовать, будут оценены. Похоже, у меня заканчиваются варианты.

3 ответа

У нас была похожая проблема, и потребовалось время, чтобы отладить и исправить это. Наш код подчеркивает необходимость использования множества входных файлов и обработки множества потоков, причем каждый поток использует среду Entity и открывает соединение с Oracle db, а также выполняет несколько запросов и вставок db, которые иногда используются для файлов. Но работает большую часть времени.

Я изменил конструктор DbContext, чтобы явно открыть OracleConnection. Я добавил такой код

for (i = 0; i < 5; i++)
   try {
       oracleConnection.Open();
   } catch (OracleException) {
     Sleep for 15 ms and retry. 
     On last attempt I also do OracleConnection.ClearAllPools()
   }

Это улучшилось, но все еще не решило это полностью. Я сломался в ловушке от отладчика и увидел, что многие потоки пытаются открыть, а несколько потоков обрабатываются. При открытии в стеке Oracle, Oracle для своих внутренних целей выполняет ThreadPool.QueueUserWorkItem и ожидает его завершения. Я могу видеть на вершине стека его ожидания. Здесь доступно множество пул соединений (по умолчанию 100), я вряд ли использую 10. Так что это не вне ресурса.

Но проблема в нашем коде, также мы использовали ThreadPool.QueueUserWorkItem без дополнительного регулирования. Я подумал, что было бы здорово просто поставить в очередь все работы, которые нам нужно сделать, сколько нам нужно, и позволить.NET позаботиться об этом. Но это имеет тонкую проблему. Все наши работы использовали полный счетчик очереди. Когда OracleConnection хочет получить пул соединения из пула, он также ставится в очередь в пул потоков. Но это никогда не закончится. Все наши работы ждут OracleConnection.Open, и его процесс Queued Thread будет по-прежнему в очереди. Так что, наконец, ожидание выйдет по таймауту. Жаль, что, несмотря на наличие большого количества доступных пулов соединений, мы использовали весь процесс ThreadPool, а пул потоков Oracle даже не получил шанса. Здесь установка ThreadPool.SetMaxThreads также не поможет. Вопрос все тот же. Мы собираем все ресурсы пула потоков, и Orcale не найдет их и все еще будет в очереди.

Исправление не состоит в том, чтобы полагаться только на ThreadPool, но мы также добавляем и собственное регулирование. Я использовал BlockingCollection и sempahores и добавил лишь некоторое ограниченное количество одновременных заданий в ThreadPool, скажем 5. Таким образом, OracleConnection всегда будет находить доступный поток ThreadPool и не потерпит неудачу.

Попробуйте добавить connection.close() в конце. Я не вижу освобождения соединений в вашем коде и их явного возврата в пул соединений. Таким образом, соединение возвращается в пул соединений только при запуске GC.

Даже я имел обыкновение получать эту проблему чаще, даже после использования Connection.Close()

После долгого анализа я узнал несколько вещей, как упомянуто ниже

  1. Connection.Close() не располагает соединением базы данных
  2. Тайм-аут соединения не означает, что проблема только с запросом базы данных
  3. Тайм-аут соединения также может быть из-за исчерпывающих соединений в пуле соединений (что было виновником для меня, поскольку оно достигло максимальных сессий соединения БД)

Исправление:- Анализ занял много времени, но исправление занимает всего 2 минуты

using(DbConnection instance)
{

}

Например:-

using (DbConnection  objDbConnection = new DbConnection())
{
   ojDbConnection.PersistData();
}

Под PersistData(); Все операции с БД, такие как Open, закрытие e.tc. будет выполнено

Как мы все знаем, "Использование" является краткой формой

try
{

}
catch()
{

}
Finally
{
  Dispose objDbConnection;
}

Надеюсь, это поможет, как это помогло мне

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