Comment puis-je résoudre un problème de pool de connexions entre ASP.NET et SQL Server?

211

Ces derniers jours, nous voyons trop ce message d'erreur sur notre site Web:

"Le délai a expiré. Le délai s'est écoulé avant d'obtenir une connexion à partir du pool. Cela peut être dû au fait que toutes les connexions mises en pool étaient en cours d'utilisation et que la taille maximale du pool a été atteinte."

Nous n'avons rien changé dans notre code depuis un moment. J'ai révisé le code pour vérifier les connexions ouvertes qui ne se sont pas fermées, mais j'ai trouvé que tout allait bien.

  • Comment puis-je resoudre ceci?

  • Dois-je modifier ce pool?

  • Comment puis-je modifier le nombre maximal de connexions de ce pool?

  • Quelle est la valeur recommandée pour un site Web à fort trafic?


Mettre à jour:

Dois-je modifier quelque chose dans IIS?

Mettre à jour:

J'ai constaté que le nombre de connexions actives est compris entre 15 et 31, et j'ai constaté que le nombre maximal autorisé de connexions configurées dans SQL Server est supérieur à 3200 connexions, 31 de trop ou dois-je modifier quelque chose dans la configuration ASP.NET ?

Amr Elgarhy
la source
La valeur par défaut de la taille maximale du pool est 100 si je me souviens bien. La plupart des sites Web n'utilisent pas plus de 50 connexions sous une charge importante - cela dépend du temps nécessaire à l'exécution de vos requêtes. Correctif à court terme dans la chaîne de connexion: essayez de définir une valeur plus élevée dans vos chaînes de connexion: "Taille maximale du pool = ..."
splattne
combien? faire 200 par exemple?
Amr Elgarhy
3
Je pense que vous devriez vraiment rechercher la cause du problème. Vos requêtes (ou certaines d'entre elles) sont-elles très longues?
splattne
c'est peut-être la vraie raison, une requête qui prend trop de temps à exécuter, je vais chercher là
dedans
1
J'espère que vous pouvez trouver le problème. Une suggestion: si vous utilisez SQL Server, essayez le "SQL Profiler" et recherchez les longues requêtes: sql-server-performance.com/articles/per/…
splattne

Réponses:

218

Dans la plupart des cas, les problèmes de regroupement de connexions sont liés à des «fuites de connexion». Votre application ne ferme probablement pas ses connexions à la base de données correctement et de manière cohérente. Lorsque vous laissez les connexions ouvertes, elles restent bloquées jusqu'à ce que le garbage collector .NET les ferme pour vous en appelant leurFinalize() méthode.

Vous voulez vous assurer que vous fermez vraiment la connexion . Par exemple, le code suivant provoquera une fuite de connexion si le code entre .Openet Closelève une exception:

var connection = new SqlConnection(connectionString);
connection.Open();
// some code
connection.Close();                

La bonne façon serait la suivante:

var connection = new SqlConnection(ConnectionString);
try
{
     connection.Open();
     someCall (connection);
}
finally
{
     connection.Close();                
}

ou

using (SqlConnection connection = new SqlConnection(connectionString))
{
     connection.Open();
     someCall(connection);
}

Lorsque votre fonction renvoie une connexion à partir d'une méthode de classe, assurez-vous de la mettre en cache localement et d'appeler sa Closeméthode. Vous fuierez une connexion en utilisant ce code par exemple:

var command = new OleDbCommand(someUpdateQuery, getConnection());
result = command.ExecuteNonQuery();
connection().Close(); 

La connexion renvoyée depuis le premier appel à getConnection()n'est pas fermée. Au lieu de fermer votre connexion, cette ligne en crée une nouvelle et essaie de la fermer.

Si vous utilisez SqlDataReaderou a OleDbDataReader, fermez-les. Même si la fermeture de la connexion semble faire l'affaire, faites l'effort supplémentaire de fermer explicitement les objets de votre lecteur de données lorsque vous les utilisez.


Cet article « Pourquoi un dépassement de pool de connexions? » De MSDN / SQL Magazine explique de nombreux détails et suggère quelques stratégies de débogage:

  • Exécutez sp_whoou sp_who2. Ces procédures stockées système renvoient des informations de la sysprocessestable système qui indiquent l'état et les informations sur tous les processus de travail. Généralement, vous verrez un ID de processus serveur (SPID) par connexion. Si vous avez nommé votre connexion en utilisant l'argument Nom de l'application dans la chaîne de connexion, vos connexions de travail seront faciles à trouver.
  • Utilisez SQL Server Profiler avec le TSQL_Replaymodèle SQLProfiler pour tracer les connexions ouvertes. Si vous connaissez Profiler, cette méthode est plus simple que d'interroger en utilisant sp_who.
  • Utilisez l'Analyseur de performances pour surveiller les pools et les connexions. Je discute de cette méthode dans un instant.
  • Surveillez les compteurs de performances dans le code. Vous pouvez surveiller l'intégrité de votre pool de connexions et le nombre de connexions établies en utilisant des routines pour extraire les compteurs ou en utilisant les nouveaux contrôles .NET PerformanceCounter.
splattne
la source
4
Une petite correction: le GC n'appelle jamais la méthode Dispose d'un objet, seulement son finaliseur (s'il y en a un). Le finaliseur peut alors effectuer un appel "de secours" à Dispose, si nécessaire, bien que je ne sois pas certain que SqlConnection le fasse.
LukeH
1
Existe-t-il une sorte de dégradation des performances lorsque nous devons définir la taille maximale du pool 50 et que nous n'avons que peu d'utilisateurs.
mahesh sharma
37

Lors de l'installation de .NET Framework v4.6.1, nos connexions à une base de données distante ont immédiatement commencé à expirer en raison de ce changement .

Pour corriger, ajoutez simplement le paramètre TransparentNetworkIPResolutiondans la chaîne de connexion et définissez-le sur false :

Server = myServerName; Database = myDataBase; Trusted_Connection = True; TransparentNetworkIPResolution = False

ajbeaven
la source
Dans ce cas, le problème est "Le délai d'attente s'est écoulé avant d'obtenir une connexion à partir du pool". Votre chaîne de connexion a-t-elle résolu ce problème ou s'agissait-il d'un problème de négociation distinct?
FBryant87
1
D'après ce dont je me souviens, c'était exactement le même message d'erreur que dans la question. Il s'est produit immédiatement après la mise à jour vers .NET Framework v4.6.1.
ajbeaven
C'était le cas pour moi aussi, je l'ai corrigé pour moi sur un App Service que j'exécutais sur Azure, connexion à une base de données SQL Azure. J'utilisais Dapper et je disposais correctement des connexions, mais le message d'erreur «Délai d'expiration s'est écoulé avant d'obtenir une connexion à partir du pool». Mais pas plus, alors merci @ajbeaven
Steve Kennaird
13

À moins que votre utilisation n'augmente beaucoup, il semble peu probable qu'il y ait juste un retard de travail. OMI, l'option la plus probable est que quelque chose utilise des connexions et ne les libère pas rapidement. Êtes-vous sûr que vous utilisez usingdans tous les cas? Ou (par n'importe quel mécanisme) libérer les connexions?

Marc Gravell
la source
13

Avez-vous vérifié les DataReaders qui ne sont pas fermés et response.redirects avant de fermer la connexion ou un lecteur de données. Les connexions restent ouvertes lorsque vous ne les fermez pas avant une redirection.

Ivo
la source
3
+1 - Ou fonctions renvoyant des DataReaders - La connexion ne se fermera jamais en dehors de la fonction que vous avez créée ...
splattne
1
Si votre fonction renvoie SqlDataReader, il vaut mieux le convertir en DataTable que d'augmenter la taille maximale du pool
live-love
10

Nous rencontrons également ce problème de temps en temps sur notre site Web. Le coupable dans notre cas, ce sont nos statistiques / index obsolètes. Cela provoque une requête qui s'exécutait précédemment rapidement (éventuellement) devient lente et expire.

Essayez de mettre à jour les statistiques et / ou de reconstruire les index sur les tables affectées par la requête et voyez si cela aide.

Alex Czarto
la source
3
Je pense que cela expliquerait pourquoi une requête pourrait expirer, mais je ne pense pas que cela expliquerait pourquoi un délai d'attente est expérimenté lors de la tentative d'obtenir une connexion.
DrGriff
1
Il se peut qu'avec un mauvais index, les requêtes prennent plus de temps et plus de connexions sont utilisées en même temps.
LosManos
1
A travaillé pour moi après avoir mis à jour toutes les statistiques avec sp_updatestats sur une base de données: EXEC sp_updatestats;
boateng
6

Vous pouvez spécifier la taille minimale et maximale du pool en spécifiant MinPoolSize=xyzet / ou MaxPoolSize=xyzdans la chaîne de connexion. Cette cause du problème pourrait cependant être différente.

Mehrdad Afshari
la source
3
Quel est le MaxPoolSize recommandé?
Amr Elgarhy
2
La meilleure façon de procéder est probablement de ne pas le spécifier sauf si vous avez des exigences particulières et que vous connaissez une bonne taille de pool pour votre cas spécifique .
Mehrdad Afshari
1
Remarque: j'ai vérifié et j'ai constaté que les connexions actives à ma base de données sont d'environ 22 en direct, est-ce trop?
Amr Elgarhy
Je ne pense pas. La taille du pool par défaut est de 100 connexions, je pense. Cela dépend de la charge de chaque connexion sur le réseau et le serveur SQL. Si ceux-ci exécutent des requêtes lourdes, cela peut provoquer des problèmes. En outre, des problèmes de réseau peuvent se produire lors du lancement d' une nouvelle connexion et peuvent provoquer cette exception.
Mehrdad Afshari
5

J'ai également rencontré ce problème lors de l'utilisation d'une couche de données tierce dans l'une de mes applications .NET. Le problème était que la couche ne fermait pas correctement les connexions.

Nous avons jeté la couche et en avons créé une nous-mêmes, qui ferme et supprime toujours les connexions. Depuis lors, nous n'avons plus d'erreur.

Wim Haanstra
la source
Nous utilisons LLBL et le site Web fonctionne depuis 2 ans et les derniers jours ont commencé à agir comme ça.
Amr Elgarhy
5

Vous pouvez également essayer cela, pour résoudre le problème de délai d'attente:

Si vous n'avez pas ajouté httpRuntime à votre configuration Web, ajoutez-le dans la <system.web>balise

<sytem.web>
     <httpRuntime maxRequestLength="20000" executionTimeout="999999"/>
</system.web>

et

Modifiez votre chaîne de connexion comme ceci;

 <add name="connstring" connectionString="Data Source=DSourceName;Initial Catalog=DBName;Integrated Security=True;Max Pool Size=50000;Pooling=True;" providerName="System.Data.SqlClient" />

Dernière utilisation

    try
    {...} 
    catch
    {...} 
    finaly
    {
     connection.close();
    }
Ka_Ya
la source
3

Cela est principalement dû au fait que la connexion n'a pas été fermée dans l'application. Utilisez "MinPoolSize" et "MaxPoolSize" dans la chaîne de connexion.

subramanian.m
la source
3

Dans mon cas, je ne fermais pas l'objet DataReader.

        using (SqlCommand dbCmd = new SqlCommand("*StoredProcedureName*"))
        using (dbCmd.Connection = new SqlConnection(WebConfigurationAccess.ConnectionString))
            {
            dbCmd.CommandType = CommandType.StoredProcedure;

            //Add parametres
            dbCmd.Parameters.Add(new SqlParameter("@ID", SqlDbType.Int)).Value = ID;
.....
.....
            dbCmd.Connection.Open();
            var dr = dbCmd.ExecuteReader(); //created a Data reader here
            dr.Close();    //gotta close the data reader
            //dbCmd.Connection.Close(); //don't need this as 'using' statement should take care of this in its implicit dispose method.
            }
DevelopZen
la source
2

Si vous travaillez sur un code hérité complexe où une simple utilisation de (..) {..} n'est pas possible - comme je l'étais - vous pouvez consulter l'extrait de code que j'ai publié dans cette question SO pour trouver un moyen de déterminer le pile d'appels de la création de connexion lorsqu'une connexion est potentiellement divulguée (non fermée après un délai défini). Cela permet de détecter assez facilement la cause des fuites.

LOAS
la source
2

N'instanciez pas trop la connexion SQL. Ouvrez une ou deux connexions et utilisez-les pour toutes les prochaines opérations SQL.

Semble que même lors Disposedes connexions, l'exception est levée.


la source
2

En plus des solutions affichées ....

En traitant 1000 pages de code hérité, chacune appelant plusieurs fois un GetRS commun, voici une autre façon de résoudre le problème:

Dans une DLL commune existante, nous avons ajouté l' option CommandBehavior.CloseConnection :

    static public IDataReader GetRS(String Sql)
    {
        SqlConnection dbconn = new SqlConnection(DB.GetDBConn());
        dbconn.Open();
        SqlCommand cmd = new SqlCommand(Sql, dbconn);
        return cmd.ExecuteReader(CommandBehavior.CloseConnection);   
    }

Ensuite, dans chaque page, tant que vous fermez le lecteur de données, la connexion est également fermée automatiquement afin d'éviter les fuites de connexion.

    IDataReader rs = CommonDLL.GetRS("select * from table");
    while (rs.Read())
    {
        // do something
    }
    rs.Close();   // this also closes the connection
David Nelson
la source
2

Je viens d'avoir le même problème et je voulais partager ce qui m'a aidé à trouver la source: ajouter le nom de l'application à votre chaîne de connexion, puis motiver la connexion ouverte à SQL Server

select st.text,
    es.*, 
    ec.*
from sys.dm_exec_sessions as es
    inner join sys.dm_exec_connections as ec on es.session_id = ec.session_id
    cross apply sys.dm_exec_sql_text(ec.most_recent_sql_handle) st
where es.program_name = '<your app name here>'
Manfred Wippel
la source
1

Ce problème que j'avais dans mon code. Je vais coller un exemple de code que j'ai dépassé en dessous de l'erreur. Le délai d'expiration s'est écoulé avant l'obtention d'une connexion à partir du pool. Cela peut se produire car toutes les connexions regroupées étaient en cours d'utilisation et la taille maximale du pool a été atteinte.

 String query = "insert into STATION2(ID,CITY,STATE,LAT_N,LONG_W) values('" + a1 + "','" + b1 + "','" + c1 + "','" + d1 + "','" + f1 + "')";
    //,'" + d1 + "','" + f1 + "','" + g1 + "'

    SqlConnection con = new SqlConnection(mycon);
    con.Open();
    SqlCommand cmd = new SqlCommand();
    cmd.CommandText = query;
    cmd.Connection = con;
    cmd.ExecuteNonQuery();
    **con.Close();**

Vous souhaitez fermer la connexion à chaque fois. Avant cela, je ne nous ai pas connecté à cause de cela, j'ai eu une erreur. Après avoir ajouté une déclaration de fermeture, j'ai surmonté cette erreur

Aravindhan R
la source
0

Vous avez divulgué des connexions sur votre code. Vous pouvez essayer d'utiliser using pour certifier que vous les fermez.

 Using (SqlConnection sqlconnection1 = new SqlConnection(“Server=.\\SQLEXPRESS ;Integrated security=sspi;connection timeout=5”)) {
                          sqlconnection1.Open();
                          SqlCommand sqlcommand1 = sqlconnection1.CreateCommand();
                          sqlcommand1.CommandText = raiserror (‘This is a fake exception’, 17,1)”;
                          sqlcommand1.ExecuteNonQuery();  //this throws a SqlException every time it is called.
                          sqlconnection1.Close(); //Still never gets called.
              } // Here sqlconnection1.Dispose is _guaranteed_

https://blogs.msdn.microsoft.com/angelsb/2004/08/25/connection-pool-and-the-timeout-expired-exception-faq/

EduLopez
la source
0

Ce problème que j'ai rencontré auparavant. Cela a fini par être un problème avec le pare-feu. Je viens d'ajouter une règle au pare-feu. J'ai dû ouvrir le port 1433pour que le serveur SQL puisse se connecter au serveur.

Feisal Aswad
la source
0

Utilisez ceci:

finally
{
    connection.Close();
    connection.Dispose();
    SqlConnection.ClearPool();
}
Jeno M
la source
12
Peut-être que je manque le point de vue SqlConnection.ClearPool, mais est-ce que cela empêche simplement que votre connexion actuelle soit rétablie dans le pool de connexions? Je pensais que l'idée du pool de connexions était de permettre des connexions plus rapides. Libérer la connexion du pool à chaque fois qu'elle est terminée signifie qu'une nouvelle connexion devra être créée CHAQUE FOIS qu'une connexion est nécessaire, au lieu d'en extraire une de rechange dans le pool? Veuillez expliquer comment et pourquoi cette technique est utile.
Dib
0

Oui, il existe un moyen de modifier la configuration. Si vous êtes sur un serveur dédié et que vous avez simplement besoin de plus de connexions SQL, vous pouvez mettre à jour les entrées "taille maximale du pool" dans les deux chaînes de connexion en suivant ces instructions:

  1. Connectez-vous à votre serveur à l'aide de Remote Desktop
  2. Ouvrez le Poste de travail (Windows - E) et accédez à C: \ inetpub \ vhosts [domaine] \ httpdocs
  3. Double-cliquez sur le fichier web.config. Cela peut simplement être répertorié comme Web si la structure du fichier est définie pour masquer les extensions. Cela ouvrira Visual Basic ou un éditeur similaire.
  4. Trouvez vos chaînes de connexion, elles ressembleront aux exemples ci-dessous:

    "add name =" SiteSqlServer "connectionString =" server = (local); database = dbname; uid = dbuser; pwd = dbpassword; pooling = true; durée de vie de la connexion = 120; taille maximale du pool = 25; ""

5.Changez la valeur max pool size = X à la taille de pool requise.

  1. Enregistrez et fermez votre fichier web.config.
Srusti Thakkar
la source
0

Dans mon cas, j'avais une boucle infinie (à partir d'une propriété get essayant d'obtenir la valeur de la base de données) qui continuait d'ouvrir des centaines de connexions SQL.

Pour reproduire le problème, essayez ceci:

while (true)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();
        someCall(connection);
    }
}
stomie
la source