Auteur invité :Michael J Swart (@MJSwart)
Nous avons récemment été surpris par un certain nombre d'exceptions que notre application a lancées. Notre application échouait lors de la tentative d'ouverture d'une SqlConnection. Les exceptions ressemblaient à ceci :
Erreur System.InvalidOperationException :Délai expiré. Le délai d'attente s'est écoulé avant l'obtention d'une connexion à partir du pool. Cela peut être dû au fait que toutes les connexions regroupées étaient utilisées et que la taille maximale du pool a été atteinte.
Groupes de connexions
N'oubliez pas que .Net utilise des pools de connexions pour éviter la surcharge liée à l'établissement d'une connexion à chaque requête. Les pools de connexions sont conservés pour chaque chaîne de connexion et, par défaut, le nombre de connexions dans le pool est limité à cent. Une centaine de connexions suffisent généralement. Nous n'avons jamais eu de problème avec cette exception auparavant et nos serveurs n'étaient pas plus occupés que d'habitude, nous avons donc hésité à augmenter la valeur de MaxPoolSize. Nous avons commencé à soupçonner des fuites de connexion à la base de données.
Fuites de connexion à la base de données
Tout comme les fuites de mémoire, des fuites de connexion à la base de données peuvent se produire si vous ne supprimez pas vos connexions à la base de données en temps opportun. Les SqlConnections sont IDisposable, il est donc recommandé d'utiliser l'instruction using :
using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); // etc... }
Dès que vous avez terminé avec SqlConnection, il est supprimé et la connexion réelle revient immédiatement au pool de connexions afin qu'elle puisse être utilisée par quelqu'un d'autre. Sinon, la connexion reste utilisée jusqu'à ce que le processus se termine ou que la récupération de place la nettoie.
Rechercher vos fuites de connexion
Ainsi, si votre application rencontre des délais d'expiration de connexion en raison d'une fuite de connexion à la base de données, les traces de la pile peuvent ne pas vous aider. Tout comme une exception de mémoire insuffisante due à une fuite de mémoire, la trace de la pile contient des informations sur la victime, mais pas sur la cause première. Alors, où pouvez-vous trouver la fuite ?
Même si les fuites de connexion à la base de données sont un problème client, vous pouvez trouver de l'aide auprès du serveur de base de données. Sur le serveur de base de données, examinez les connexions par processus et par base de données pour obtenir une estimation approximative de la taille de chaque pool :
select count(*) as sessions, s.host_name, s.host_process_id, s.program_name, db_name(s.database_id) as database_name from sys.dm_exec_sessions s where is_user_process = 1 group by host_name, host_process_id, program_name, database_id order by count(*) desc;
Le nom du programme, le nom de l'hôte, l'ID du processus et le nom de la base de données sont généralement suffisants pour identifier les connexions provenant du même pool de connexions.
Cela m'amène à poser quelques questions supplémentaires sur les piscines avec de nombreuses connexions. Étant donné un pool, y a-t-il des sessions qui dorment depuis un certain temps et, si oui, combien de temps ont-elles dormi et quelle a été la dernière instruction SQL qu'elles ont exécutée ?
declare @host_process_id int = 1508; declare @host_name sysname = N'SERV4102'; declare @database_name sysname = N'My_Database'; select datediff(minute, s.last_request_end_time, getdate()) as minutes_asleep, s.session_id, db_name(s.database_id) as database_name, s.host_name, s.host_process_id, t.text as last_sql, s.program_name from sys.dm_exec_connections c join sys.dm_exec_sessions s on c.session_id = s.session_id cross apply sys.dm_exec_sql_text(c.most_recent_sql_handle) t where s.is_user_process = 1 and s.status = 'sleeping' and db_name(s.database_id) = @database_name and s.host_process_id = @host_process_id and s.host_name = @host_name and datediff(second, s.last_request_end_time, getdate()) > 60 order by s.last_request_end_time;
Le texte peut maintenant être utilisé pour rechercher dans la base de code de votre application afin de trouver où vous pourriez avoir une fuite de connexion à la base de données.
Ces requêtes sont utiles pour dépanner une fuite de connexion à la base de données et elles peuvent également être utilisées pour créer un moniteur ou un bilan de santé.
Jetez vos produits jetables, utilisez ces usages, scellez ces fuites !