Что на самом деле означает «открытие соединения»? - PullRequest
11 голосов
/ 02 октября 2010

Я пытался объяснить кому-то, почему соединения с базой данных реализуют IDisposable, когда я понял, что действительно не знаю, что на самом деле означает "открытие соединения".
Поэтому мой вопрос - что практически делает c #, когда открывает соединение?

Спасибо.

Ответы [ 3 ]

22 голосов
/ 02 октября 2010

На самом деле для реализации соединения используются два класса (на самом деле больше, но я упрощаю).

Одним из них является реализация IDbConnection (SQLConnection, NpgsqlConnection, * 1006).* и т. д.), который вы используете в своем коде.Другой - это «реальный» объект соединения, который находится внутри сборки и не виден вашему коду.Сейчас мы назовем это «RealConnection», хотя его фактическое имя отличается в разных реализациях (например, в Npgsql, где я больше всего знаком с реализацией, класс называется NpgsqlConnector).

Когда вы создаете IDbConnection, у него нет RealConnection.Любая попытка что-то сделать с базой данных потерпит неудачу.Когда вы Open() это, тогда происходит следующее:

  1. Если пул включен, и в пуле есть RealConnection, удалите его и сделайте его RealConnection для IDbConnection.
  2. Если пул включен, а общее количество RealConnection существующих объектов превышает максимальный размер, выдается исключение.
  3. В противном случае создайте новый RealConnection.Инициализируйте его, что потребует открытия какого-либо сетевого соединения (например, TCP / IP) или дескриптора файла (для чего-то вроде Access), просмотрите протокол базы данных для рукопожатия (зависит от типа базы данных) и авторизуйте соединение.Затем он становится RealConnection для IDbConnection.

Операции, выполняемые на IDbConnection, превращаются в операции, которые RealConnection выполняет для своего сетевого подключения (или чего-либо еще).Результаты превращаются в объекты, реализующие IDataReader и т. Д., Чтобы обеспечить согласованный интерфейс для вашего программирования.

Если IDataReader был создан с CommandBehavior.CloseConnection, тогда этот обработчик данных получает «право собственности» наRealConnection.

При вызове Close() происходит одно из следующих действий:

  1. Если пул и если пул не заполнен, то объект помещаетсяв очереди для использования с более поздними операциями.
  2. В противном случае RealConnection выполнит все определенные протоколом процедуры для завершения соединения (сигнализируя базе данных, что соединение будет закрыто) и закроет сетьподключение и т. д. Затем объект может выйти из области видимости и стать доступным для сборки мусора.

Исключением может быть случай CommandBehavior.CloseConnection, в этом случае Close() или Dispose()вызывается на IDataReader, который вызывает это.

Если вы звоните Dispose(), то происходит то же самое, что и Close().Разница в том, что Dispose() рассматривается как «очистка» и может работать с using, тогда как Close() может использоваться в середине срока службы, за которым следует более поздняя Open().

Из-за использования объекта RealConnection и того факта, что они объединены в пул, открывающиеся и закрывающиеся соединения превращаются из чего-то относительно тяжелого в относительно легкое.Следовательно, вместо того, чтобы сохранять открытыми соединения в течение длительного времени, чтобы избежать накладных расходов на их открытие, важно сохранять их открытыми как можно более короткое время, поскольку RealConnection обрабатывает накладные расходы для вас, ичем быстрее вы их используете, тем эффективнее пулы подключений распределяются между пользователями.

Обратите также внимание, что все в порядке с Dispose() и IDbConnection, которые вы уже называли Close() (этоправило, что всегда должно быть безопасно вызывать Dispose(), независимо от состояния, даже если оно уже было вызвано).Следовательно, если бы вы вручную вызывали Close(), все равно было бы хорошо иметь соединение в блоке using, чтобы отследить случаи, когда возникают исключения перед вызовом Close().Единственное исключение - когда вы действительно хотите, чтобы соединение оставалось открытым;скажем, вы возвращали IDataReader, созданный с помощью CommandBehavior.CloseConnection, и в этом случае вы не располагаете IDbConnection, а do утилизируйте считыватель.

Если вам не удастся утилизировать соединение, то RealConnection не будет возвращен в пул для повторного использования или для процедуры его завершения.Либо пул достигнет своего предела, либо число базовых подключений увеличится до такой степени, что ухудшит производительность и заблокирует создание новых.В конце концов, можно вызвать финализатор на RealConnection и привести к его исправлению, но финализация только уменьшает урон и не может зависеть от него.(IDbConnection не нужен финализатор, поскольку RealConnection содержит неуправляемый ресурс и / или должен завершить работу).

Также разумно предположить, что есть некоторыедругое требование к утилизации, уникальное для реализации IDbConnection, помимо этого, и его все равно следует утилизировать, даже если анализ вышеизложенного заставляет вас считать, что это не нужно (исключение составляет CommandBehavior.CloseConnection, когда все бремя утилизации переходит на1091 *, но так же важно располагать этим читателем).

4 голосов
/ 02 октября 2010

Хороший вопрос.

Из моих (несколько ограниченных знаний) работы SQL-соединения "под капотом" много этапов, таких как:

Шаги под капотом

  1. Открыт физический сокет / канал (с использованием заданных драйверов, например ODBC)
  2. Рукопожатие с SQL Server
  3. Строка подключения / учетные данные согласованы
  4. Объем транзакции

Не говоря уже о пуле соединений, я полагаю, что существует какой-то тип алогрита (если строка соединения соответствует единице для уже существующего пула, соединение добавляется в пул, в противном случае создается новый)

IDiposable

Что касается SQL-соединений, мы реализуем IDisposable, чтобы при вызове метода dispose (либо с помощью директивы using, либо через explicity) он снова помещал соединение в пул соединений. Это резко контрастирует с простым старым sqlConnection.Close () - поскольку все это временно закрывает его, но резервирует это соединение для дальнейшего использования.

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

Имея это в виду, по крайней мере, это хорошая практика для реализации IDisposable.

0 голосов
/ 02 октября 2010

Добавление к ответам выше ... Ключ в том, что при "открытии соединения" могут быть выделены ресурсы, для восстановления которых потребуется больше, чем стандартная сборка мусора, а именно открытый сокет / канал / IPC некоторого вида.Метод Dispose () очищает их.

...