一、执行摘要
| 维度 | 详情 |
|---|---|
| 问题发生时间 | 2026-07-27 09:05 ~ 09:50 |
| 核心现象 | 大部分接口响应超时/不可用,租户侧发药、HIS 同步、查询类接口大量失败 |
| 直接异常 | Lock wait timeout exceeded(314 次)、MyBatisSystemException(大量) |
| 主要受影响表 | ihm_order_prescription |
| 根因定位 | 直接原因:核心服务 HikariCP 连接池容量过小(默认仅 10),早高峰并发请求迅速耗尽连接,引发级联超时与服务雪崩 。深层诱因:V1 版本 syncOrderDeliveryInfoByDelivery 在 @Transactional 事务内同步调用顺丰医寄通 API和其他业务处理(handleDelivered,约 20s),使单连接被长时间占用并持有 ihm_order_prescription 行锁,显著放大了小连接池的脆弱性。syncOrderDeliveryInfoV2 已将物流下单移出事务与锁,仅单医院试点,非本次事故主因 |
| 关键量化 | MySQL max_connections=4190,当前 Threads_connected=387;而按现有 12 服务 × 2 实例、多数服务仅 10 连接估算,全集群最大连接数仅约 160~560,远低于 MySQL 容量,连接池是瓶颈而非数据库 |
| MySQL 侧状态 | Threads_connected=387,max_connections=4190,MySQL 总体连接数未耗尽,但单服务连接池已打满 |
二、问题现象详细描述


2.1 业务侧表现
- 09:05 起,租户回调、HIS 处方同步、发药、查询等接口开始出现大面积超时。
- 以 V1 入口 TenantCallbackServiceImpl.java#L147(file:///e:/javaProject/chinaunicom-medical-ihm/chinaunicom-medical-ihm-business/chinaunicom-medical-ihm-portal/src/main/java/com/chinaunicom/medical/ihm/service/impl/TenantCallbackServiceImpl.java#L147)
orderApi.syncOrderDeliveryInfo(dto).getData()为例:该接口在 Patient 服务由OrderServiceImpl.syncOrderDeliveryInfoByDelivery以@Transactional大事务执行。 - V1 与 V2 的核心差异在于顺丰 API 调用是否在事务内 :
- V1:
OrderServiceImpl.syncOrderDeliveryInfoByDelivery方法加@Transactional(rollbackFor = Throwable.class),在事务内同步调用rpDeliveryService.handleDelivered(logisticsCompanyEnum, rpDeliveryData)(顺丰医寄通下单,约 20s),随后才更新ihm_order_prescription、ihm_order、his_order等表。整个过程中数据库连接与行锁被持续占用。 - V2:
SyncDeliveryInfoV2ServiceImpl.syncByDelivery先把顺丰 API 调用放在事务与 Redisson 锁之外 执行,仅将处方、订单、his_order 的更新通过TransactionTemplate包裹为短事务;锁也只覆盖 DB 写入段。顺丰 API 仍需同步调用,因为 HIS 需要实时获取返回的物流单号、备注等信息,不能异步化。
- V1:
syncOrderDeliveryInfoV2仅对单一试点医院放开,早晨仅有几笔订单,其他医院全部走 V1 入口,因此 V2 不是本次事故主因。- 多服务接口返回
网络繁忙,请稍候重试、系统异常等错误。
2.2 技术侧表现
1、连接池层(最直接) :核心服务(patient/portal/operate 等)使用 HikariCP 默认最大连接数 10 【仅hospital、mpc单独增加了连接数】,早高峰并发下迅速耗尽;新请求获取连接等待 5 分钟后失败,是服务大面积不可用的直接原因。



2、应用层 :大量 org.mybatis.spring.MyBatisSystemException: null,说明 MyBatis 无法获取 JDBC 连接。
3、MySQL 层 :大量 Lock wait timeout exceeded; try restarting transaction,集中在 ihm_order_prescription 表;这是长事务持有行锁后的次生现象,进一步加剧连接池耗尽。
三、日志分析关键发现
3.1 Lock wait timeout 日志分析
日志文件 :e:\javaProject\chinaunicom-medical-ihm\doc\Lock wait timeout日志.txt
3.1.1 发生频率
| 时间段 | 次数 |
|---|---|
| 09:15 | 57 |
| 09:16 | 51 |
| 09:17 | 38 |
| 09:18 | 38 |
| 09:19 | 50 |
| 09:20 | 55 |
| 09:21 | 24 |
| 合计 | 314 |
314 次锁等待超时集中爆发在 09:15 ~ 09:21,与系统整体超时时段完全吻合。
3.1.2 涉及的数据库操作
| SQL 模式 | 次数 | 占比 | 说明 |
|---|---|---|---|
SELECT ... FROM ihm_order_prescription WHERE deleted=0 AND order_seq = ? |
243 | 77.4% | 按订单号查询处方全字段 |
其他 ihm_order_prescription 查询 |
47 | 15.0% | 含 apply_order_id 等条件 |
| 未知/上下文不完整 | 21 | 6.7% | --- |
| 合计 | 314 | 100% | --- |
关键结论 :所有锁等待全部集中在
ihm_order_prescription表,说明该表存在严重的并发访问冲突。
3.1.3 典型日志片段
typescript
2026-07-27 09:21:40.615 [patient,...] [http-nio-8883-exec-24] - [ERROR]
[c.c.m.i.service.impl.ConsultationRecordServiceImpl : 1100] -
org.springframework.dao.CannotAcquireLockException:
### Error querying database.
Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException:
Lock wait timeout exceeded; try restarting transaction
### The error may exist in com/chinaunicom/medical/ihm/mapper/OrderPrescriptionMapper.java
### SQL: SELECT id,org_code,rp_seq,... FROM ihm_order_prescription
WHERE deleted=0 AND (order_seq = ?)
分析:
- 即使是普通
SELECT也出现了Lock wait timeout,说明该查询在事务中执行,且存在其他事务长时间持有ihm_order_prescription相关行的锁。 - 结合
innodb_lock_wait_timeout默认 50s,意味着大量线程被阻塞近 50 秒后才报错,严重占用 Tomcat 线程和 Hikari 连接。
3.2 HikariPool/连接池相关日志分析
日志文件 :e:\javaProject\chinaunicom-medical-ihm\doc\0727系统超时问题-怀疑是HikariPool连接池被打满了.txt
3.2.1 异常类型分布
| 异常类型 | 特征 | 时间段 |
|---|---|---|
org.mybatis.spring.MyBatisSystemException: null |
获取 JDBC 连接失败 | 09:05 ~ 09:23 |
3.2.2 时间分布
| 时间段 | MyBatisSystemException 次数 |
|---|---|
| 09:05 | 13 |
| 09:06 | 12 |
| 09:07 | 7 |
| 09:09 | 12 |
| 09:10 | 12 |
| 09:11 | 24 |
| 09:12 | 13 |
| 09:13 | 20 |
| 09:14 | 13 |
| 09:15 | 31 |
| 09:16 | 31 |
| 09:17 | 31 |
| 09:18 | 34 |
| 09:19 | 7 |
| 09:22 | 11 |
| 09:23 | 23 |
异常从 09:05 开始持续,09:15-09:18 达到峰值,与 Lock wait timeout 趋势一致。
3.2.3 典型日志片段
typescript
2026-07-27 09:23:51.909 [portal,...] [http-nio-8881-exec-11] - [WARN]
[c.c.m.i.s.impl.SystemInteractionLogServiceImpl : 211] - 错误参数:
org.mybatis.spring.MyBatisSystemException
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(...)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)
at jdk.proxy2/jdk.proxy2.$Proxy142.insert(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(...)
...
分析:
-
异常消息为
null,说明底层异常(如Connection is not available, request timed out after 300000ms)在全局异常处理中被吞掉,仅保留了外层 MyBatisSystemException。经过详细排查,最终找到了连接池问题的关键证据!!
typescript
message: ("Connection pool exhausted" OR "Cannot get connection" OR "too many connections" OR "Could not get JDBC Connection" OR "unable to acquire" OR "HikariPool" OR "Druid")

- 但从堆栈可确认:所有异常均发生在 MyBatis 尝试从 HikariCP 获取连接/执行 SQL 时。
- 保存系统交互日志、获取预问诊信息等非核心业务接口也频繁报错,说明连接池问题已扩散到全服务。
3.3 MySQL 连接数量信息分析
数据来源 :e:\javaProject\chinaunicom-medical-ihm\doc\生产mysql连接数量信息.txt
sql
-- 查看当前所有连接
SHOW FULL PROCESSLIST;
-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';
-- 查看最大连接数限制
SHOW VARIABLES LIKE 'max_connections';
-- 按用户/主机分组统计连接数
SELECT user, host, COUNT(*) AS conn_count
FROM information_schema.processlist
GROUP BY user, host;
-- 查看portal写入的表是否有锁等待
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started ASC;
-- 查看是否有慢查询
SELECT id, user, host, db, command, time, state, info
FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 2
ORDER BY time DESC;

| 指标 | 数值 | 说明 |
|---|---|---|
Threads_connected |
387 | 当前已连接会话数 |
max_connections |
4190 | MySQL 最大允许连接数 |
| 连接使用率 | 9.2% | 387 / 4190 |
按主机统计的连接分布:
| 主机 | 连接数 |
|---|---|
| 132.83.xx.91 | 3 |
| 132.83.xx.160 | 3 |
| 132.83.xx.133 | 3 |
| 其他 | 378 |
关键发现:
- MySQL 实例层面连接数远未耗尽(仅使用 9.2%)。
- 这说明问题不在于 MySQL 总连接数不足,而在于单个 Java 服务的 HikariCP 连接池容量太小,无法充分利用 MySQL 的连接能力。
3.4 Nacos 配置分析
配置目录 :e:\javaProject\chinaunicom-medical-ihm\doc\nacos_config_ihm\ihm\
| 配置文件 | 是否配置 hikari | maximum-pool-size | 备注 |
|---|---|---|---|
common-mysql.yml |
YES | default(10) | 仅配置 connection-init-sql |
hospital.yml |
YES | 50 | 唯一显式配置较大的服务 |
mpc-server.yml |
YES | 50 | 唯一显式配置较大的服务 |
patient.yml |
NO | default(10) | 核心服务,使用默认 |
portal.yml |
NO | default(10) | 核心服务,使用默认 |
operate.yml |
NO | default(10) | 核心服务,使用默认 |
baseservice.yml |
NO | default(10) | 基础服务 |
dict.yml |
NO | default(10) | 字典服务 |
orders.yml |
NO | default(10) | 订单服务 |
his.yml |
NO | default(10) | HIS 服务 |
consumer.yml |
NO | default(10) | 消费者服务 |
关键配置片段(hospital.yml):
yaml
spring:
datasource:
hikari:
connection-init-sql: set names utf8mb4
minimum-idle: 25
idle-timeout: 18000
maximum-pool-size: 50 # 仅 hospital/mpc 显式配置
auto-commit: true
pool-name: hospital-db
max-lifetime: 28800000
connection-timeout: 300000 # 5 分钟
common-mysql.yml:
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://dbmaster:3306/...
hikari:
connection-init-sql: set names utf8mb4
# 未配置 maximum-pool-size,默认 10
关键发现:
- 只有
hospital和mpc-server两个服务显式配置了maximum-pool-size=50。- 核心高并发服务
patient、portal、operate等全部使用 HikariCP 默认最大连接数 10。connection-timeout=300000(5 分钟)设置过长,意味着当连接池耗尽时,线程会阻塞长达 5 分钟才失败,进一步加剧连接池耗尽。
3.5 连接池容量与并发能力测算
3.5.1 全集群连接池理论容量
按生产部署 12 个服务 × 2 实例( hospital/hospital_hd、patient/patient_hd、portal/portal_hd、operate/operate_hd、mpc/mpc_hd、baseservice/baseservice_hd ),结合 Nacos 配置估算:
| 服务 | 实例数 | 单实例 maximum-pool-size | 该服务总连接 |
|---|---|---|---|
| hospital + hospital_hd | 2 + 2 | 50 | ~200 |
| mpc + mpc_hd | 2 + 2 | 50 | ~200 |
| patient + patient_hd | 2 + 2 | 10 (default) | 40 |
| portal + portal_hd | 2 + 2 | 10 (default) | 40 |
| operate + operate_hd | 2 + 2 | 10 (default) | 40 |
| baseservice + baseservice_hd | 2 + 2 | 10 (default) | 40 |
| 合计(理论峰值) | --- | --- | ~560 |
注:即使按最乐观估算,全集群理论峰值也仅约 560 个连接 ,仅占 MySQL
max_connections=4190的 13.4% ;而事发时Threads_connected=387,说明 MySQL 整体连接数远未耗尽,瓶颈在应用层单服务连接池。
3.5.2 单服务并发能力估算
以 V1 syncOrderDeliveryInfoByDelivery 为例,假设顺丰 API 调用 + 事务内 DB 操作平均占用连接 20 秒:
| 单服务连接池大小 | 理论最大并发处理数(20s/请求) | 每分钟可处理请求数 |
|---|---|---|
| 10 | 10 | 30 |
| 50 | 50 | 150 |
| 100 | 100 | 300 |
关键结论:
- 当前 patient/portal/operate 等核心服务仅 10 个连接 ,在 20s 长事务下每分钟只能处理约 30 笔 发药/物流同步请求,早高峰极易被打满。
- 若将连接池扩大到 50 ,同等条件下并发处理能力可提升 5 倍,能充分利用 MySQL 剩余的连接容量。
- 若进一步将 V1 大事务改造为 V2 短事务(连接占用降至 1~2 秒),同样 50 连接可支撑的吞吐量还能再提升一个数量级。
四、根因定位过程
4.1 事故链路还原
typescript
09:05 早间业务高峰开始
│
▼
09:15 大量并发请求命中 patient/portal/operate 服务
│
├─ 绝大多数医院走 V1 入口 TenantCallbackServiceImpl#L147
│ └─ OrderServiceImpl.syncOrderDeliveryInfoByDelivery 使用 @Transactional 大事务
│ ├─ 事务内同步调用外部顺丰医寄通 API(handleDelivered,~20s)
│ ├─ 事务内更新 ihm_order_prescription、ihm_order、his_order 等表
│ └─ 长时间占用 Hikari 连接并持有 ihm_order_prescription 行锁
│
├─ V2 入口仅单医院试点,订单量极少,且顺丰 API 已移出事务/锁,不是主因
│
├─ 其他请求需要查询/更新 ihm_order_prescription
│ └─ 由于长事务持有行锁,触发 Lock wait timeout(50s)
│
▼
09:17 Hikari 连接池(10 个)被长事务与锁等待长时间占用
│
▼
09:18 新请求无法获取连接 → MyBatisSystemException
│
▼
09:19 Tomcat HTTP 线程池被占满(线程阻塞在获取连接或等待锁)
│
▼
09:20 服务整体不可用,大量接口超时
4.2 根因分层
| 层级 | 根因 | 证据 |
|---|---|---|
| 直接原因(配置层) | 核心服务 HikariCP maximum-pool-size 仅 10,单服务并发承载力极低;早高峰请求迅速耗尽连接池,是全服务雪崩的直接触发条件 |
Nacos 配置中只有 hospital/mpc 配置 50;全集群理论峰值仅 ~560,远低于 MySQL max_connections=4190 |
| 放大因子(事务层) | V1 长事务在事务内同步调用顺丰 API+其他业务处理(约 20s),导致单连接被长时间占用并持有 ihm_order_prescription 行锁,使 10 连接的小池更快耗尽 |
V1 入口 OrderServiceImpl.syncOrderDeliveryInfoByDelivery 被 @Transactional 包裹,外部 API 与 DB 写入在同一事务 |
| SQL 层 | ihm_order_prescription 表并发访问冲突,锁等待严重 |
314 次 Lock wait timeout 全部集中在该表 |
| 超时层 | connection-timeout=300000(5 分钟)过长,线程阻塞不释放 |
hospital.yml 配置;耗尽时线程阻塞 5 分钟才失败 |
| 观测层 | 异常 message 被吞,未打印底层 Connection is not available |
日志中仅显示 MyBatisSystemException: null |
4.3 关键矛盾点
-
MySQL 连接数充足 vs 应用连接池耗尽:
- MySQL
Threads_connected=387,max_connections=4190,使用率仅 9.2%。 - 但单个 Java 服务 Hikari 连接池只有 10,无法横向扩展到 MySQL 的实际容量。
- 这是一种连接池配置未充分利用数据库能力的典型问题:数据库能扛 4190 连接,但应用层自己把并发能力限制在几十以内。
- MySQL
-
不是 MySQL 整体撑不住,而是单个服务连接池太小:
- 按 V1 请求平均占用连接 20s 计算,10 连接的服务每分钟只能处理约 30 笔发药/物流同步请求,早高峰极易被打满。
- 若将 patient/portal/operate 等核心入口服务池扩到 50,同等条件下并发承载能力提升 5 倍,可充分利用 MySQL 剩余容量。
- 连接池扩容是缓解当前问题最直接、见效最快的手段。
-
顺丰 API 不能异步,但 V2 已将其移出事务:
- 物流下单必须同步调用顺丰医寄通并立即返回物流单号给 HIS,因此不能改为 MQ 异步。
- V2 的优化价值在于:把顺丰 API 调用放在事务与 Redisson 锁之外,使数据库连接和行锁只被短事务占用;V1 的问题在于 API 调用在事务内,放大连接与锁的占用时长。
4.4 V1 与 V2 版本责任划分
| 维度 | V1 (OrderServiceImpl.syncOrderDeliveryInfoByDelivery) |
V2 (SyncDeliveryInfoV2ServiceImpl.syncByDelivery) |
|---|---|---|
| 入口比例 | 绝大多数医院(早晨高峰主要流量) | 仅单个试点医院,几笔订单 |
| 顺丰 API 调用 | 在 @Transactional 事务内同步调用 |
在事务与 Redisson 锁之外同步调用 |
| 事务范围 | 大事务:机构查询、租户配置、顺丰 API、处方/订单/HIS 更新全部在一个事务 | 短事务:仅处方/订单/HIS 更新通过 TransactionTemplate 包裹 |
| 锁范围 | Redisson 锁包裹整个方法(含顺丰 API 调用) | Redisson 锁仅包裹 DB 写入段 |
| 连接占用时长 | 数十秒(API 耗时 + DB 写入) | 数秒以内(主要是短事务 DB 写入) |
| 本次事故角色 | 主要诱因/放大因子:事务内同步调用顺丰 API 使单连接占用 ~20s,让小连接池更快耗尽并引发锁竞争 | 非主因:流量极小,且顺丰 API 已移出事务,不会长时间占连接 |
结论:V2 版本本身不是本次事故主因,反而是解决方向;真正的风险是绝大多数医院仍走 V1 大事务入口。
五、影响范围评估
5.1 受影响服务
| 服务 | 影响程度 | 说明 |
|---|---|---|
| patient | 🔴 严重 | 发药、处方同步、订单查询等核心业务大量超时/失败 |
| portal | 🔴 严重 | 租户回调、文件下载、日志保存等接口异常 |
| operate | 🟡 中等 | 运营平台接口受影响 |
| hospital | 🟢 较轻 | 因连接池 50,相对耐冲击,但仍可能受 MySQL 锁影响 |
| mpc/baseservice | 🟡 中等 | 取决于具体配置 |
5.2 受影响业务
| 业务场景 | 影响 |
|---|---|
| HIS 处方同步 | 同步失败,可能数据延迟 |
| 处方发药/退药 | 物流下单超时,患者无法及时收到药品 |
| 订单查询 | 查询超时,用户体验差 |
| 系统交互日志 | 无法记录,影响审计与排查 |
| 预问诊信息查询 | 获取失败 |
5.3 数据一致性风险
- Portal Feign 超时断开后,Patient 服务线程仍在继续执行 并完成物流下单,可能导致:
- 上游认为失败,下游实际成功(状态不一致)。
- 重试时可能触发重复发货(需依赖幂等校验)。
六、解决方案建议
6.1 紧急止损(已发生或再次发生)
-
临时扩容连接池(最高优先级):
- 将 patient、portal、operate 等核心服务的
maximum-pool-size临时调整到 50~100。 - 同时调小
connection-timeout至 3000~5000ms,快速失败避免线程长期阻塞。 - 预期效果:按 V1 请求平均占连接 20s 估算,单服务并发承载能力从约 30 req/min 提升到 150~300 req/min,可直接利用 MySQL 剩余的连接容量,是止血最快手段。
- 将 patient、portal、operate 等核心服务的
-
重启/扩容受影响的 Pod:
- 当连接池已耗尽时,重启可快速释放被占用的连接和线程。
- 增加 Pod 副本数可临时提升整体连接数。
-
限流降级:
- 对
syncOrderDeliveryInfo、syncHisOrder等重接口进行限流(如 Sentinel),降低并发峰值对连接池的冲击。 - 非核心接口(如系统交互日志保存)可异步化或丢弃。
- 对
6.2 短期优化(1-2 周内)
-
统一 HikariCP 配置,充分使用 MySQL 并发能力:
在
common-mysql.yml中统一配置合理参数,让所有服务默认获得足够连接,避免个别服务成为瓶颈【这里 maximum-pool-size连接池大小先设置50,后续多观察,再进行调整】:yamlspring: datasource: hikari: connection-init-sql: set names utf8mb4 # --- 核心连接池配置 --- # 最小空闲连接:闲时维持 25 个。 # 作用:保证早晚高峰切换瞬间有足够的"热"连接可用,无需从 0 开始创建,同时避免闲时过多连接占用 DB 资源。 minimum-idle: 25 # 最大连接池大小:忙时扩容至 50 个。 # 作用:应对早高峰突发流量,直接将单服务承载力提升 5 倍(相比默认 10)。 maximum-pool-size: 50 # --- 关键超时配置 --- # 数据库连接超时时间,默认30秒,即30000毫秒。 connection-timeout: 30000 # --- 连接生命周期管理 --- # 空闲超时:10 分钟。 # 优化理由:当流量从高峰回落后,连接会在空闲 10 分钟后逐渐回收至 minimum-idle (25)。 # 这个时间窗口设置得稍长一点(10分钟),可以避免流量波动造成的频繁连接创建/销毁(抖动)。 idle-timeout: 600000 # 连接最大生命周期:30 分钟。 # 作用:防止长时间复用同一个连接导致 MySQL 端状态不一致或内存泄漏。 max-lifetime: 1800000 # --- 稳定性保障 --- # 连接泄漏检测:60 秒。 # 作用:针对长事务问题,如果一个连接被占用超过 60 秒,HikariCP 会打印警告日志(Close connection exception...)这有助于我们在生产环境快速发现那些未及时释放的长事务代码。 leak-detection-threshold: 60000 # 其他基础配置:此属性控制从池返回的连接的默认自动提交行为,默认值:true auto-commit: true pool-name: HikariCP-Common各服务如需更大容量,可单独覆盖:
yamlspring: datasource: hikari: maximum-pool-size: 100 -
缩短 MySQL 锁等待超时:
- 设置
innodb_lock_wait_timeout=10~20(当前默认 50s),让锁竞争快速失败,避免线程长期占用。
- 设置
-
优化
ihm_order_prescription表访问:- 检查
(order_seq, deleted)索引是否合理。 - 避免大字段全量查询,减少锁持有时间。
- 将非必要查询移出事务。
- 检查
-
连接池更新配置生效,效果如下:

6.3 中期改造(1 个月内)
-
扩大 V2 覆盖范围,逐步下线 V1:
SyncDeliveryInfoV2ServiceImpl已将顺丰 API 调用移出事务与锁,并使用TransactionTemplate短事务写入 DB。- 在确认 V2 业务等价性后,逐步将更多医院切到 V2(通过
ihm_hospital_config.sync_delivery_info_version=2),从入口上消除 V1 大事务。 - 顺丰 API 仍需同步调用(HIS 需实时获取物流单号),不能改为 MQ 异步。
-
对 V1 做最小化改造(过渡方案):
- 若部分医院短期内无法切 V2,可在 V1 的
OrderServiceImpl.syncOrderDeliveryInfoByDelivery中:- 将
rpDeliveryService.handleDelivered(...)提前到事务开启前执行; - 或引入
TransactionTemplate仅包裹处方/订单/HIS 更新段,缩小事务范围。
- 将
- 若部分医院短期内无法切 V2,可在 V1 的
-
全局异常处理优化:
- 在
GlobalExceptionHandler中正确打印异常 cause,避免MyBatisSystemException: null无法定位根因。
- 在
-
接口超时配置梳理:
- Feign
readTimeout从 180s 调整至合理值(如 30s),配合熔断降级。 - Tomcat 线程池、HTTP 超时同步评估。
- Feign
6.4 长期建设
-
连接池监控告警:
- 接入 HikariCP Micrometer metrics,监控
activeConnections、pendingThreads、totalConnections。 - 设置告警:pendingThreads > 5 或 activeConnections/max > 0.8。
- 接入 HikariCP Micrometer metrics,监控
-
数据库锁监控:
- 在 MySQL 中持续监控
information_schema.INNODB_TRX、performance_schema.data_lock_waits。 - 对长事务(> 10s)实时告警。
- 在 MySQL 中持续监控
-
压测与容量规划:
- 定期开展全链路压测,验证连接池、线程池、MySQL 锁的承载能力。
- 建立容量基线文档。
七、预防措施
- 禁止核心服务使用默认连接池配置 :所有服务的
maximum-pool-size、connection-timeout、leak-detection-threshold必须显式配置并经过评审;配置需与 MySQLmax_connections做容量匹配,避免应用层人为限制数据库并发能力。 - 外部 API 调用禁止放在事务内:物流、支付等外部依赖必须在事务边界外调用。
- 长事务治理:任何超过 5 秒的事务必须拆解或异步化。
- 异常信息完整输出:禁止在全局异常处理中吞掉 cause,确保问题可定位。
- 常态化监控:HikariCP、MySQL 锁、Tomcat 线程池必须接入监控大盘。
八、结论
本次 07-27 早间接口超时事故的本质是 "应用层 HikariCP 连接池容量过小 + V1 长事务放大连接占用" 引发的级联雪崩,而非 MySQL 整体连接数不足。
- 最直接原因:核心服务 HikariCP 仅 10 个连接,在 V1 长事务占连接约 20s 的场景下,单服务每分钟仅能处理约 30 笔发药/物流同步请求,早高峰迅速耗尽连接池,是全服务不可用的第一块多米诺骨牌。
- MySQL 总连接数充足(387/4190),全集群理论峰值也仅约 560,远低于 MySQL 容量;瓶颈在应用层连接池配置,而非数据库。
ihm_order_prescription表锁竞争严重,314 次 Lock wait timeout 均集中于此,是长事务持有行锁后的次生现象。- V1 大事务在事务内同步调用顺丰 API,长时间占用连接并持有行锁,显著放大了小连接池的脆弱性;V2 已将该 API 移出事务,不是主因。
connection-timeout=300000过长,连接池耗尽时线程阻塞 5 分钟才失败,进一步拖垮 Tomcat 线程池。
最优先的改进措施:
- 立即在 Nacos
common-mysql.yml中统一 HikariCP 配置,核心服务maximum-pool-size=50(可按需 100)、connection-timeout=5000ms:这是缓解当前问题最直接、见效最快的手段,可让单服务并发承载能力提升 5~10 倍,充分利用 MySQL 剩余连接容量。 - 同步调小
innodb_lock_wait_timeout至 10~20s:让锁竞争快速失败,避免线程长期占用连接。 - 尽快扩大
syncOrderDeliveryInfoV2覆盖范围或改造 V1 事务边界 :把顺丰 API 调用移出@Transactional事务,仅对处方/订单/HIS 更新使用短事务;顺丰 API 因需实时返回物流单号给 HIS,不能异步化。
附录:关键数据图表
A. Lock wait timeout 时间分布
typescript
09:15 ████████████████████████████████████████████████ 57
09:16 ██████████████████████████████████████████████ 51
09:17 ██████████████████████████████████████ 38
09:18 ██████████████████████████████████████ 38
09:19 ██████████████████████████████████████████████ 50
09:20 ████████████████████████████████████████████████ 55
09:21 ████████████████████████ 24
B. MyBatisSystemException 时间分布
typescript
09:15 ████████████████████████████████████████████████ 31
09:16 ████████████████████████████████████████████████ 31
09:17 ████████████████████████████████████████████████ 31
09:18 ██████████████████████████████████████████████████ 34
09:23 ██████████████████████████████████ 23
C. 各服务 HikariCP 配置对比
typescript
hospital ████████████████████████████████████████████████████████████████ 50
mpc-server ████████████████████████████████████████████████████████████████ 50
patient ██████████ 10 (default)
portal ██████████ 10 (default)
operate ██████████ 10 (default)
baseservice ██████████ 10 (default)
dict ██████████ 10 (default)
orders ██████████ 10 (default)
his ██████████ 10 (default)
consumer ██████████ 10 (default)