Пул соединений с возможными разорванными соединениями - PullRequest
5 голосов
/ 21 марта 2012

У меня есть несколько потоков, обращающихся к одной и той же базе данных (с одинаковой строкой соединения).Каждый поток:

  • создает свой собственный экземпляр SqlConnection с использованием той же строки подключения
  • использует код ниже, чтобы открыть свой собственный экземпляр подключения, когда ему нужен один

        try
        {
            wasOpened = connection.State == ConnectionState.Open;
    
            if (connection.State == ConnectionState.Closed)
            {
                connection.Open();
            }
        }
        catch (Exception ex)
        {
            throw new Exception(string.Format("Connection to data source {0} can not be established! Reason: {1} - complete stack {2}",
                                              connection.Database, ex.Message, ex.StackTrace == null ? "NULL" : ex.StackTrace.ToString()));
        }
    

Мы уже тестировали этот код на 2 серверах, и один сервер иногда вызывает исключение в методе SqlConnection.Open.Вот сообщение об исключении, которое мы получаем из блока catch:

Невозможно установить соединение с источником данных xyz!Причина: недопустимая операция.Соединение закрыто.- полный стек

в System.Data.SqlClient.SqlConnection.GetOpenConnection ()
в System.Data.SqlClient.SqlConnection.get_Parser ()
в System.Data.SqlClient.SqlConnection.Open ()

Проверка метода SqlConnection.GetOpenConnection показывает, что innerConnection имеет значение null:

internal SqlInternalConnection GetOpenConnection()
{
    SqlInternalConnection innerConnection = this.InnerConnection as SqlInternalConnection;
    if (innerConnection == null)
    {
        throw ADP.ClosedConnectionError();
    }
    return innerConnection;
}

Мне остается неясным: почему пул соединений иногда дает мне разорванное соединение (innerConnection == null)?

Edit # 1 : в коде нет статических свойств - мы всегда закрываем соединение при необходимости, wasOpened используется в нашем методе Close и означает: если соединение уже былооткрывается, когда вызывается наш Open, просто оставьте его открытым при Close, в противном случае закройте его.Однако это не связано с проблемой, описанной в этом вопросе (innerConnection == null).

Edit # 2 : Сервер: SQL Server 2008 R2, Windows Server 2003. Клиент: Windows Server2003 (код запускается в пользовательском компоненте пакета служб SSIS).Строка подключения: Data Source=server_name;Initial Catalog=db_name;Integrated Security=SSPI;Application Name=app_name

Ответы [ 3 ]

5 голосов
/ 21 марта 2012

Во-первых, внимательно прочитайте это: Пул соединений с сервером SQL (ADO.NET)

Я процитирую самую важную часть для вас (я думаю):

Пул соединений создается для каждой уникальной строки соединения. Когда создан пул, несколько объектов подключения созданы и добавлены в пул, так что требование минимального размера пула удовлетворяется. Соединения добавляются в пул по мере необходимости, до максимального пула указанный размер (по умолчанию 100) . Соединения освобождаются обратно в бассейн когда они закрыты или утилизированы .

Когда запрашивается объект SqlConnection, он получается из пула если доступное соединение доступно. Для использования соединение должно быть быть неиспользованным, иметь соответствующий контекст транзакции или быть не связанным с любой контекст транзакции, и иметь действительную ссылку на сервер.

Диспетчер подключений удовлетворяет запросы на подключение перераспределение соединений по мере их выпуска обратно в пул. Если достигнут максимальный размер пула, и не используется соединение доступно, запрос в очереди . Пулер затем пытается восстановить любой подключений до истечения времени ожидания (по умолчанию 15 секунд). Если диспетчер не может удовлетворить запрос до времени соединения исключение .

Настоятельно рекомендуем всегда закрывать соединение , когда вы закончили использовать его, так что соединение будет возвращено бассейн. Вы можете сделать это, используя методы Close или Dispose Объект подключения или путем открытия всех подключений внутри использования оператор в C # или оператор Using в Visual Basic. связи которые явно не закрыты, не могут быть добавлены или возвращены бассейн. Для получения дополнительной информации см. Использование Statement (C # Reference)

Короче говоря : не вмешиваться на территории пула соединений и не закрывать соединения, как только вы закончите с ними (например, через using-statement).

Поскольку вы не хотите выбрасывать свой DB-Class в мусорное ведро , я бы предложил либо увеличить максимальный размер пула и / или тайм-аут, либо отключить пул и посмотреть, что произойдет.

<add name="theConnectionString" connectionString="Data Source=(local);
     Database=AdventureWorks; Integrated Security=SSPI; 
     Max Pool Size=200; Pooling=True; Timout=60" />

Вам также следует попытаться перехватить эту конкретную ошибку и очистить все пулы соединений :

System.Data.SqlClient.SqlConnection.ClearAllPools();

Или взгляните на эти вопросы, которые выглядят многообещающе:

2 голосов
/ 25 марта 2012

Ответы, предоставленные Тимом Шмелтером и Брайаном Кросби , очень ценны с точки зрения руководящих принципов, ссылок, передового опыта и так далее.К сожалению, этого было недостаточно для меня, потому что я не могу внести критические изменения в унаследованный код.

Решение этой конкретной проблемы заключалось в инкапсуляции методов SqlConnection Open и Close с одинаковой блокировкой.Обратите внимание, что он соответствует нашему сценарию, он может не соответствовать другим.

Мне очень жаль, я не могу сейчас углубиться в это и выяснить, не был ли источником проблемы наш код или пул соединений не полностьюпотокобезопасный.Я знаю, что, скорее всего, источник находится в нашем коде.Имея это в виду, этот ответ - скорее обходной путь, чем реальное решение.

Прежде чем кто-либо применяет этот обходной путь, пожалуйста, прочитайте и другие ответы.

2 голосов
/ 21 марта 2012

У меня есть несколько потоков, обращающихся к одной и той же базе данных (с одинаковой строкой соединения).Каждый поток:

  1. создает свой собственный экземпляр SqlConnection, используя ту же строку подключения
  2. использует код ниже, чтобы открыть свой собственный экземпляр подключения, когда ему нужен один

Если у вас возникла проблема, которая появляется случайным образом, в вашем случае, учитывая код, который вы отобразили, у вас может быть:

  1. Проблема с пулом соединений на сервере
  2. Состояние гонки где-то в вашем коде

Все это, как говорится ...

Вы должны заключить SqlConnection в оператор using.Таким образом, соединение будет закрыто, когда ваш код или поток завершат работу с ним.

using (SqlConnection connection = new SqlConnection(connectionString))
{
   //... stuff
}

Таким образом, соединение гарантированно вызовет метод Dispose() (который в любом случае вызывает внутренний вызов Close()),Таким образом, соединение может быть возвращено в пул, если оно используется (что, вероятно, используется).

...