如何主動清空.NET資料庫連線池?

語言: CN / TW / HK

一般我們的專案中會使用1到2個數據庫連線配置,同程藝龍的資料庫連線配置被收攏到統一的配置中心,由DBA統一維護,業務方通過某個配置字串拿到的是 開箱即用的 Connection物件

DBA能在對業務方無侵入的情況下,給業務方切換備份資料庫,之後DBA要求舊連線池必須立即被清空。

那麼問題來了: 不能立即清空.NET連線池? 注意我用得是清空,而不是釋放連線。

如果有同學不知道DBA做這個要求的目的,那我囉嗦一下:

應用程式不再使用舊連線時,理論上你的連線池要被完全清空,因為單純的釋放連線,只會讓 連線池中的Connection處於Sleep狀態,依舊維持了短時間的物理連線這個短時間其實是不必要的佔用,影響了舊連線資料庫的吞吐量。

連線池知識背景

回答這個問題之前, 我們還是先研究一下.NET資料庫連線池。

1. .NET資料庫連線池的背景

資料庫連線是一個耗時的行為,大多數應用程式只使用1到幾種資料庫連線,為了最小化開啟連線的成本,ado.net使用了一種稱為連線池的優化技術。

2. .NET 資料庫連線池的表現

資料庫連線池減少了必須開啟新連線的次數,池程式維護了資料庫物理連線。

通過為每個特定的連線配置保持一組活動的連線物件來管理連線。

每當應用程式嘗試Open連線,池程式就會在池中找到可用的連線,如果有則返回給呼叫者;

應用程式Close連線物件時,池程式將連線物件返回到池中( Sleep ), 這個連線可以在下一次Open呼叫中重用。

看黑板,下面是這次的重點:

3. .NET是如何形成資料庫連線池的?

只有相同的連線配置才能被池化,.NET為不同的配置維護了不同的連線池。

相同的配置限制為:

程序相同、

連線字串相同、

(連線字串提供的關鍵字順序不同也將被分到不同的池)。

連線池中的可用連線的數量由連線字串 Max Pool Size 決定。

在一個應用程式中,有如下程式碼:

using (SqlConnection connection = new SqlConnection(  
"Integrated Security=SSPI;Initial Catalog=Northwind"))
{
connection.Open();
// Pool A is created.
}

using (SqlConnection connection = new SqlConnection(
"Integrated Security=SSPI;Initial Catalog=pubs"))
{
connection.Open();
// Pool B is created because the connection strings differ.
}

using (SqlConnection connection = new SqlConnection(
"Integrated Security=SSPI;Initial Catalog=Northwind"))
{
connection.Open();
// The connection string matches pool A.
}

上面建立了 三個Connection物件 ,但是隻形成了 兩個資料庫連線池

還是以上程式碼,如果有兩個相同的應用程式,理論上就形成了四個資料庫連線池。

4. 連線池中的連線什麼時候被移除?

連線池中的連線空閒4-8 分鐘,池程式會移除這個連線。

應用程式下線,連線池直接被清空。

如何主動清空.NET連線池

有了以上知識背景, 我們再來回顧一下DBA的要求,切換資料庫連線配置的時候,清空原連線池。

.NET提供了 ClearAllPools、ClearPool 靜態方法用於清空連線池。

•  ClearAllPools:      清空與這個DBProvider相關的所有連線池  ClearPool(DBConnection conn)      清空與這個連線物件相關的連線池

很明顯,我們這次要使用 ClearPool(DBConnection conn) 方法。

光說不練不驗證,不是我的風格。

天錘壓測 /query api 產生一個包含大量連線物件的連線池;

適當的時候,呼叫 /clearpool api清空連線池。

 public class MySqlController : Controller
{
// GET: MySql
[Route("query")]
public string Index()
{
var s = "User ID=teinfra_neo_netreplay;Password=123456;DataBase=teinfra_neo_netreplay;Server=10.100.41.196;Port=3980;Min Pool Size=1;Max Pool Size=28;CharSet=utf8;";
using (var conn = new MySqlConnection(s))
{
var comm = conn.CreateCommand();
comm.CommandText = "select count(*) from usertest;";
conn.Open();
var ret = comm.ExecuteScalar();

comm.CommandText = "select count(*) from information_schema.PROCESSLIST WHERE HOST like '10.22.12.245%';";
var len = comm.ExecuteScalar();
return $"查詢結果:{ret} ,順便查一下當前連線池的連線物件個數: {len}";
};
}

[Route("clearpool")]
public string Switch()
{
var s = "User ID=teinfra_neo_netreplay;Password=123456;DataBase=teinfra_neo_netreplay;Server=10.100.41.196;Port=3980;Min Pool Size=1;Max Pool Size=28;CharSet=utf8;";
using (var conn = new MySqlConnection(s))
{
conn.Open();
MySqlConnection.ClearPool(conn);
};

using (var conn = new MySqlConnection(s))
{
conn.Open();
var comm = conn.CreateCommand();
comm.CommandText = "select count(*) from information_schema.PROCESSLIST WHERE HOST like '10.22.12.245%';";
var len = comm.ExecuteScalar();
return $"之前已經清空連線池, 此次查詢連線池有 {v1} 個連線物件";
}

}
}

1. 壓測產生大量連線物件

2. mysql資料庫對比

mysql的連線數查詢命令: (host是web伺服器IP):

select * from information_schema.PROCESSLIST WHERE HOST like '10.22.12.245%' ;

3.  呼叫 /clearpool api,清空連線池

bingo,清空連線池的理論得到驗證。

這是我在同程藝龍最近爬的比較深的坑位, 在本次實踐中我們瞭解到:

.NET 資料庫連線池屬程式語言範疇,連線池維護了物理連線 .NET資料庫連線池的定義方式:(同一程序、同一連線字串、同一連線字串關鍵key順序一致) 被劃到一個池 DB客戶端查詢當前連線數的方式

根據這個思路改造祖傳程式碼,.NET資料獲取元件SDK 已經滿足了DBA的要求。

希望本文 設計考量、理論+論證 的行文思路對讀者有所幫助, 距離上次發文一月有餘,再次感謝5000+讀者不離不棄。

引用連結

[1] sql連線池(ado.net):  https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-connection-pooling

btw 成都同程藝龍常年招收資深golang開發者,有機會參與企業級服務治理實踐。

往期 精彩 回顧

【推薦】.NET Core開發實戰視訊課程   ★★★

.NET Core實戰專案之CMS 第一章 入門篇-開篇及總體規劃

【.NET Core微服務實戰-統一身份認證】開篇及目錄索引

Redis基本使用及百億資料量中的使用技巧分享(附視訊地址及觀看指南)

.NET Core中的一個介面多種實現的依賴注入與動態選擇看這篇就夠了

10個小技巧助您寫出高效能的ASP.NET Core程式碼

用abp vNext快速開發Quartz.NET定時任務管理介面

在ASP.NET Core中建立基於Quartz.NET託管服務輕鬆實現作業排程

現身說法:實際業務出發分析百億資料量下的多表查詢優化

關於C#非同步程式設計你應該瞭解的幾點建議

C#非同步程式設計看這篇就夠了