一、问题现象:半夜告警炸锅了
凌晨两点,手机突然被报警短信轰炸。监控系统显示生产环境的openGauss数据库连接数从平时的200猛增到1200,连接池完全耗尽,应用开始大面积报"too many connections"错误。这时候最怕的就是——明明业务量没增加,数据库却像被僵尸围攻一样。
典型症状包括:
- 应用日志突然出现大量连接超时错误
- 数据库监控面板的连接数曲线呈90度直角上升
- 业务响应时间从200ms飙升到10秒以上
-- 技术栈:openGauss SQL
-- 紧急查看当前连接数(运维常用命令)
SELECT count(*) FROM pg_stat_activity;
-- 查看具体连接来源(发现大量相似IP)
SELECT client_addr, application_name, count(*)
FROM pg_stat_activity
GROUP BY client_addr, application_name;
二、排查三板斧:从现象到根源
2.1 第一斧:连接来源分析
先搞清楚这些连接是谁创建的。通过下面这个SQL,我们发现80%的连接都来自同一个微服务,且application_name都显示为"订单服务"。
-- 技术栈:openGauss SQL
-- 深入分析连接特征(关键排查步骤)
SELECT
datname as 数据库名,
usename as 用户名,
application_name as 应用名称,
state as 状态,
count(*) as 连接数
FROM pg_stat_activity
GROUP BY datname, usename, application_name, state
ORDER BY count(*) DESC;
2.2 第二斧:连接状态追踪
接着检查这些连接在干什么,发现大量"idle in transaction"状态连接——这意味着应用开启了事务但没提交或回滚。这就像去餐厅吃饭不结账,占着桌子不走。
-- 技术栈:openGauss SQL
-- 检查长时间运行的事务(发现问题关键)
SELECT
pid,
now() - xact_start as 事务持续时间,
query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY 事务持续时间 DESC
LIMIT 10;
2.3 第三斧:代码层定位
根据应用名称定位到Java服务,发现一段有问题的订单查询代码:
// 技术栈:Java + JDBC
// 问题代码示例(反面教材)
public List<Order> queryOrders(String userId) {
Connection conn = null;
try {
conn = dataSource.getConnection(); // 获取连接
conn.setAutoCommit(false); // 开启事务 ← 这里埋雷了!
// 执行查询但忘记提交/回滚
return jdbcTemplate.query("SELECT * FROM orders WHERE user_id=?",
new OrderRowMapper(), userId);
// 缺少conn.commit()或conn.rollback()
} catch (SQLException e) {
// 异常处理中也未回滚事务
throw new RuntimeException(e);
} finally {
// 虽然关闭了连接,但事务未结束!
if(conn != null) try { conn.close(); } catch (SQLException ignore) {}
}
}
三、解决方案:多管齐下止血
3.1 紧急止血方案
先用管理命令清理僵尸连接:
-- 技术栈:openGauss SQL
-- 强制终止空闲事务(救急用)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND now() - xact_start > interval '5 minutes';
3.2 中期防护措施
调整数据库参数,增加连接数上限并设置超时:
-- 技术栈:openGauss SQL
-- 修改关键参数(需要重启)
ALTER SYSTEM SET max_connections = 1500;
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
3.3 长期根治方案
修复应用代码,确保事务正确处理:
// 技术栈:Java + JDBC
// 修复后的正确写法
public List<Order> queryOrders(String userId) {
Connection conn = null;
try {
conn = dataSource.getConnection();
// 关键修改1:不需要事务的查询关闭自动提交
conn.setAutoCommit(true);
return jdbcTemplate.query("SELECT * FROM orders WHERE user_id=?",
new OrderRowMapper(), userId);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 关键修改2:使用DataSourceUtils确保正确释放
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
四、防患于未然:最佳实践
4.1 连接池配置要点
建议采用HikariCP连接池,配置关键参数:
# 技术栈:Spring Boot配置
spring:
datasource:
hikari:
maximum-pool-size: 100 # 最大连接数
idle-timeout: 60000 # 空闲超时(毫秒)
max-lifetime: 1800000 # 最大存活时间
leak-detection-threshold: 5000 # 泄漏检测阈值
4.2 监控告警设置
配置Prometheus监控规则:
# 技术栈:Prometheus告警规则
groups:
- name: openGauss-alert
rules:
- alert: HighConnections
expr: pg_stat_activity_count > (pg_settings_max_connections * 0.8)
for: 5m
labels:
severity: critical
annotations:
summary: "数据库连接数超过80%容量"
4.3 事务使用原则
记住事务处理的"三要三不要":
- 要:短小精悍、快速提交、明确边界
- 不要:长事务、不必要的事务、忘记关闭
五、经验总结
通过这次事故,我们总结出几个关键点:
- 连接数暴增往往只是表象,根本原因可能是事务泄漏
- 数据库监控必须包含连接数、事务持续时间等关键指标
- ORM框架不是免死金牌,不当使用反而更容易出问题
- 连接池参数需要根据业务特点精心调优
最后特别提醒:所有数据库操作都应该有超时机制。就像我们不会允许一个服务员永远等顾客点单一样,数据库操作也必须有合理的时间限制。
评论