DbContextPool 是 ASP.NET Core 2.1 引入的新特性,能够节省建立 DbContext 实例的开销,但没有想到其中藏着一个小坑。git
最近有一个 ASP.NET Core 项目持续运行一段时间后日志中就会出现数据库链接池达到最大链接数限制的错误:github
System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached. at System.Data.Common.ADP.ExceptionWithStackTrace(Exception e)
开始觉得是哪一个地方的代码形成 DbContext 不能正常 Dispose ,但在代码中没有找到任何相关线索。后来实在没有其余能够怀疑的地方,惟有 DbContextPool ,因而尝试去掉 DbContextPool ,结果错误就消失了。果真是 DbContextPool 引发的,但让人纳闷的是 DbContextPool 原本就是为了节省建立 DbContext 实例的开销,怎么反而消耗更多数据库链接,并且这个项目的负载很低,怎么可能把整个链接池都消耗殆尽呢?数据库
今天在周会上谈了这个怪问题,后来忽然想到:每一个 DbContext 实例都会占用一个数据库链接(SqlConnection),不启用 DbContextPool 的时候,请求一结束,对应 DbContext 实例就被 Dispose ,数据库链接就会被放回链接池。而使用 DbContextPool 的时候,请求结束后 DbContext 不会被 Dispose 而是被放回 DbContextPool ,DbContext 被放回属于本身的池中,就意味它对应的数据库链接不会被放回它所属的链接池。DbContextPool 中的每个 DbContext 都对应一个数据库链接,DbContextPool 中每多一个 DbContext ,数据库链接池中就会少一个数据库链接。当这两个池的大小不同且 DbContextPool 大于数据库链接池,问题就来了,DbContextPool 根据自家池(假设是128)子的大小畅快地向池中填 DbContext ,浑然不顾数据库链接池的大小(假设是100),当填到第 101 个 DbContext 时就会出现上面的错误。ui
这个项目中用的都是默认设置,是否是默认设置就会触发这个问题呢?this
查看 DbContextPool 的 实现源码 发现池的默认大小限制是 128日志
public static IServiceCollection AddDbContextPool<TContext>( [NotNull] this IServiceCollection serviceCollection, [NotNull] Action<DbContextOptionsBuilder> optionsAction, int poolSize = 128) where TContext : DbContext => AddDbContextPool<TContext, TContext>(serviceCollection, optionsAction, poolSize);
查看 SqlConnention 的 实现源码 发现链接池的默认大小限制是 100code
internal const int Max_Pool_Size = 100;
默认设置就会触发问题,实实在在的一个小坑。get
知道了缘由,解决起来就很简单了,将 DbContextPool 的 poolSize 设置为小于数据库链接池的 Max_Pool_Size源码
services.AddDbContextPool<JobDb>(option => option.UseSqlServer(Configuration.DbConnectionStr()), poolSize: 64);