O que "abrir uma conexão" realmente significa?
-
27-09-2019 - |
Pergunta
Eu estava tentando explicar a alguém porque conexões de banco de dados Implemente o IDisposable, quando percebi que realmente não sei o que realmente significa "abrir uma conexão".
Então, minha pergunta é - o que C# praticamente faz quando abre uma conexão?
Obrigada.
Solução
Na verdade, existem duas classes envolvidas na implementação de uma conexão (na verdade, mais, mas estou simplificando).
Um deles é o IDbConnection
implementação (SQLConnection
, NpgsqlConnection
, OracleConnection
, etc.) que você usa em seu código. O outro é um objeto de conexão "real" que é interno à montagem e não é visível ao seu código. Vamos chamar isso de "RealConnection
"Por enquanto, embora seu nome real seja diferente de diferentes implementações (por exemplo, no NPGSQL, que é o caso em que estou mais familiarizado com a implementação, a classe é chamada NpgsqlConnector
).
Quando você cria o seu IDbConnection
, não tem um RealConnection
. Qualquer tentativa de fazer algo com o banco de dados falhará. Quando você Open()
então o seguinte acontece:
- Se o pool estiver ativado, e há um
RealConnection
na piscina, deque -a e faça com que seja oRealConnection
para oIDbConnection
. - Se o pool estiver ativado e o número total de
RealConnection
Objetos existentes são maiores que o tamanho máximo, faça uma exceção. - Caso contrário, crie um novo
RealConnection
. Inicialize-o, que envolverá a abertura de algum tipo de conexão de rede (por exemplo, TCP/IP) ou identificador de arquivo (para algo como acesso), passe pelo protocolo do banco de dados para a baleia das mãos (varia com o tipo de banco de dados) e autorize a conexão. Isso então se torna oRealConnection
para oIDbConnection
.
Operações realizadas no IDbConnection
são transformados em operações o RealConnection
faz em sua conexão de rede (ou qualquer outra coisa). Os resultados são transformados em objetos implementando IDataReader
E assim por diante, para fornecer uma interface consistente para sua programação.
Se um IDataReader
foi criado com CommandBehavior.CloseConnection
, então esse DataReader obtém "propriedade" do RealConnection
.
Quando Você ligar Close()
Então um dos seguintes itens acontece:
- Se o pool, e se o pool não estiver cheio, o objeto será colocado na fila para uso com operações posteriores.
- Caso contrário, o
RealConnection
Realizará quaisquer procedimentos definidos por protocolo para encerrar a conexão (sinalização ao banco de dados que a conexão será desligada) e fecha a conexão de rede etc. O objeto pode cair do escopo e ficar disponível para coleta de lixo.
A exceção seria se o CommandBehavior.CloseConnection
Caso aconteceu, nesse caso, é Close()
ou Dispose()
sendo chamado no IDataReader
Isso desencadeia isso.
Se você ligar Dispose()
Então a mesma coisa acontece como Close()
. A diferença é que Dispose()
é considerado como "limpeza" e pode trabalhar com using
, enquanto Close()
pode ser usado no meio da vida e seguido por um posterior Open()
.
Por causa do uso do RealConnection
Objeto e o fato de serem reunidos, abrindo e fechando as conexões mudam de algo relativamente pesado para relativamente leve. Portanto, em vez de ser importante manter as conexões abertas por um longo tempo para evitar a sobrecarga de abri -las, torna -se importante mantê -las abertas o mais curto possível, uma vez que o RealConnection
Lida com a sobrecarga para você e, quanto mais rapidamente você as usa, mais eficientemente as conexões agrupadas são compartilhadas entre os usos.
Observe também que não há problema em Dispose()
um IDbConnection
que você já ligou Close()
em (é uma regra que sempre deve ser seguro ligar Dispose()
, qualquer que seja o estado, de fato, mesmo que já fosse chamado). Portanto, se você estava ligando manualmente Close()
ainda seria bom ter a conexão em um using
Block, para capturar casos em que as exceções acontecem antes da chamada para Close()
. A única exceção é onde você realmente deseja que a conexão fique aberta; diga que você estava devolvendo um IDataReader
criado com CommandBehavior.CloseConnection
, nesse caso, você não descarta o IDbConnection
, mas Faz descarte o leitor.
Você deve deixar de descartar a conexão, então o RealConnection
não será devolvido ao pool para reutilização ou passar pelo procedimento de desligamento. O pool atingirá seu limite ou o número de conexões subjacentes aumentará ao ponto de prejudicar o desempenho e o bloqueio mais por ser criado. Eventualmente o final do final RealConnection
pode ser chamado e levar a isso ser fixo, mas a finalização apenas reduz o dano e não pode depender. (O IDbConnection
não precisa de um final, pois é o RealConnection
Isso mantém o recurso não gerenciado e/ou precisa fazer o desligamento).
Também é razoável supor que existe algum outro requisito para o descarte exclusivo da implementação do IDbConnection
além disso, e ainda deve ser descartado, mesmo que a análise acima o leve a acreditar que não é necessário (a exceção é quando CommandBehavior.CloseConnection
passa toda a carga de descarte para o IDataReader
, mas então é igualmente importante descartar esse leitor).
Outras dicas
Boa pergunta.
Do meu (um pouco limitado de conhecimento) do trabalho "sob a calça" de uma conexão SQL, muitas etapas estão envolvidas, como:
Os degraus sob o capô
- Soquete físico/tubo é aberto (usando drivers determinados, por exemplo, ODBC)
- Handshake com SQL Server
- String de conexão/credenciais negociadas
- Escopo de transação
Sem mencionar o pool de conexões, acredito que existe algum tipo de alogritmo envolvido (se a string de conexão corresponde a uma para um pool já existente, a conexão será adicionada ao pool, caso contrário, a nova será criada)
Idipossável
No que diz respeito às conexões SQL, implementamos o IDisposable para que, quando chamamos Dispose (através da Diretiva Usando ou Explicidade), ele coloca a conexão novamente no pool de conexão. Isso contrasta fortemente apenas com o antigo sqlConnection.close () - como tudo isso é fechado temporariamente, mas reserva essa conexão para uso posterior.
Do meu entendimento, .close () fecha a conexão com o banco de dados, enquanto .dispose () chama .close () e então libera recursos não gerenciados.
Esses pontos em mente, no mínimo, é uma boa prática implementar o IDisposable.
Adicionando as respostas acima ... A chave é que, ao "abrir a conexão", os recursos podem ser alocados que levarão mais do que a coleção de lixo padrão para se recuperar, a saber, um soquete/tubo/ipc aberto de alguma coisa. O método dispete () os limpa.