一、问题现象:半夜告警炸锅了

凌晨两点,手机突然被报警短信轰炸。监控系统显示生产环境的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 事务使用原则

记住事务处理的"三要三不要":

  • 要:短小精悍、快速提交、明确边界
  • 不要:长事务、不必要的事务、忘记关闭

五、经验总结

通过这次事故,我们总结出几个关键点:

  1. 连接数暴增往往只是表象,根本原因可能是事务泄漏
  2. 数据库监控必须包含连接数、事务持续时间等关键指标
  3. ORM框架不是免死金牌,不当使用反而更容易出问题
  4. 连接池参数需要根据业务特点精心调优

最后特别提醒:所有数据库操作都应该有超时机制。就像我们不会允许一个服务员永远等顾客点单一样,数据库操作也必须有合理的时间限制。