HikariCP Connection Adder 被驱动 Socket Read 卡死:故障复盘与排查手册

先看一段栈,再决定要不要继续读——
# 这两份 stack dump 间隔 14 分钟抓的,"jdbc:mysql://10.14.127.31:19030/xxx"
# 的 connection adder 线程,tid 完全一致,栈帧一帧没动。
# 业务侧呢?告警群里全是 SQLTransientConnectionException。说真的,这种栈谁都不想看到——10 个 HikariCP 连接池、每个池的 adder 都卡在 socketRead0,不是忙,是死等。更扎心的是,业务方第一时间甩锅 HikariCP"卡死了",但 HikariCP 的 MAIN 池好好的;另一边 DBA 看到 MySQL 进程没崩、SHOW PROCESSLIST 也查不出明显异常。两边都没事,问题到底出在哪?
这篇文章就是讲这次排查过程的:怎么从两份 dump 锁定到驱动层握手 read 死等,怎么排除掉一堆看似合理实则救不了命的客户端调参,以及 HikariCP 4.x 的 async-init 这次为什么没救成。文末我会留一份"如果你也在用 5.1.47,请按这个 checklist 立刻自检"的清单,能直接拿走。
如果你的项目里还在跑 mysql-connector-java < 5.1.49,强烈建议你先跳到 §6 的 Q&A 把 Q7 读完——它会告诉你为什么"加 socketTimeout"这个直觉做法在这次故障里完全没用。
0. 一页纸结论
- 现象:应用 10 个 HikariCP 连接池同时失效,业务侧持续报
SQLTransientConnectionException: Connection is not available, request timed out。 - 直接卡点:每个池的
connection adder线程均卡在socketRead0,两份 jstack 间隔 14 分钟、同一组 tid、同一帧栈。 - 根因:mysql-connector-java 5.1.47 在新建连接握手阶段的 socket read 永不返回;叠加 HikariCP 单 adder 同步执行
newConnection()的架构,被服务端瞬时异常扇出成大故障。 - 修复:升级驱动到 5.1.49 或 8.x;HikariCP 升 4.x 已做
async-init,但本故障死等发生在 init-async 切出前的握手 read 阶段,必须配合驱动升级才能根治。 - 预防:驱动层 socket timeout、服务端可用性、应用层 watchdog 三层联动;任意一层单独加固都无法覆盖该故障。
1. 故障现场
1.1 时间线
| 时间 | 事件 |
|---|---|
| T0 | 10 个业务连接池(g_caiwu_stream / soda_*_stream / …)陆续报 Connection is not available, request timed out after 30000ms |
| T0+7min | 第一次 jstack(stack.log),10 个 adder 全部卡死 |
| T0+21min | 第二次 jstack(stack1.log),与第一次同一组 tid、同一帧栈 |
| T0+30min | 重启客户端服务,恢复 |
1.2 影响面
- 受影响池:10 个(同一 MySQL 端点
10.14.127.31:19030) - 未受影响池:
MAIN_HikariCP——说明问题在业务数据源侧,不在 HikariCP 框架本身
1.3 关键 stack(pool-A)
"jdbc:mysql://10.14.127.31:19030/pool-A?... connection adder" #7952751 daemon
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3011)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3472)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3462)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3905)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2491)
at com.mysql.jdbc.ConnectionImpl.loadServerVariables(ConnectionImpl.java:3797) ← 卡点
at com.mysql.jdbc.ConnectionImpl.initializePropsFromServer(ConnectionImpl.java:3230)
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2243)
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2025)
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:778)
at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47)
... 反射构造 ...
at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:330)
at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138)
at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:364)
at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206)
at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:476)
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:726)少数 pool 走的是另一条握手路径 setupServerForTruncationChecks(被 jdbcCompliantTruncation=true 触发),栈底完全一致。
1.4 两次 dump 的交叉验证
两份 stack dump 间隔 14 分钟,10 个卡住的线程 tid / nid / 栈帧完全一致。这是判断"阻塞"而非"繁忙"的关键证据:
RUNNABLE+socketRead0不代表正在执行,仅说明 OS 调度层面可运行。- 14 分钟栈帧未移动、且多个池同步发生——可以判定为阻塞在 syscall。
2. 根因分析
2.1 三层归因
┌─────────────────────────────────────────────────────────────────┐
│ 应用层:HikariCP connection adder 被驱动同步阻塞 │
├─────────────────────────────────────────────────────────────────┤
│ 驱动层:mysql-connector-java 5.1.47 握手阶段 socket read 无超时 │
├─────────────────────────────────────────────────────────────────┤
│ 服务端:MySQL 在握手阶段对新建连接不响应(或响应极慢) │
└─────────────────────────────────────────────────────────────────┘2.2 为什么是 mysql-connector-java 5.1.47
握手阶段死等的调用栈:
MysqlIO.readFully
→ ReadAheadInputStream.fill
→ SocketInputStream.read
→ socketRead0 (native, 阻塞)| 参数 | 在握手阶段是否生效(5.1.47) | 原因 |
|---|---|---|
JDBC URL connectTimeout | 生效 | 但卡点不在 TCP 三次握手 |
JDBC URL socketTimeout | 不生效 | 5.1.x 握手阶段走 MysqlIO.readFully → ReadAheadInputStream.fill,该路径不传递 setSoTimeout |
Statement.setQueryTimeout | 无效 | 连接尚未建立 |
tcpKeepAlive=true | 默认关闭,探测周期过长 | 5.1.49 之前默认 false;Linux 默认 tcp_keepalive_time=7200 |
只有升级到 5.1.49 或 8.x(com.mysql.cj.*)才能修复该问题——8.x 重写了握手代码路径,socketTimeout 才能真正打断该 read。
2.3 为什么 HikariCP 4.0.3 也无法解决
| HikariCP 配置 | 是否能中断握手 read | 原因 |
|---|---|---|
connectionTimeout | 否 | 控制业务 getConnection() 的等待时长,不涉及驱动建连过程 |
validationTimeout | 否 | 仅用于探测已有连接 |
keepaliveTime / maxLifetime | 否 | 用于驱逐存量连接 |
initializationFailTimeout | 否 | 仅在启动期生效 |
4.x async-init | 部分缓解 | 仅将"握手后"逻辑切出主线程,握手 read 仍在 newConnection() 内,adder 仍同步阻塞 |
关键结论:任何 Java 层超时都无法中断 native socketRead0——线程阻塞在 syscall,Java 层没有机会检查中断标志。
2.4 为什么 10 个池同时失效
这 10 个池全部指向同一 MySQL 端点 10.14.127.31:19030。故障链大致是这样的:
- 池里现连接被服务端踢除(例如
wait_timeout过期、KILL 连接),或 HikariCP 因为maxLifetime主动驱逐。 - 池瞬间需要批量重建连接,多个池的 adder 同时向 MySQL 发起握手。
- 某个服务端诱因在同一时刻被踩中——
max_connections打满、主从切换、VIP 漂移、磁盘 IO 抖动等,导致新握手在服务端被排队或丢包。 - 所有 adder 上的握手 read 全部死等,10 个池同步卡死。
服务端常见诱因(按出现概率从高到低):
max_connections打满:新握手被排队,客户端 read 死等。- DNS 反向解析慢:
gethostbyaddr使服务端卡在 login 阶段。 - 主从切换 / VIP 漂移:包被发往旧主后丢弃,客户端 read 永不 ACK。
- 磁盘 IO 抖动:MySQL 握手线程被 IO 调度阻塞。
- LB / 防火墙会话表过期:握手包过去、回包被丢弃。
2.5 与 HikariCP#2161 的关系
| 维度 | #2161 | 本次故障 |
|---|---|---|
| 根因 | 同类:adder 卡驱动握手 socket read | 同类 |
| 驱动 | Oracle 18.3.0.0 | MySQL Connector/J 5.1.47 |
| 阻塞阶段 | Oracle T4C logon | MySQL loadServerVariables / setupServerForTruncationChecks |
| HikariCP 能否规避 | 3.4.5 不能 | 4.0.3 部分缓解,仍阻塞 |
| 临时止血手段 | oracle.jdbc.ReadTimeout(实测无效) | socketTimeout(同样无效) |
| 终结方案 | 升级 HikariCP / 驱动 | 升级驱动到 5.1.49 / 8.x + 排查服务端 |
3. 排查手册
故障定位的核心思路就一句话:先排除"是不是卡在驱动 socket read 上",再决定下一步是改客户端还是找 DBA。下面这一节是按这个思路写的可复用 SOP。
3.1 一分钟识别
判断"HikariCP 连接池被打挂且是 adder 卡驱动"的三项特征:
- stack dump 中
XxxDataSource connection adder线程处于RUNNABLE,但栈底位于socketRead0/read0 - 多次 dump(间隔 ≥5 分钟)这些 adder 的 tid、栈帧完全一致
- HikariPool housekeeper 报
Pool stats (total=0, active=0, idle=0, waiting=N)并伴有Add connection elided日志
任一满足即可按下文继续排查。
3.2 信息收集 checklist
应用侧(RD / 中间件):
- 依赖版本:HikariCP / mysql-connector-java / JDK
- HikariCP 配置:
maximumPoolSize/minimumIdle/connectionTimeout/validationTimeout/keepaliveTime/maxLifetime/initializationFailTimeout - JDBC URL 完整参数(重点关注
useServerPrepStmts/cachePrepStmts/jdbcCompliantTruncation/connectTimeout/socketTimeout/tcpKeepAlive) - 故障窗口两次 jstack,间隔 ≥10 分钟(用于对比 tid / 栈帧是否一致)
- HikariPoolMXBean 的 active / idle / waiting / total 时间线
SQLTransientConnectionException首次出现的时间戳
DBA / 服务端侧:
- 故障 MySQL 的
max_connections/Threads_connected/Threads_running峰值 SHOW PROCESSLIST中是否存在unauthenticated user | login堆积- MySQL error log:是否存在连接拒绝、OOM、KILL 事件
- 主从状态:是否存在切换、relay log 损坏
- 磁盘 IO:故障窗口的
iostat/iotop - 网络:是否存在 VIP 漂移、交换机异常、防火墙事件
skip-name-resolve是否已开启
3.3 三步定位法
Step 1. 客户端抓包 — 30 秒内可区分服务端与驱动的责任边界
tcpdump -i any -nn -s 0 host 10.14.127.31 and port 19030 -w /tmp/dump.pcap配合 Wireshark 查看握手 read 是否有回包:
- 有回包 → 驱动未正确解析(升级驱动)
- 无回包 → 服务端未响应(继续 Step 2)
Step 2. 服务端确认 — 由 DBA 执行
-- 快速看连接是否被打满、是否有未完成握手
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';
SHOW PROCESSLIST; -- 重点观察是否存在 unauthenticated | login 堆积
SHOW VARIABLES LIKE 'skip_name_resolve'; -- 未开启 = 握手阶段会做 DNS 反查,慢/挂会拖死连接
-- 故障窗口的 SQL 聚合(需 performance_schema 开启;<故障窗口> 替换为实际起止时间戳)
SELECT *
FROM performance_schema.events_statements_summary_by_digest
WHERE first_seen BETWEEN '2026-08-11 20:30:00' AND '2026-08-11 21:30:00'
ORDER BY count_star DESC
LIMIT 20;Step 3. 应用层止血 — 不依赖根因修复
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
public void evictStuckPool(HikariDataSource ds, String reason) {
HikariPoolMXBean mx = ds.getHikariPoolMXBean();
// 仅在池已失效时驱逐,避免误伤健康池
if (mx.getTotalConnections() == 0
&& mx.getIdleConnections() == 0
&& mx.getThreadsAwaitingConnection() > 0) {
mx.softEvictConnections();
log.warn("Evicted stuck Hikari pool '{}', reason={}", ds.getPoolName(), reason);
}
}注意:若服务端仍无响应,重连后会再次死等——本步骤只能争取时间,不能替代根因修复。
3.4 修复方案
方案 A:升级驱动(推荐,根治)
<!-- 旧:mysql-connector-java 5.1.47 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version> <!-- 修复握手阶段 socketTimeout 失效 -->
</dependency>
<!-- 更推荐:迁移至 8.x,包名 com.mysql.cj.* -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
</dependency>若选择 5.1.49,JDBC URL 追加:
?connectTimeout=3000&socketTimeout=20000&tcpKeepAlive=true&tcpKeepAliveTime=30注意:5.1.49 仅支持 JDK 8 / 11;JDK 25 必须迁移至 8.x。
方案 B:HikariCP 调优(缓解,不能根治)
hikari:
connection-timeout: 5000 # 业务 getConnection 超时
validation-timeout: 2000 # housekeeper 探测
initialization-fail-timeout: 1 # 启动期快速失败
keepalive-time: 30000 # 30s 心跳
max-lifetime: 1800000 # 30min 驱逐
minimum-idle: 5 # 保持最低水位提醒:这些参数无法解决"新建连接握手死等",但能让失效的存量连接更快被发现。
方案 C:应用层 watchdog(兜底防线)
@Scheduled(fixedDelay = 30_000)
public void hikariWatchdog() {
hikariPools.forEach(pool -> {
var mx = pool.getHikariPoolMXBean();
if (mx.getTotalConnections() == 0
&& mx.getIdleConnections() == 0
&& mx.getThreadsAwaitingConnection() > 0) {
// 池已被打挂:驱逐卡死连接,触发重建
mx.softEvictConnections();
alert("Hikari pool " + pool.getPoolName() + " stuck, evicted");
}
});
}软驱逐会触发下一次 fill 提交新 FutureTask;前提是服务端已恢复。
方案 D:服务端修复(治本之策)
- 开启
skip-name-resolve - 调高
max_connections/back_log,并合理设置wait_timeout/interactive_timeout - 主从切换接入 ProxySQL / MHA 自动重试
- 关键池接入 VIP 健康检查与漂移告警
4. 经验沉淀
4.1 关于 HikariCP 的几个常见误解
- “
connectionTimeout能管所有阻塞”——不能。该参数无法触及驱动内部 socket read。 - “HikariCP 4.x 已修复该 bug”——部分。
async-init仅切出"握手后"逻辑,握手 read 仍阻塞 adder。 - “
socketTimeout调大就行”——5.1.47 握手阶段根本未应用该参数,必须升级驱动。 - “加
tcpKeepAlive=true即可”——默认探测周期 2 小时,故障窗口内无法生效。
4.2 选型建议
| 维度 | 建议 |
|---|---|
| JDK | 8 / 11 / 17 推荐;25 及以上需等待驱动官方认证 |
| HikariCP | ≥ 4.0.3 |
| MySQL 驱动 | ≥ 5.1.49 或 8.x;5.1.47 及更早必须升级 |
| 监控 | HikariPoolMXBean 全量指标(active / idle / total / waiting / connect / lastAcquire) |
4.3 排查 SOP(一页纸版)
SQLTransientConnectionException
│
├── 抓 stack dump(间隔 ≥10 分钟两次)
│ │
│ ├── adder 线程栈底在 socketRead0 + 两次栈一致
│ │ │
│ │ ├── 驱动版本 < 5.1.49 且数据库为 MySQL → 升级驱动
│ │ └── 抓包确认握手 read 是否有服务端回包
│ │ │
│ │ ├── 有回包 → 驱动 bug(升级驱动)
│ │ └── 无回包 → 服务端问题
│ │ │
│ │ ├── max_connections / Threads_connected
│ │ ├── skip-name-resolve
│ │ ├── 主从切换 / VIP 漂移
│ │ └── 磁盘 IO / 防火墙
│ │
│ └── adder 线程在等锁 / 等 FutureTask → 池满或业务并发高
│
└── 观察 HikariPoolMXBean:active / idle / total / waiting 时间线排查手册讲到这里,方法论已经齐了。下面两节是这次故障的原始证据——一份故障复盘如果只讲方法、不放原始材料,读者很难判断方法的可靠性。
5. 附录:原始 stack dump 关键片段
5.1 stack.log 摘录
2026-08-11 20:50:13
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):
"MAIN_HikariCP connection adder" #8230979 daemon ... waiting on condition
TIMED_WAITING (parking) - LinkedBlockingQueue.poll
"jdbc:mysql://10.14.127.31:19030/pool-A?... connection adder" #7952751 daemon ... runnable
at java.net.SocketInputStream.socketRead0(Native Method)
...
at com.mysql.jdbc.ConnectionImpl.loadServerVariables(ConnectionImpl.java:3797)
...
at com.zaxxer.hikari.pool.HikariPool$PoolEntryCreator.call(HikariPool.java:726)5.2 stack1.log 摘录
2026-08-11 21:04:39
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):
(同一组 tid #7952751/#7952621/#7952611/#7952602/#7952551/#7952367/
#7952361/#7952206/#7952202/#7952145,栈帧完全一致)5.3 应用依赖(故障时)
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>故障现场已经摆出来了,接下来是这次分享里我最想留下的部分——13 个常见的追问。这一节源自故障复盘后两次内部 tech review 的 QA,我尽量保留了当时被问到的原话和回答。
6. 问答预案
Q1:只加 socketTimeout=2000 能止血吗?
A:在 5.1.47 上基本无效。握手阶段 MysqlIO.readFully → ReadAheadInputStream.fill 走的是内部 buffered stream,驱动并未将 SO_RCVTIMEO 应用到底层 socket,read 无限阻塞。必须升级驱动。
Q2:HikariCP 4.x 不是已经 async-init 了吗?为什么仍卡住?
A:async-init 仅切出"握手后"的 connection-init-sql,握手 read 仍在 newConnection() 内同步执行,adder 仍被阻塞。4.x 无法覆盖该场景。
Q3:softEvictConnections() 能恢复吗?
A:能重启 adder,但前提是服务端已恢复。服务端无响应时驱逐后重连仍会死等。
Q4:能不能直接迁移至 8.x?
A:可以,但需注意:
- 包名
com.mysql.jdbc.*→com.mysql.cj.* - 默认时区 / SSL 行为有变化
- 部分历史参数不再支持(如
useOldUTF8Behavior) - 强烈建议灰度验证
Q5:能否不升级驱动,仅让 DBA 修复服务端?
A:可以,但客户端脆弱性依然存在——下次服务端再抖动还会卡住。建议客户端与服务端协同修复。
Q6:能否用 Druid 等替代 HikariCP?
A:Druid 等主流连接池的 createConnection 同样同步等待驱动返回,根因同样存在。换连接池无法消除驱动握手 read 死等,反而会失去 HikariCP 的监控能力。
Q7:5.1.47 为什么无法通过 socketTimeout 解决 read 超时?
A:5.1.47 握手 read 链路为 MysqlIO.readFully → ReadAheadInputStream.read → ReadAheadInputStream.fill → underlyingInputStream.read → Socket.getInputStream().read → socketRead0。Socket 层 SO_RCVTIMEO 理论上可中断 read,但存在三个致命阻断:
- JDBC URL 上的
socketTimeout在连接建立后才生效,应用层无法在握手 read 之前调用setSoTimeout。 - 握手阶段的内部
StatementImpl.executeQuery走 internal statement,无queryTimeout路径,驱动也不会临时切换SO_RCVTIMEO。 ReadAheadInputStream.readFromUnderlyingStreamIfNecessary在n=0时无条件重试,不抛出任何超时、中断异常,阻塞无上限。
简言之:驱动在握手 read 前并未调用 setSoTimeout,即便 socket 支持该能力也无法触发。connectTimeout 控制的是 TCP 三次握手,不影响握手 SQL 的读包;tcpKeepAlive 默认关闭;Statement.setQueryTimeout 不覆盖驱动内部握手 read。
5.1.49 之后才在握手 read 前后显式保存并恢复 socket 超时,并以 connectTimeout 作为握手 read 的兜底超时;8.x 重写协议层,所有 read 统一受 socketTimeout 管控。
Q8:5.1.49 相对 5.1.47 改了什么?
A:5.1.49 是 5.1 分支的最后一个 GA 版本(2022-07-25 发布,2023-10-17 进入 EOL,2024-04-30 起全面退役),累积了一批与本次故障直接相关的修复:
- 握手阶段 read 套用
connectTimeout作为兜底超时——握手 SQL(loadServerVariables、setupServerForTruncationChecks)的 read 会被中断,抛出CommunicationsException,adder 释放,池不再被打挂。 tcpKeepAlive默认开启,并新增tcpKeepAliveTime/tcpKeepAliveInterval/tcpKeepAliveProbes参数,可控内核 keepalive 探测间隔(不再依赖系统默认 7200 秒)。- 握手失败路径主动关闭 socket——避免 5.1.47 时代"半死 fd"残留在池内。
- 失败时诊断信息更详细——
CommunicationsException.getMessage()输出connectTimeout/socketTimeout/ 远端地址等。 - TLS 1.3 支持、MySQL 8.0
caching_sha2_password兼容性改善、utf8mb4_0900_ci支持完善。
但 5.1.49 救不了"5.1.x 系列对 JDK 17 / 21 / 25 缺乏官方认证"这一根本问题。JDK 25 必须直接迁移至 8.x。
Q9:JDK 25 升 5.1.49 行不行?为什么必须迁 8.x?
A:5.1.x 最后一个版本 5.1.53 发布时 JDK 25 尚未出现,Socket 层在 JDK 17+ 的 NIO 行为变化(NioSocketImpl、EPollArrayWrapper 优化等)它并未适配。本次故障正是这一未认证矩阵的直接后果。8.x(com.mysql.cj.*)对 JDK 17+ 提供完整官方认证,HikariCP 4.x async-init + 8.x 协议层重写叠加,才构成 JDK 25 的稳态组合。
Q10:迁 8.x 后,jdbcCompliantTruncation=true 这类"握手阶段触发 SQL"的参数还会踩坑吗?
A:8.x 将握手阶段的 SQL 探测设计为可配置、可跳过。jdbcCompliantTruncation=true 在 8.x 下走异步协议层 read,受 socketTimeout 严格管控。即便服务端异常,socketTimeout 中断后会抛出 SQLException,adder 释放,不会再出现 5.1.47 时代的无限阻塞。
此外,8.x 内部将 loadServerVariables 这类握手探测改为懒加载:仅在真正使用变量时才查询,进一步收窄了握手 read 的窗口。
Q11:为什么 HikariCP 4.x 的 async-init 没有修复该问题?
A:async-init 切出的是"握手后"的 connection-init-sql / setNetworkTimeout 等应用配置层逻辑,目的是避免 adder 被应用侧 init 脚本拖慢。但握手 read 仍发生在 Driver.connect 内部——即 newConnection() 的同步路径。4.x 将 init 切出后,adder 仍须等待 Driver.connect 返回才能进入下一步。驱动卡在握手 read 时,adder 永远等不到 Driver.connect 返回,因此 async-init 无法解决该问题。
驱动握手死等的真正解决方案是 5.1.49 的 connectTimeout 兜底配合 8.x 的协议层重写。
Q12:能否使用 Druid 等连接池绕开 HikariCP 的 adder 模型?
A:不能。Druid / Tomcat JDBC Pool / DBCP / Vibur 等所有主流连接池的"新建连接"均同步等待驱动返回。驱动卡在握手 read 上时,任何连接池的对应线程都会同步卡住。Druid 还自带心跳与后台校验逻辑,可能让故障更复杂。
换连接池无法消除驱动层根因。唯一能让池幸免的方式,是驱动自身能在 socketTimeout 内打断 read。
Q13:服务端侧可以做哪些工作?
A:服务端修复是治本之策,与客户端升级同等重要。建议 DBA 同步处理:
skip-name-resolve=ON:避免握手阶段 DNS 反查拖慢连接max_connections/back_log留足 bufferwait_timeout/interactive_timeout与客户端 keepalive 探测周期对齐- 主从切换 / VIP 漂移时通过 ProxySQL / MHA 自动重试
- 关键池接入 VIP 健康检查与漂移告警
- 监控
unauthenticated user | login状态的连接堆积
7. 参考资料
- HikariCP#2161: Available connections in HikariPool occasionally drop to zero and not being released
- HikariCP 4.0 release notes: async-init-sql
- mysql-connector-java 5.1.49 changelog:
socketTimeout/tcpKeepAlive修复 - MySQL 8.0 JDBC driver: connection handshake 重写
8. 下一步该做什么
如果你的项目仍在使用 mysql-connector-java < 5.1.49,请把以下清单贴到下一个迭代里,按顺序执行:
- 拉一份依赖清单,确认驱动版本;5.1.49 或 8.x 以下的,立即规划升级
- JDK ≥ 17(含你这次在用的 JDK 25)的项目,直接迁移至
com.mysql.cj.*8.x;JDK 8 / 11 可临时升 5.1.49 作为过渡 - JDBC URL 追加
connectTimeout=3000&socketTimeout=20000&tcpKeepAlive=true - 上线 HikariPoolMXBean 全量指标(active / idle / total / waiting),告警阈值按 §3.1 的三项特征设置
如果已经在升级或排障过程中,欢迎把这次的 stack dump、JDBC URL、MySQL SHOW PROCESSLIST 输出贴到评论区,我帮你看看。
相关内容
如果你觉得这篇文章对你有所帮助,请我一杯咖啡吧~
微信支付
支付宝