ゲスト作成者:Michael J Swart(@MJSwart)
最近、アプリケーションがスローしたいくつかの例外に驚いていました。 SqlConnectionを開こうとしたときに、アプリケーションが失敗していました。例外は次のようになりました:
エラーSystem.InvalidOperationException:タイムアウトが期限切れになりました。プールから接続を取得する前にタイムアウト期間が経過しました。これは、プールされたすべての接続が使用されていて、最大プールサイズに達したために発生した可能性があります。
接続プール
.Netは接続プールを使用して、すべてのクエリで接続を確立するオーバーヘッドを回避できることを忘れないでください。接続プールは接続文字列ごとに維持され、デフォルトではプール内の接続数は100に制限されています。通常、100の接続で十分です。これまでこの例外で問題が発生したことはなく、サーバーは通常よりもビジー状態ではなかったため、MaxPoolSizeの値を増やすことを躊躇していました。データベース接続のリークが疑われ始めました。
データベース接続のリーク
メモリリークと同様に、データベース接続をタイムリーに破棄しないと、データベース接続リークが発生する可能性があります。 SqlConnectionsはIDisposableであるため、usingステートメントを使用することをお勧めします:
using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); // etc... }
SqlConnectionの使用が完了するとすぐに破棄され、実際の接続はすぐに接続プールに戻るため、他のユーザーが使用できるようになります。それ以外の場合、接続は、プロセスが終了するか、ガベージコレクションによってクリーンアップされるまで使用され続けます。
接続リークの検出
そのため、データベース接続のリークが原因でアプリケーションで接続タイムアウトが発生した場合、スタックトレースが役に立たない可能性があります。メモリリークによるメモリ不足の例外と同様に、スタックトレースには被害者に関する情報がありますが、根本的な原因はありません。では、どこでリークを見つけることができますか?
データベース接続のリークはクライアントの問題ですが、データベースサーバーからヘルプを見つけることができます。データベースサーバーで、データベースごとのプロセスごとの接続を調べて、各プールのサイズの概算を取得します。
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;
プログラム名、ホスト名、プロセスID、データベース名は通常、同じ接続プールからの接続を識別するのに十分です。
これにより、多くの接続があるプールについてさらにいくつか質問することになります。プールがある場合、しばらくスリープしているセッションはありますか?ある場合、それらはどのくらいスリープしていて、最後に実行したSQLステートメントは何でしたか?
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;
これで、テキストを使用してアプリケーションのコードベースを検索し、データベース接続のリークが発生する可能性のある場所を見つけることができます。
これらのクエリは、データベース接続リークのトラブルシューティングに役立ち、モニターまたはヘルスチェックの作成にも使用できます。
使い捨てを処分し、それらの使用法を使用し、それらの漏れを封印してください!