DbContextPool 是 ASP.NET Core 2.1 引入的新特性,可以節(jié)省創(chuàng)建 DbContext 實(shí)例的開銷,但沒(méi)有想到其中藏著一個(gè)小坑。
最近有一個(gè) ASP.NET Core 項(xiàng)目持續(xù)運(yùn)行一段時(shí)間后日志中就會(huì)出現(xiàn)數(shù)據(jù)庫(kù)連接池達(dá)到最大連接數(shù)限制的錯(cuò)誤:
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)
開始以為是哪個(gè)地方的代碼造成 DbContext 不能正常 Dispose ,但在代碼中沒(méi)有找到任何相關(guān)線索。后來(lái)實(shí)在沒(méi)有其他可以懷疑的地方,唯有
DbContextPool ,于是嘗試去掉 DbContextPool ,結(jié)果錯(cuò)誤就消失了。果然是 DbContextPool 引起的,但讓人納悶的是
DbContextPool 本來(lái)就是為了節(jié)省創(chuàng)建 DbContext
實(shí)例的開銷,怎么反而消耗更多數(shù)據(jù)庫(kù)連接,而且這個(gè)項(xiàng)目的負(fù)載很低,怎么可能把整個(gè)連接池都消耗殆盡呢?
今天在周會(huì)上談了這個(gè)怪問(wèn)題,后來(lái)突然想到:每個(gè) DbContext 實(shí)例都會(huì)占用一個(gè)數(shù)據(jù)庫(kù)連接(SqlConnection),不啟用
DbContextPool 的時(shí)候,請(qǐng)求一結(jié)束,對(duì)應(yīng) DbContext 實(shí)例就被 Dispose ,數(shù)據(jù)庫(kù)連接就會(huì)被放回連接池。而使用
DbContextPool 的時(shí)候,請(qǐng)求結(jié)束后 DbContext 不會(huì)被 Dispose 而是被放回 DbContextPool ,DbContext
被放回屬于自己的池中,就意味它對(duì)應(yīng)的數(shù)據(jù)庫(kù)連接不會(huì)被放回它所屬的連接池。DbContextPool 中的每一個(gè) DbContext
都對(duì)應(yīng)一個(gè)數(shù)據(jù)庫(kù)連接,DbContextPool 中每多一個(gè) DbContext ,數(shù)據(jù)庫(kù)連接池中就會(huì)少一個(gè)數(shù)據(jù)庫(kù)連接。當(dāng)這兩個(gè)池的大小不一樣且
DbContextPool 大于數(shù)據(jù)庫(kù)連接池,問(wèn)題就來(lái)了,DbContextPool 根據(jù)自家池(假設(shè)是128)子的大小暢快地向池中填 DbContext
,渾然不顧數(shù)據(jù)庫(kù)連接池的大小(假設(shè)是100),當(dāng)填到第 101 個(gè) DbContext 時(shí)就會(huì)出現(xiàn)上面的錯(cuò)誤。
這個(gè)項(xiàng)目中用的都是默認(rèn)設(shè)置,是不是默認(rèn)設(shè)置就會(huì)觸發(fā)這個(gè)問(wèn)題呢?
查看 DbContextPool 的 實(shí)現(xiàn)源碼
<https://github.com/aspnet/EntityFrameworkCore/blob/release/2.2/src/EFCore/EntityFrameworkServiceCollectionExtensions.cs#L145>
發(fā)現(xiàn)池的默認(rèn)大小限制是 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 的 實(shí)現(xiàn)源碼
<https://github.com/dotnet/corefx/blob/release/2.2/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnectionString.cs#L35>
發(fā)現(xiàn)連接池的默認(rèn)大小限制是 100
internal const int Max_Pool_Size = 100;
默認(rèn)設(shè)置就會(huì)觸發(fā)問(wèn)題,實(shí)實(shí)在在的一個(gè)小坑。
知道了原因,解決起來(lái)就很簡(jiǎn)單了,將 DbContextPool 的 poolSize 設(shè)置為小于數(shù)據(jù)庫(kù)連接池的 Max_Pool_Size
services.AddDbContextPool<JobDb>(option =>
option.UseSqlServer(Configuration.DbConnectionStr()), poolSize: 64);
熱門工具 換一換
