You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.Net Core访问PostgreSQL报连接数过多并发访问问题咨询

问题背景

现有两个基于 .Net Core 开发的控制台服务,共同访问同一个 PostgreSQL 数据库中的目标表:

  • 服务1以后台线程形式运行,每分钟向目标表插入约500行数据
  • 服务2持续读取同一张表的数据,其内置的MQTT发布端在收到新数据请求时会触发读表操作,读操作频率极高,至少为每分钟4~5次

服务运行过程中抛出固定错误:

FATAL: sorry, too many clients already

最初排查方向为读写高频并发时数据库连接未正确释放,曾考虑实现写入时阻塞读的逻辑,补充信息如下:

  • 已知系统存在连接池机制但不清楚具体配置位置
  • 核心诉求是解决PostgreSQL数据库并发访问冲突问题
  • 目前使用DbContext时已经外层包裹using语句,且在finally块中手动执行Dispose释放

现有读操作代码

using (PlatinumDBContext platinumDBContext = new PlatinumDBContext())
{
    try
    {
        var data = platinumDBContext.TrendPoints.Where(x => ids.Contains(x.TrendPointID) && x.TimeStamp >= DateTime.Now.AddHours(-timeinHours));
        result = data.Select(x => new Last24hours
        {
            Label = x.TrendPointID.ToString(),
            Value = (double)x.TrendPointValue,
            time = x.TimeStamp.ToString("MM/dd/yyyy HH:mm:ss")
        }).ToList();
    }
    catch (Exception oE)
    {
    }
    finally {
        platinumDBContext.Dispose();
    }
}

现有写操作代码

using (PlatinumDBContext platinumDBContext = new PlatinumDBContext())
{
    try
    {
        foreach (var point in trendPoints)
        {
            if (point != null)
            {
                TrendPoint item = new TrendPoint();
                item.CreatedDate = DateTime.Now;
                item.ObjectState = ObjectState.Added;
                item.TrendPointID = point.TrendID;
                item.TrendPointValue = double.IsNaN(point.Value) ? decimal.MinValue : (decimal)point.Value;
                item.TimeStamp = new DateTime(point.TimeStamp);
                platinumDBContext.Add(item);
            }
        }
        platinumDBContext.SaveChanges();
    }
    catch (Exception ex)
    {
    }
    finally
    {
        platinumDBContext.Dispose();
    }
}

解决方案

too many clients already错误和读写冲突没有直接关系,本质是数据库总连接数超过了PostgreSQL配置的最大连接上限,不需要实现写阻塞读的逻辑。PostgreSQL本身的MVCC机制天然支持读写不互斥,加阻塞逻辑反而会大幅降低性能。

第一优先级:修复代码里的连接泄漏问题

现有代码存在两个直接导致连接无法及时归还连接池的问题:

  1. 重复Dispose无实际作用,空catch块吞异常会引发状态异常:using语句本身会在代码块退出时自动执行Dispose,在finally里再次写Dispose没有任何意义。更严重的是所有异常被直接吞掉,如果查询/写入过程中出现连接状态异常,没有任何日志可排查,甚至可能出现连接断裂后无法被连接池正常回收的情况。
  2. 每次实例化DbContext却未配置连接池参数:EF Core for PostgreSQL 默认开启连接池,但如果没有在连接字符串里显式配置Max Pool Size,默认单个连接池最大连接数是100,两个服务高并发下如果连接归还不及时,很容易打满连接数。

先把代码改成正确写法,去掉重复的Dispose,补充异常记录逻辑:

修正后的读操作示例

// using会自动执行Dispose,不需要额外在finally中手动调用
using var platinumDBContext = new PlatinumDBContext();
try
{
    var data = platinumDBContext.TrendPoints.Where(x => ids.Contains(x.TrendPointID) && x.TimeStamp >= DateTime.Now.AddHours(-timeinHours));
    result = data.Select(x => new Last24hours
    {
        Label = x.TrendPointID.ToString(),
        Value = (double)x.TrendPointValue,
        time = x.TimeStamp.ToString("MM/dd/yyyy HH:mm:ss")
    }).ToList();
}
catch (Exception oE)
{
    // 必须记录异常,禁止空catch吞错
    Console.WriteLine($"读数据出错: {oE}");
    throw;
}

修正后的写操作优化

现有写操作逐条Add的效率偏低,可以优化批量操作逻辑,缩短连接持有时间:

using var platinumDBContext = new PlatinumDBContext();
try
{
    var insertList = new List<TrendPoint>();
    foreach (var point in trendPoints)
    {
        if (point == null) continue;
        insertList.Add(new TrendPoint
        {
            CreatedDate = DateTime.Now,
            ObjectState = ObjectState.Added,
            TrendPointID = point.TrendID,
            TrendPointValue = double.IsNaN(point.Value) ? decimal.MinValue : (decimal)point.Value,
            TimeStamp = new DateTime(point.TimeStamp)
        });
    }
    // 批量添加范围,比逐条Add效率更高
    platinumDBContext.AddRange(insertList);
    platinumDBContext.SaveChanges();
}
catch (Exception ex)
{
    Console.WriteLine($"写数据出错: {ex}");
    throw;
}

第二优先级:正确配置PostgreSQL连接池

连接池配置直接写在数据库连接字符串中即可,配置示例:

Host=数据库地址;Port=5432;Database=库名;Username=账号;Password=密码;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50;Connection Idle Lifetime=300;

核心参数说明:

  • Maximum Pool Size:单个进程的连接池最大持有连接数,两个服务配置的最大连接数之和不要超过PostgreSQL的max_connections配置值(PostgreSQL默认max_connections为100,注意给系统运维操作预留连接配额)
  • Connection Idle Lifetime:空闲连接超过300秒会自动回收,避免闲置连接长期占用数据库连接配额

第三优先级:检查PostgreSQL服务端配置

  1. 登录PostgreSQL执行以下命令查看当前最大连接数配置:
    SHOW max_connections;
    
    如果业务并发确实高,可以适当调大该值,注意PostgreSQL每个连接会占用约10MB左右内存,根据服务器内存配置调整,不要盲目调大。
  2. 执行以下命令查看当前所有连接的来源,排查是否有其他闲置连接占用配额:
    SELECT client_addr, application_name, state, count(*) FROM pg_stat_activity GROUP BY client_addr, application_name, state;
    
    清理长期处于idle状态的无用连接。

额外优化建议

  • 不要自行实现读写互斥锁,PostgreSQL的MVCC机制天生支持读写不阻塞,加锁只会大幅降低并发性能
  • 读操作如果是高频固定查询,可以适当加二级缓存,减少直接访问数据库的频率
  • 每分钟500条的写入量级非常小,用AddRange批量提交完全可以支撑,不需要做分库分表类的重优化

内容的提问来源于stack exchange,提问作者Geervani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 00:24:21