EFCore启用数据库连接池

本文环境为.Net5 + ASP.Net Core Web API + EFCore 5.0.12 + MySQL 5,介绍了使用EFCore开启数据库连接池的2种方法,以及它们之间的区别。

在实现了自己的DbContext之后,需要在Startup.cs的ConfigureServices方法中注册DbContext。在ASP.Net Core Web API中,每次请求对应的Controller都会创建一次DbContext对象,而普通的注册会在每次创建DbContext对象时连接一次数据库,这样会频繁的连接数据库(然后关闭),很有可能会使数据库的连接资源耗尽。因此,需要使用连接池以便减少数据库的连接次数,使系统更加稳健。

EFCore中的DbContextPool与数据库连接池是两种不同的概念:DbContextPool指的是数据库上下文实例池化,它与数据库连接池正相关,可以对DbContext进行高效管理,满足高性能场景的性能需求;而数据库连接池是在数据库驱动程序的较低级别进行管理的。

鉴于上述池化的说明,DbContextPool提升的是数据库上下文的性能,是本文的重点,数据库连接池的设置更多是在DbContextPool注册时候的数据库连接数量进行了体现。相关资料参见“(EFCore)Advanced Performance Topics”,主要内容如下:

DbContext pooling

DbContext is generally a light object: creating and disposing one doesn't involve a database operation, and most applications can do so without any noticeable impact on performance. However, each context instance does set up a various internal services and objects necessary for performing its duties, and the overhead of continuously doing so may be significant in high-performance scenarios. For these cases, EF Core can pool your context instances: when you dispose your context, EF Core resets its state and stores it in an internal pool; when a new instance is next requested, that pooled instance is returned instead of setting up a new one. Context pooling allows you to pay context setup costs only once at program startup, rather than continuously.

Note that context pooling is orthogonal to database connection pooling, which is managed at a lower level in the database driver.

EFCore提供了2种开启数据库连接池的方法,一种是使用IServiceCollection接口的扩展方法AddDbContextPool,该扩展方法注册了DbContext类,可以在Controller中注入DbContext对象;另一种方法是使用IServiceCollection接口的扩展方法AddPooledDbContextFactory,该扩展方法注册了IDbContextFactory接口,可以在Controller中注入IDbContextFactory类型的对象。

2种开启数据库连接池方法的区别是:前者(AddDbContextPool)直接注入DbContext对象,因此在请求建立连接时就进行数据库连接,尽管是使用池中的数据库连接;后者(AddPooledDbContextFactory)对IDbContextFactory进行了单例注册,在需要数据库连接的时候使用CreateDbContext方法建立数据库连接,可视为能够仅在需要请求数据库的时候动态建立数据库连接,灵活性比较大。

针对上述2种方法,AddDbContextPool方法适合在每个请求都需要连接数据库的Controller中使用,AddPooledDbContextFactory方法则没有使用限制,唯一需要注意的是处理好2个关联的需要使用DbContext对象的方法,防止频繁创建DbContext。鉴于此,更推荐使用AddPooledDbContextFactory方法启用数据库连接池。

需要注意的是,在启用数据库连接池的时候,自己实现的DbContext派生类中,构造函数不能有除了DbContextOptions以外的参数,这是因为连接池时DbContext对象会被连接池保存,这意味着DbContext对象是单例的,所以不能有其他注入的服务。参考stackoverflow

此外,数据库连接字符串默认参数是pooling=true,如果改为pooling=false也会对启用数据库连接池造成影响。

1、默认不启用数据库连接池

//注册数据库上下文
services.AddDbContext<MySqlDbContext>();

2、使用AddDbContextPool启用数据库连接池

//注册数据库上下文(启用数据库连接池)
var connectionString = Configuration.GetConnectionString("MySQLConnection");
services.AddDbContextPool<MySqlDbContext>(optionsBuilder =>
        optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)),
    64
);

3、使用AddPooledDbContextFactory启用数据库连接池

//注册数据库上下文(启用数据库连接池)
var connectionString = Configuration.GetConnectionString("MySQLConnection");
services.AddPooledDbContextFactory<MySqlDbContext>(optionsBuilder =>
        optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)),
    64
);
[Route("api/[controller]")]
[ApiController]
public class ClientsController : ControllerBase
{
    private readonly ILogger<ClientsController> _logger;
    private readonly IDbContextFactory<MySqlDbContext> _dbContextFactory;

    public ClientsController(
        ILogger<ClientsController> logger,
        IDbContextFactory<MySqlDbContext> dbContextFactory
    )
    {
        _logger = logger;
        _dbContextFactory = dbContextFactory;
    }

    [HttpGet("count")]
    public async Task<IActionResult> GetCountAsync()
    {
        try
        {
            using (var dbContext = _dbContextFactory.CreateDbContext())
            {
                var count = await dbContext.Clients.CountAsync();
                return Ok(count);
            }
        }
        catch (Exception e)
        {
            var message = $@"GetCountAsync failed:{e.Message}.";
            _logger.LogError(message);
            return Problem(message);
        }
    }
}
posted @ 2022-04-19 09:55  xhubobo  阅读(3524)  评论(3编辑  收藏  举报