当开发人员遇到“Connection request timed out”这个错误时,第一反应往往是网络不通或者数据库宕机。但真正罪魁祸首其实是连接池已满——新请求在调用 Open() 时被阻塞,直到超过超时阈值才抛出异常。简单来说就是“拿不到连接”。你会在监控中看到这样的典型场景:v$session 里充斥着 status = 'inactive' 的会话,应用连接数死死卡在 Max Pool Size 上,重启后问题立刻消失,但几小时后再次复现。这种局面,十有八九是连接泄漏导致的:OracleConnection 没有释放,OracleDataReader 没有关闭,GC 不会帮你收拾残局,ODP.NET 只认 Dispose() 或 Close()。

常见的泄漏写法包括:用 using (var conn = new OracleConnection(cs)) { ... } 本来是最安全的做法,但如果里面提前执行了 return、throw 或者 goto,就会跳过 Dispose;或者在 try { cmd.ExecuteReader(); } catch { log.Error(e); return; } 这种异常路径里忘记释放连接;还有人把 OracleConnection 存在类字段或静态变量中长期持有——这些都是典型的“坑”。
连接字符串必须逐字符一致,否则池会被分裂
ODP.NET 判断是否复用连接,依据的是连接字符串的完全相同的字面量。大小写、空格、分号位置、换行符不同,都会创建独立的连接池。后果是:本该共享 100 个连接的流量,被切成两个池各占 50,每个池都卡在 Max Pool Size=100 上限,NumberOfFreeConnections 持续为 0。
- 禁止拼接连接字符串:
$"Data Source={host};"很容易引入不可见字符。 - 等号前后绝对不要加空格:
Max Pool Size=100✅,Max Pool Size = 100❌(参数会被直接忽略)。 - 统一用小写参数名,服务名全大写或全小写保持一致。
- 检查日志中是否出现多个
OracleConnection实例指向同一数据库却显示不同池名(比如pool-1和pool-2)。
关键连接字符串参数必须显式设置
以下几个参数如果不显式配置,性能优化基本等于白费,尤其在高并发场景下:
Pooling=true:必须显式写,某些旧部署环境默认是false。Statement Cache Size=50:默认是 0(禁用),开启后能显著减少硬解析;但值超过 200 可能拖慢查找。Metadata Performance=Enabled:高频租户分表场景必开,跳过首次 DML 时查询all_constraints等系统表的开销。Validate Connection=true:每次取连接前做一次轻量 ping,规避防火墙断连,不过会增加大约 1–2ms 开销。Connection Timeout=15:控制“等连接”的最长时间,设太小频繁报错,设太大让故障响应变慢。
特别提醒:Connection Lifetime=60 其实是性能杀手——它让连接被取出后存活满 60 秒就标记过期,归还时直接不进池,等于废掉复用能力。生产环境务必设为 0。
Max Pool Size 不能超过 Oracle PROCESSES 限制
每个 ODP.NET 物理连接至少占用一个 Oracle PROCESS。如果数据库 PROCESSES=150,而你设 Max Pool Size=200,实际可用连接最多也就 120–130 个(需要预留后台进程)。超出后会出现 ORA-12519 报错,而不是连接池耗尽。
- 查看当前限制:
SELECT VALUE FROM V$PARAMETER WHERE NAME = 'processes'; - 安全上限建议:
Max Pool Size = PROCESSES × 0.7(比如 150 → 100)。 - 不要在
appsettings.json里写"Max Pool Size": 100——JSON 解析后变成整数,ODP.NET 只认字符串形式参数。 Min Pool Size不维持常驻连接,只影响冷启动,值过大反而浪费资源。
真正卡住等待时间的,从来不是池大小本身,而是泄漏点没暴露、池被割裂、或者服务端资源已经达到硬限——这些细节比单纯调数字难盯得多。
