Desempenho de conexão usando ADO.NET
Autor: Julio Kawai | Publicado: 2013-01-14T19:49:00-02:00

Outro dia me questionaram a respeito do problema de desempenho associado ao fato de se abrir e fechar a conexão ao banco de dados a cada chamada de método da camada de acesso a dados.
Minha opinião era de que a diferença não era relevante.
De fato, abrir um conexão com um banco de dados é bastante trabalhoso (para o sistema) e causaria lentidão se fosse feita a cada chamada de método, no entanto, o Framework cria um &nbsp;pool de conexões que funciona de forma transparente para o programador, assim, não se abrem diversas conexões, apenas uma quantidade pré-determinada delas e as reutiliza.
Resolvi fazer alguns testes comparativos para comprovar isso.

Primeiramente medi o tempo gasto para abrir e fechar conexões comparando o cenário com pool e sem pool.


[TestMethod()]
public void TesteAbrirFecharConexao()
{
 SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder();
 sb.DataSource = "meuservidor";
 sb.InitialCatalog = "meubancodedados";
 sb.UserID = "meuususario";
 sb.Password = "senha";
 sb.PersistSecurityInfo = true;
 sb.IntegratedSecurity = false;

 sb.Pooling = true;

 using (SqlConnection conn = new SqlConnection(sb.ConnectionString))
 {
  Stopwatch watch = new Stopwatch();
  watch.Start();

  for (int i = 0; i &lt; 1000; i++)
  {
   conn.Open();

   conn.Close();
  }

  watch.Stop();
  Debug.WriteLine("Tempo total: " + watch.ElapsedMilliseconds);
 }
}

Obtive os tempos de&nbsp;137 ms utilizando pooling e 15 segundos sem pooling. Uma diferença da ordem de 100 vezes.
Em seguida comparei o tempo gasto abrindo e fechando a conexão apenas uma vez com o tempo gasto fazendo isso a cada iteração. A diferença de tempo encontrada foi irrelevante.


[TestMethod()]
//Tempo 1.8 segundos
public void TesteAbrirFecharComSelectConexao()
{
 SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder();
 sb.DataSource = "meuservidor";
 sb.InitialCatalog = "meubancodedados";
 sb.UserID = "meuususario";
 sb.Password = "senha";
 sb.PersistSecurityInfo = true;
 sb.IntegratedSecurity = false;

 sb.Pooling = true;

 using (SqlConnection conn = new SqlConnection(sb.ConnectionString))
 {
  Stopwatch watch = new Stopwatch();
  watch.Start();

  DataTable dt = new DataTable();
  for (int i = 0; i &lt; 1000; i++)
  {
   using (SqlCommand cmd = conn.CreateCommand())
   {
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = "SELECT * FROM MINHA_TABELA WHERE ID = " + i;
    conn.Open();

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
     dt.Load(reader);
    }

    conn.Close();
   }
  }

  watch.Stop();
  Debug.WriteLine("Tempo total: " + watch.ElapsedMilliseconds);
 }
}

[TestMethod()]
//Tempo 1.7 segundos
public void TesteAbertoComSelectConexao()
{
 SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder();
 sb.DataSource = "meuservidor";
 sb.InitialCatalog = "meubancodedados";
 sb.UserID = "meuususario";
 sb.Password = "senha";
 sb.PersistSecurityInfo = true;
 sb.IntegratedSecurity = false;

 sb.Pooling = true;

 using (SqlConnection conn = new SqlConnection(sb.ConnectionString))
 {
  Stopwatch watch = new Stopwatch();
  watch.Start();

  conn.Open();

  DataTable dt = new DataTable();
  for (int i = 0; i &lt; 1000; i++)
  {
   using (SqlCommand cmd = conn.CreateCommand())
   {
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = "SELECT * FROM MINHA_TABELA WHERE ID = " + i;

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
     dt.Load(reader);
    }
   }
  }

  conn.Close();

  watch.Stop();
  Debug.WriteLine("Tempo total: " + watch.ElapsedMilliseconds);
 }
}



Finalmente, para aproveitar a moda dos multicores e utilizar a nova funcionalidade do Framework 4.0, implementei uma versão que abre conexões paralelas, que rendeu uma pequena redução de 60% no tempo de execução.


[TestMethod()]
//Executa 600 ms
public void TesteAbrirFecharEmThread()
{
 SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder();
 sb.DataSource = "meuservidor";
 sb.InitialCatalog = "meubancodedados";
 sb.UserID = "meuususario";
 sb.Password = "senha";
 sb.PersistSecurityInfo = true;
 sb.IntegratedSecurity = false;

 sb.Pooling = true;

 Stopwatch watch = new Stopwatch();
 watch.Start();

 Parallel.For(0, 1000, i =&gt;
 {
  using (SqlConnection conn = new SqlConnection(sb.ConnectionString))
  {
   conn.Open();

   DataTable dt = new DataTable();
   using (SqlCommand cmd = conn.CreateCommand())
   {
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = "SELECT * FROM MINHA_TABELA WHERE ID = " + i;

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
     dt.Load(reader);
    }
   }
   conn.Close();
  }
 });

 watch.Stop();
 Debug.WriteLine("Tempo total: " + watch.ElapsedMilliseconds);
}



Minha conclusão é que abrir e fechar as conexões a cada chamada facilita a codificação, impede que o programador tenha que se lembrar do estado da conexão, e facilita escalar o sistema para uso em ambiente multiprocessado.