Пул соединений с возможными разорванными соединениями

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

  • создает собственный экземпляр 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)?

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

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


person Filip Popović    schedule 20.03.2012    source источник
comment
Зачем нужно сохранять старое состояние подключения? Закройте его, как только будете готовы, лучше всего через using-statement. Здесь что-то статичное?   -  person Tim Schmelter    schedule 21.03.2012
comment
@TimSchmelter правильный .. вы должны закрыть соединение после доступа к нему ..   -  person BizApps    schedule 21.03.2012
comment
@TimSchmelter: нет, ничего статичного; мы знаем об этой плохой практике, но это устаревший код: это базовый класс для некоторых старых объектов доступа к базе данных, которые поддерживают использование одного и того же соединения из нескольких объектов доступа к базе данных - означает: wasOpened используется в методе Close базового класса, чтобы оставить соединение открыть, если он не был открыт в соответствующем методе Open - и все.   -  person Filip Popović    schedule 21.03.2012
comment
@BizApps: пожалуйста, взгляните на мой комментарий к TimSchmelter и Edit # 1.   -  person Filip Popović    schedule 21.03.2012
comment
@ FilipPopović: Боюсь, что вам нужно это серьезное изменение. Взгляните на мой ответ на связанный вопрос несколько дней назад, возможно, это поможет понять, почему не закрывать соединение - плохая практика: stackoverflow.com/questions/9705637/ (i нужно лечь спать сейчас)   -  person Tim Schmelter    schedule 21.03.2012
comment
@TimSchmelter: спасибо за ссылку, полностью согласен с этим. Просто я не понимаю, как это соотносится с моей проблемой - в настоящее время невозможны серьезные изменения. Соединение создается (новый SqlConnection (...)) внутри потока. Исключение не указывает, что кто-то другой использует соединение, а код SqlConnection.GetOpenConnection показывает разорванный объект innerConnection. И я даже не знаю, что устанавливает для innerConnection значение null, но Close - нет, это точно, потому что это соединение, которое пул соединений на самом деле не закрывается, когда вы просите его закрыть соединение.   -  person Filip Popović    schedule 21.03.2012
comment
@ FilipPopović: Вкратце: не переманивайте территорию пула соединений;) Изменить: они связаны, потому что я предполагаю, что в пуле нет доступных соединений, поскольку все они используются (не закрыт от вас).   -  person Tim Schmelter    schedule 21.03.2012
comment
@TimSchmelter: хороший аргумент, проголосовали за! :) и крепкого сна, проблем с пулом соединений у вас все-таки нет :)   -  person Filip Popović    schedule 21.03.2012
comment
@TimSchmelter: Я уверен, что закрыл его из-за блоков finally с close везде (до сих пор много раз пересматривал). когда пул соединений заполнен, ошибка истекла. Период тайм-аута истек до получения соединения из пула. Это могло произойти из-за того, что все соединения пула использовались и был достигнут максимальный размер пула. Однако завтра я перепроверяю состояние пула соединений и вернусь сюда с информацией.   -  person Filip Popović    schedule 21.03.2012


Ответы (3)


Сначала внимательно прочтите это: Пул подключений SQL Server (ADO.NET)

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

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

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

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

Мы настоятельно рекомендуем всегда закрывать соединение по окончании его использования, чтобы соединение было возвращено в пул. Вы можете сделать это, используя методы Close или Dispose объекта Connection, или открыв все соединения внутри инструкции using в C # или инструкции Using в Visual Basic. Соединения, которые не были закрыты явно, не могут быть добавлены или возвращены в пул. Дополнительные сведения см. В разделе Использование инструкции (Справочник по C #).

Короче: не переманивайте территорию пула соединений и не закрывайте соединения, как только вы закончите с ними (например, через _ 1_).

Поскольку вы не хотите бросать ваш 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();

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

person Tim Schmelter    schedule 21.03.2012
comment
+1 за очень подробный ответ и много хороших ссылок. Возможно, вам будет интересно прочитать мой ответ. - person Filip Popović; 25.03.2012
comment
забыл упомянуть: я проверил количество подключений, как вы советовали в комментариях к вопросу, и количество подключений на сервере во время нескольких выполнений не превышало 24. - person Filip Popović; 25.03.2012

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

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

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

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

Все, что сказано ...

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

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

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

person Bryan Crosby    schedule 20.03.2012
comment
Я согласен с тем, что это ваш путь в целом, но, пожалуйста, предоставьте дополнительные объяснения того, как это связано с проблемой innerConnection == null, описанной в вопросе? - person Filip Popović; 21.03.2012
comment
@ FilipPopović: Почему вы в первую очередь проверяете, открыто ли соединение? Это означает, что вы не используете оператор using где-то в своем коде ... - person Bryan Crosby; 21.03.2012
comment
Кроме того, не могли бы вы опубликовать строку подключения и дополнительные сведения о базе данных? - person Bryan Crosby; 21.03.2012
comment
Ну ... это устаревший код, и у каждого метода есть попытка открыть (как видно из ответа), наконец, закрыть. И закрыть оставляет соединение открытым, если оно было открыто при вызове Open. Просто чтобы позволить разным экземплярам (не статическому коду) использовать одно и то же соединение и транзакцию, если это применимо. Изучая код, я не вижу возможности установить для SqlConnection.InnerConnection значение null. И из SqlConnection.GetOpenConnection ясно, что он каким-то образом имеет значение null. Так что, открыт он или нет, не имеет значения. Он не работает на SqlConnection.Open с сообщением Invalid operation. Соединение закрыто. - person Filip Popović; 21.03.2012
comment
И, конечно, он закрыт, поэтому я хочу его открыть! :) - person Filip Popović; 21.03.2012
comment
Сначала не видел вашего комментария о базе данных. Взгляните на Edit # 2. - person Filip Popović; 21.03.2012
comment
+1 за рассуждения и советы. Возможно, вам будет интересно прочитать мой ответ. - person Filip Popović; 25.03.2012

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

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

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

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

person Filip Popović    schedule 25.03.2012