第一章:Go数据库连接池耗尽?——现象、根源与影响面全景扫描
当Go服务在高并发场景下突然响应延迟飙升、HTTP 500错误激增,或日志中频繁出现 sql: connection pool exhausted 错误时,数据库连接池耗尽往往已是系统瓶颈的明确信号。该问题并非孤立故障,而是横跨应用层、数据库驱动层与底层基础设施的连锁反应。
典型现象识别
- HTTP请求超时(如
context deadline exceeded)集中出现在数据库操作路径; net/http/pprof中goroutine数量异常攀升,大量协程阻塞在database/sql.(*DB).Conn或QueryContext调用;- 数据库端观察到活跃连接数稳定在
maxOpenConns上限,但实际业务吞吐未达预期。
根源深度剖析
根本原因在于连接生命周期管理失控:
- 连接泄漏:
rows.Close()或tx.Commit()/Rollback()未被调用,导致连接无法归还池中; - 配置失衡:
maxOpenConns过小(如默认值0即无上限,但实际受maxIdleConns和maxLifetime协同制约),或maxIdleConns设置为0导致空闲连接被立即释放; - 长事务阻塞:单个事务持有连接超时(如未设
context.WithTimeout),使连接长期不可复用。
影响面全景图
| 维度 | 表现形式 |
|---|---|
| 应用可用性 | 请求失败率上升,P99延迟跳变 |
| 资源效率 | 大量goroutine等待连接,内存持续增长 |
| 数据库负载 | 连接数满载,但QPS未提升,CPU利用率反降 |
验证连接池状态可执行以下诊断代码:
db, _ := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/test")
// 输出当前连接池统计
stats := db.Stats()
fmt.Printf("Open connections: %d, In use: %d, Idle: %d\n",
stats.OpenConnections, stats.InUse, stats.Idle)
// 强制回收空闲连接(仅用于调试)
db.Close() // 注意:生产环境慎用
该代码通过 Stats() 实时暴露连接池内部状态,结合 InUse 与 Idle 的比值可快速判断是否存在连接滞留。若 InUse 持续等于 maxOpenConns 且 Idle 长期为0,则确认耗尽已发生。
第二章:sql.DB连接池三参数深度解析与调优实践
2.1 SetMaxOpenConns:并发上限与资源争抢的临界点建模
数据库连接池的 SetMaxOpenConns 并非简单限制数字,而是对系统吞吐、延迟与稳定性三者博弈的量化锚点。
连接耗尽的雪崩路径
当并发请求超过 MaxOpenConns 时,新请求将阻塞在连接获取队列中,触发级联延迟放大:
db.SetMaxOpenConns(10) // 全局最大活跃连接数
db.SetMaxIdleConns(5) // 空闲连接保有量,避免频繁创建销毁
db.SetConnMaxLifetime(30 * time.Minute) // 连接老化策略,防长连接僵死
逻辑分析:
MaxOpenConns=10意味着最多10个goroutine可同时执行SQL;若平均查询耗时200ms,则理论峰值QPS≈50。但真实场景中,锁竞争、网络抖动、事务嵌套会使有效吞吐骤降——此时临界点由QPS × 响应时间 × 并发系数动态决定。
资源争抢的典型表现
| 现象 | 根本诱因 | 观测指标 |
|---|---|---|
| P99延迟突增300% | 连接等待队列堆积 | sql.DB.Stats().WaitCount |
| CPU空转但QPS停滞 | goroutine阻塞于pool.conn() |
runtime.NumGoroutine()持续高位 |
| 连接泄漏 | defer rows.Close()缺失 |
Stats().OpenConnections持续增长 |
临界建模示意
graph TD
A[请求抵达] --> B{连接池有空闲?}
B -->|是| C[分配连接,执行SQL]
B -->|否| D[进入等待队列]
D --> E{超时或中断?}
E -->|是| F[返回error]
E -->|否| G[排队至连接释放]
2.2 SetMaxIdleConns:空闲连接复用效率与内存泄漏风险平衡术
SetMaxIdleConns 控制连接池中全局最大空闲连接数,直接影响复用率与资源驻留时长。
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100, // 全局最多100个空闲连接
MaxIdleConnsPerHost: 50, // 每Host最多50个(优先级更高)
IdleConnTimeout: 30 * time.Second,
},
}
逻辑分析:
MaxIdleConns是全局硬上限;当MaxIdleConnsPerHost总和可能超限时,该参数兜底防内存膨胀。若设为0,则禁用空闲连接复用,每次请求新建连接——高延迟、低吞吐;若设为过高(如1000+),在突发流量退潮后易残留大量未释放连接,引发内存缓慢增长。
关键权衡维度
- ✅ 提升:短连接高频场景下的 RTT 降低(复用免握手)
- ⚠️ 风险:
IdleConnTimeout未配合调优时,空闲连接长期驻留堆内存 - 📊 推荐初始值:
MaxIdleConns = 50~200,MaxIdleConnsPerHost = min(50, MaxIdleConns/4)
| 场景 | 建议 MaxIdleConns | 理由 |
|---|---|---|
| 内部微服务调用 | 100 | 网络稳定,Host数有限 |
| 对外多租户API网关 | 200 | Host分散,需保障并发复用 |
| 批量离线任务客户端 | 20 | 连接周期长,避免冗余驻留 |
graph TD
A[请求发起] --> B{连接池有可用空闲连接?}
B -- 是 --> C[复用连接 → 快速发送]
B -- 否 --> D[新建连接 → 加入空闲队列?]
D --> E{当前空闲总数 < MaxIdleConns?}
E -- 是 --> F[加入队列]
E -- 否 --> G[立即关闭释放]
2.3 SetConnMaxLifetime:连接老化策略与数据库端超时协同机制
连接老化与服务端超时的错配风险
当数据库配置 wait_timeout=60s,而 Go 应用未设置 SetConnMaxLifetime,空闲连接可能在第61秒被 MySQL 主动断开,后续 Ping() 或查询将触发 driver: bad connection。
协同配置黄金法则
- 应用层
SetConnMaxLifetime必须 严格小于 数据库wait_timeout(建议设为 80%) - 同时需配合
SetMaxIdleConns和SetMaxOpenConns避免连接雪崩
示例配置与逻辑分析
db.SetConnMaxLifetime(45 * time.Second) // 小于 MySQL wait_timeout(60s)
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(50)
此配置确保连接在老化前被主动回收,避免因服务端静默断连导致的 invalid connection 错误;45s 是经压测验证的平衡点——既减少重连开销,又规避超时风险。
超时协同关系表
| 维度 | 数据库端(MySQL) | Go sql.DB 端 |
|---|---|---|
| 作用对象 | TCP 连接 | 连接池中的连接实例 |
| 触发条件 | 空闲超时 | 创建后存活时长 |
| 推荐差值 | ≥15s | — |
graph TD
A[应用创建连接] --> B{连接存活时间 < 45s?}
B -->|是| C[正常使用]
B -->|否| D[连接被标记为“可销毁”]
D --> E[下次获取连接时触发重建]
2.4 三参数耦合效应实测:高并发场景下的TPS/延迟/连接抖动曲线分析
在压测平台中同步采集 TPS、p95延迟(ms) 和 活跃连接抖动率(%) 三维度时序数据,发现非线性耦合现象显著:
关键观测现象
- 当并发线程数 ≥ 1200 时,TPS 增长趋缓,延迟突增 37%,连接抖动率跃升至 8.2%
- 抖动峰值与 GC Pause(G1 Mixed GC)窗口高度重合(±120ms)
核心指标对比(峰值区段)
| 并发量 | TPS | p95延迟 | 连接抖动率 |
|---|---|---|---|
| 800 | 4210 | 48 | 1.3% |
| 1600 | 4890 | 126 | 8.2% |
// JVM 启动参数关键配置(影响三参数耦合边界)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150 // 直接约束延迟上限阈值
-XX:G1HeapRegionSize=2M // 影响GC频率→连接复用稳定性
-Dio.netty.maxDirectMemory=2g // 防止堆外内存抖动传导
参数说明:
MaxGCPauseMillis设定过紧会触发更频繁 Mixed GC,加剧连接池连接回收抖动;G1HeapRegionSize偏小则 Region 数量激增,增加元空间压力,间接抬高 p95 尾部延迟。
耦合路径示意
graph TD
A[并发请求激增] --> B[线程池饱和]
B --> C[G1 Mixed GC 触发]
C --> D[Netty EventLoop 阻塞]
D --> E[连接超时重试+连接池抖动]
E --> F[TPS 下滑 & 延迟尖峰]
2.5 黄金比例公式推导:基于QPS、平均查询耗时与DB连接数限制的动态计算模型
在高并发场景下,数据库连接资源需在吞吐(QPS)与响应延迟间取得平衡。设 QPS 为每秒查询数,T_avg(单位:秒)为平均查询耗时,N_max 为最大可用连接数,则稳态下连接池应满足:
# 黄金比例约束:连接数 ≈ QPS × T_avg × 安全冗余系数 α
alpha = 1.2 # 防抖动冗余(经验值,范围1.1–1.5)
N_required = QPS * T_avg * alpha
# 动态裁剪:不可超过物理上限
N_final = min(int(N_required), N_max)
逻辑说明:
QPS × T_avg是Little定律下的理论并发连接均值;α补偿慢查询毛刺与连接复用不均;min()确保不越界。
关键参数关系如下:
| 参数 | 符号 | 物理意义 | 典型取值范围 |
|---|---|---|---|
| 查询吞吐量 | QPS | 每秒有效请求量 | 100–5000 |
| 平均耗时 | T_avg | DB端执行+网络RTT均值 | 0.01–2.0 s |
| 连接上限 | N_max | 数据库max_connections | 100–2000 |
graph TD
A[QPS输入] --> B[× T_avg]
B --> C[× α 冗余]
C --> D[裁剪至N_max]
D --> E[N_final 用于连接池配置]
第三章:连接泄漏的典型模式与根因定位方法论
3.1 defer db.Close()误区与事务未提交导致的隐式连接滞留
defer db.Close() 的常见误用场景
db.Close() 并非“释放单个连接”,而是关闭整个连接池,应仅在应用退出前调用。若在函数内误用,将导致后续所有数据库操作失败:
func badExample() {
db, _ := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/test")
defer db.Close() // ⚠️ 错误:过早关闭连接池
_, _ = db.Query("SELECT 1") // panic: sql: database is closed
}
逻辑分析:sql.DB 是连接池抽象,Close() 立即终止所有空闲连接并拒绝新请求;参数 db 为全局复用对象,非每次调用新建。
事务未提交引发的连接滞留
开启事务后未显式 Commit() 或 Rollback(),会导致底层连接被长期占用,无法归还池中:
| 状态 | 连接是否可重用 | 原因 |
|---|---|---|
tx.Commit() 后 |
✅ | 连接归还池 |
tx.Rollback() 后 |
✅ | 连接归还池 |
| 函数返回且无显式结束 | ❌ | 连接持续占用,直至超时或进程终止 |
func leakyTx() error {
tx, _ := db.Begin()
_, _ := tx.Exec("INSERT INTO users(name) VALUES(?)", "alice")
// 忘记 tx.Commit() 或 tx.Rollback()
return nil // 连接被隐式持有,池资源逐步耗尽
}
逻辑分析:sql.Tx 持有独占连接,其生命周期独立于 db;未终结事务 → 连接永不释放 → 连接池饥饿。
正确实践建议
- ✅
db.Close()仅置于main()退出前或服务 shutdown 钩子中 - ✅ 所有
tx必须配对defer tx.Rollback()+ 显式Commit() - ✅ 使用
context.WithTimeout为事务设边界,防无限挂起
graph TD
A[Start Transaction] --> B{Commit/Rollback?}
B -->|Yes| C[Release Connection]
B -->|No| D[Held Until Timeout/Process Exit]
D --> E[Connection Leak]
3.2 context.Context超时未传播至Query/Exec引发的连接悬挂
当 context.WithTimeout 创建的上下文未被底层驱动正确消费,db.Query 或 db.Exec 会忽略超时信号,导致 goroutine 长期阻塞在 net.Conn.Read,连接无法释放。
根本原因:驱动层未透传 context
多数旧版 driver(如 mysql v1.6.0 前)仅支持 driver.Conn 接口,不实现 driver.ConnContext,故 QueryContext/ExecContext 退化为无上下文调用:
// ❌ 错误示例:未使用 Context 方法
rows, err := db.Query("SELECT SLEEP(10)") // 忽略 ctx.Done()
// ✅ 正确做法:显式调用 Context 版本
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT SLEEP(10)") // 触发中断
QueryContext内部检查ctx.Done()并调用cancel()关闭底层连接;若驱动不支持,则降级为阻塞等待,造成连接池耗尽。
典型表现对比
| 场景 | 连接状态 | 超时响应 | 是否复用 |
|---|---|---|---|
QueryContext + 兼容驱动 |
及时关闭 | ✅ | ✅ |
Query / 不兼容驱动 |
持续占用 | ❌ | ❌ |
graph TD
A[HTTP Handler] --> B[context.WithTimeout]
B --> C[db.QueryContext]
C --> D{Driver implements ConnContext?}
D -->|Yes| E[中断网络读取]
D -->|No| F[阻塞直至SQL完成]
3.3 ORM层(如GORM)隐式连接获取未释放的反模式代码审计
隐式连接的陷阱
GORM 在 Preload 或关联查询中会自动复用底层 *sql.DB 连接,但若未显式控制生命周期,连接可能滞留于 sql.Conn 池中,导致连接泄漏。
典型反模式代码
func GetUserWithOrders(userID uint) *User {
var user User
// ❌ 隐式连接未释放:Preload 触发嵌套查询,连接在 defer 前已归还池,但事务/上下文未绑定
db.Preload("Orders").First(&user, userID)
return &user // user.Orders 可能触发 N+1 或延迟获取,再次拉取连接
}
逻辑分析:
Preload内部执行 JOIN 查询并复用当前连接;若该函数被高频调用且无 context.WithTimeout,空闲连接可能堆积。db实例默认共享全局连接池,First()返回后连接即归还,但后续访问user.Orders(惰性加载)会重新 Acquire 连接——若未配db.Session(&gorm.Session{NewDB: true}),易引发竞争。
连接状态对比表
| 场景 | 连接获取方式 | 是否自动释放 | 风险等级 |
|---|---|---|---|
db.First() |
从池中 Acquire | ✅ 归还池 | 低 |
db.Preload().First() |
复用同一连接执行多语句 | ⚠️ 仅最后归还 | 中 |
user.Orders(惰性) |
新 Acquire 连接 | ✅ 但不可控时机 | 高 |
修复路径示意
graph TD
A[调用 Preload] --> B{是否启用 Session?}
B -->|否| C[连接复用+延迟释放]
B -->|是| D[独立连接生命周期]
D --> E[WithContext ctx.Done()]
第四章:生产级连接健康度监控与自动化诊断体系
4.1 实时指标采集:sql.DB.Stats()关键字段语义解读与告警阈值设定
sql.DB.Stats() 返回 sql.DBStats 结构体,是观测 Go 应用数据库连接健康状态的核心接口:
stats := db.Stats()
fmt.Printf("Open connections: %d, InUse: %d, Idle: %d\n",
stats.OpenConnections, stats.InUse, stats.Idle)
逻辑分析:
OpenConnections = InUse + Idle,反映当前连接池总负载;InUse持续高位(如 >80% 连接池容量)可能预示查询阻塞或事务未及时提交。
常见关键字段语义与推荐告警阈值:
| 字段 | 含义 | 健康阈值 | 风险信号 |
|---|---|---|---|
WaitCount |
等待空闲连接的累计次数 | 突增表明连接池过小或慢查询积压 | |
MaxOpenConnections |
连接池上限 | 显式设置(如 50) | 为 0 表示无限制,生产环境严禁 |
WaitDuration |
累计等待耗时 | >5s/分钟需立即介入 |
连接泄漏检测逻辑
graph TD
A[每30秒调用db.Stats] --> B{WaitCount增量 > 50?}
B -->|Yes| C[触发告警并dump goroutines]
B -->|No| D[继续监控]
4.2 连接泄漏检测脚本开发:基于goroutine dump+stack trace的自动归因分析
核心思路
通过定期采集 runtime.Stack() 输出,结合正则匹配活跃 goroutine 中的 net.Conn、database/sql.(*DB) 等资源持有栈帧,定位未关闭连接的调用源头。
关键代码片段
func detectLeakedConns() []string {
var buf bytes.Buffer
runtime.Stack(&buf, true) // 获取所有 goroutine stack trace
lines := strings.Split(buf.String(), "\n")
var leaks []string
for i, line := range lines {
if strings.Contains(line, "net.(*conn).Read") ||
strings.Contains(line, "(*DB).queryConn") {
// 向上回溯3行获取调用方函数名
if i >= 3 {
caller := strings.TrimSpace(lines[i-3])
if strings.Contains(caller, "myapp/") {
leaks = append(leaks, caller)
}
}
}
}
return leaks
}
该函数触发全量 goroutine dump,扫描含网络/数据库读操作的栈帧,并提取其上游业务调用位置(如 myapp/service/user.go:127),实现调用链归因。
检测结果示例
| 泄漏位置 | 所属模块 | 发生频次 |
|---|---|---|
user.go:127 |
auth | 14 |
payment.go:89 |
billing | 5 |
自动化流程
graph TD
A[定时触发] --> B[goroutine dump]
B --> C[栈帧正则过滤]
C --> D[调用点归一化]
D --> E[聚合统计+告警]
4.3 Prometheus+Grafana可视化看板搭建:连接池水位、创建/关闭速率、等待队列深度联动监控
为实现数据库连接池健康态的多维可观测性,需在应用端暴露关键指标并构建联动视图。
指标采集配置(Prometheus)
# prometheus.yml 片段:抓取应用暴露的连接池指标
scrape_configs:
- job_name: 'db-pool'
static_configs:
- targets: ['app-service:8080']
该配置使Prometheus每15秒拉取/actuator/prometheus(Spring Boot Actuator)暴露的hikaricp_connections_active、hikaricp_connections_pending等原生指标。
关键指标语义对齐
| 指标名 | 含义 | 建议告警阈值 |
|---|---|---|
hikaricp_connections_active |
当前活跃连接数 | > 90% maximumPoolSize |
hikaricp_connections_pending |
等待获取连接的请求数 | > 5 持续30s |
rate(hikaricp_connections_created_total[1m]) |
每秒新建连接速率 | 突增200%触发分析 |
联动分析逻辑(Grafana变量与查询)
// 等待队列深度与活跃连接率热力图联动表达式
100 * avg_over_time(hikaricp_connections_active[5m])
/ on(instance) group_left max by(instance)(hikaricp_pool_maximum)
该表达式动态计算各实例连接池水位百分比,结合hikaricp_connections_pending时间序列,可定位“高水位+高排队”故障模式。
graph TD A[应用埋点] –> B[Prometheus拉取] B –> C[Grafana多Panel联动] C –> D[水位/速率/队列三轴联动着色]
4.4 熔断与降级预案:连接池饱和时的优雅退化策略与fallback SQL执行路径设计
当 HikariCP 连接池活跃连接达 maximumPoolSize 且等待超时(connection-timeout),常规 SQL 执行将阻塞或抛出 SQLTimeoutException。此时需触发熔断并启用降级路径。
降级决策边界
- 连接池使用率 ≥ 95% 持续 30s
- 平均获取连接耗时 > 500ms
- 连续 5 次获取失败
fallback SQL 设计原则
- 仅查询缓存/本地副本(如 Redis 中的聚合视图)
- 禁用 JOIN 与子查询,单表主键查询优先
- 返回精简字段(避免
SELECT *)
// 降级执行器示例
public OrderSummary fallbackGetOrderSummary(long orderId) {
// 从 Redis Hash 中快速读取预聚合数据
String key = "order:summary:" + orderId;
Map<String, String> fields = redis.hgetAll(key); // 非阻塞、毫秒级
return OrderSummary.fromMap(fields);
}
该逻辑绕过数据库直连,依赖预热缓存;hgetAll 响应时间稳定在 2–5ms,吞吐量达 50k QPS,满足 SLA 降级阈值(P99
降级路径状态流转
graph TD
A[正常SQL执行] -->|连接池饱和| B[触发熔断]
B --> C{是否启用fallback?}
C -->|是| D[执行fallback SQL]
C -->|否| E[返回503]
D --> F[记录降级日志+监控告警]
| 降级类型 | 触发条件 | 响应延迟 | 数据一致性 |
|---|---|---|---|
| 缓存读取 | Redis 存在有效key | 最终一致 | |
| 静态兜底 | 缓存缺失,返回默认模板 | 弱一致 |
第五章:从连接池到云原生数据访问层——演进趋势与架构升级建议
连接池的瓶颈在真实业务中持续暴露
某电商中台在大促期间遭遇数据库连接耗尽,监控显示 HikariCP 连接池活跃连接长期维持在 98% 以上,平均获取连接耗时从 2ms 飙升至 320ms。根因分析发现:微服务间调用链深度达 7 层,每层均独立维护连接池,且未配置 maxLifetime 与 connection-timeout 的协同策略,导致大量“僵尸连接”滞留于 NAT 网关超时窗口内。
数据访问中间件成为过渡关键枢纽
字节跳动开源的 ShardingSphere-JDBC 在支付核心系统中被改造为嵌入式数据访问代理层。其实际部署结构如下表所示:
| 组件 | 部署形态 | 关键能力 | 生产指标 |
|---|---|---|---|
| SQL 解析引擎 | 内存驻留 | 支持 PostgreSQL/MySQL 协议兼容解析 | QPS ≥ 12k,P99 |
| 分布式事务协调器 | Sidecar 模式 | Seata AT 模式 + 本地消息表兜底 | 跨库事务成功率 99.992% |
| 连接复用网关 | Envoy 扩展 Filter | 复用上游连接池,按租户标签路由 | 连接数下降 63%,内存占用降低 41% |
云原生数据访问层的典型拓扑
采用 eBPF 技术实现内核态连接复用的 DataPlane v2 架构已在某银行风控平台上线。其核心流程通过 Mermaid 可视化呈现:
flowchart LR
A[Service Pod] -->|HTTP/1.1| B[Envoy Proxy]
B -->|eBPF socket redirect| C[DataPlane Agent]
C -->|Multiplexed TLS 1.3| D[(Shared Connection Pool)]
D --> E[MySQL Primary]
D --> F[PostgreSQL Replica]
C -->|Async metrics push| G[OpenTelemetry Collector]
迁移路径必须匹配组织成熟度
某 SaaS 厂商分三阶段实施升级:第一阶段将 Druid 替换为 PgBouncer + Connection Pooler Sidecar(K8s Init Container 启动),第二阶段接入 Vitess 的 Query Planner 替代 MyBatis 动态 SQL,第三阶段基于 WASM 编译数据访问策略,使租户级 SQL 熔断规则可热更新无需重启。实测表明,第三阶段上线后,单集群支撑租户数从 1200 提升至 4700,而数据库 CPU 利用率反降 22%。
安全边界需重构而非平移
原连接池配置中的 allowPublicKeyRetrieval=true 参数在云原生层被强制禁用,取而代之的是 SPIFFE 证书自动轮转机制。所有数据访问请求必须携带经过 Istio mTLS 验证的 spiffe://cluster.local/ns/default/sa/payment-svc 标识,数据库端通过 PAM 插件校验该标识并映射至最小权限 DB Role。
监控维度必须穿透协议栈
在 Prometheus 中新增以下自定义指标:
data_access_layer_connection_multiplex_ratio(复用比,当前值 1:8.3)sql_parse_cache_hit_rate(SQL 解析缓存命中率,P95 ≥ 99.2%)tenant_isolation_violation_total(租户隔离违规事件,SLO 要求 0)
某次灰度发布中,该指标突增触发告警,定位出新版本 SDK 未正确传递租户上下文字段,3 分钟内完成回滚。
开发体验需同步进化
团队将 JPA Repository 接口通过 Annotation Processor 自动生成 gRPC 接口定义,配合 Protobuf Schema Registry 实现强类型数据契约。开发者仅需编写 @CloudNativeRepository("order") public interface OrderRepo extends CrudRepository<Order, Long>,即可获得带租户透传、自动重试、分布式追踪注入的客户端实例。
测试策略必须覆盖协议语义层
引入 Chaos Mesh 注入网络分区故障,验证数据访问层在 tcp-reset 场景下的连接恢复能力;使用 SQLSmith 生成模糊 SQL 测试查询计划缓存污染防护;针对跨 AZ 部署场景,模拟 120ms RTT 网络延迟,验证连接预热机制是否在 3 秒内建立有效连接池。
