如何主动清空 .NET 数据库连接池?
前言
一般我们的项目中会使用1到2个数据库连接配置,同程艺龙的数据库连接配置被收拢到统一的配置中心,由DBA统一维护,业务方通过某个配置字符串拿到的是开箱即用的Connection对象。
DBA能在对业务方无侵入的情况下,给业务方切换备份数据库,之后DBA要求旧连接池必须立即被清空。
那么问题来了: 能不能立即清空.NET连接池?
注意我用得是清空,而不是释放连接。
如果有同学不知道DBA做这个要求的目的,那我啰嗦一下:
应用程序不再使用旧连接时,理论上你的连接池要被完全清空,因为单纯的释放连接,只会让连接池中的Connection处于Sleep状态,依旧维持了短时间的物理连接,这个短时间其实是不必要的占用,影响了旧连接数据库的吞吐量。
连接池知识背景
回答这个问题之前, 我们还是先研究一下.NET数据库连接池。
1、.NET数据库连接池的背景
数据库连接是一个耗时的行为,大多数应用程序只使用1到几种数据库连接,为了最小化打开连接的成本,ado.net使用了一种称为连接池的优化技术。
2、.NET 数据库连接池的表现
数据库连接池减少了必须打开新连接的次数,池程序维护了数据库物理连接。通过为每个特定的连接配置保持一组活动的连接对象来管理连接。每当应用程序尝试Open连接,池程序就会在池中找到可用的连接,如果有则返回给调用者;
应用程序Close连接对象时,池程序将连接对象返回到池中(Sleep
), 这个连接可以在下一次Open调用中重用。看黑板,下面是这次的重点:
3、.NET是如何形成数据库连接池的?
进程相同、
连接字符串相同、
连接字符串关键key顺序相同。
(连接字符串提供的关键字顺序不同也将被分到不同的池)。连接池中的可用连接的数量由连接字符串
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的要求。
希望本文设计考量、理论+论证
的行文思路对读者有所帮助。