Поставщик данных 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()
После долгого анализа я узнал несколько вещей, как упомянуто ниже
- Connection.Close() не располагает соединением базы данных
- Тайм-аут соединения не означает, что проблема только с запросом базы данных
- Тайм-аут соединения также может быть из-за исчерпывающих соединений в пуле соединений (что было виновником для меня, поскольку оно достигло максимальных сессий соединения БД)
Исправление:- Анализ занял много времени, но исправление занимает всего 2 минуты
using(DbConnection instance)
{
}
Например:-
using (DbConnection objDbConnection = new DbConnection())
{
ojDbConnection.PersistData();
}
Под PersistData(); Все операции с БД, такие как Open, закрытие e.tc. будет выполнено
Как мы все знаем, "Использование" является краткой формой
try
{
}
catch()
{
}
Finally
{
Dispose objDbConnection;
}
Надеюсь, это поможет, как это помогло мне