0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

生产环境数据库连接池耗尽的全流程排查与性能优化实战

马哥Linux运维 来源:马哥Linux运维 2026-03-27 15:58 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

1. 连接池原理与工作机制

1.1 连接池核心概念

数据库连接池是应用程序与数据库之间的缓存连接组件。连接池在应用程序启动时创建一组数据库连接,应用程序从连接池获取连接,使用完毕后归还连接池而非关闭连接,避免反复建立和关闭连接的性能开销。

连接池的核心参数有三个:最小连接数(minimumIdle)、最大连接数(maximumPoolSize)、连接超时(connectionTimeout)。

最小连接数是连接池保持的空闲连接数量。在应用程序启动时,连接池会根据这个值创建相应数量的连接。如果实际使用的连接数小于最小连接数,空闲的连接会保持连接状态但不使用。

最大连接数是连接池允许的最大连接数。当所有连接都被占用时,新请求需要等待可用连接。如果等待时间超过连接超时时间,应用程序会抛出SQLException。

连接超时(connectionTimeout)是获取连接的最大等待时间。默认值为30秒。当连接池中没有可用连接时,请求会进入等待队列,等待时间超过这个值后会抛出异常。

连接池的工作原理是:应用程序启动时,根据最小连接数创建一批连接;请求到来时,从连接池队列取出可用连接标记为占用,使用完毕后清空占用标记归还队列;如果可用连接数为零,新请求进入等待队列;如果等待时间超过连接超时,抛出SQLException。

连接生命周期的状态流转:初始化→空闲→占用→归还→空闲→销毁。连接在占用期间执行SQL,如果SQL执行时间过长,会导致连接被长时间占用;连接归还后等待下一次使用;如果连接空闲时间超过最大生存时间,连接被销毁。

1.2 常见连接池实现

HikariCP是目前最流行的连接池实现,被Spring Boot选为默认连接池。HikariCP以性能著称,官方数据显示其性能是其他连接池的数倍。

HikariCP的优势在于:轻量级,核心代码约1.3万行;零依赖,不需要额外jar包;采用FastList替代ArrayList减少开销;采用字节码增强减少反射调用;连接健康检查高效。

HikariCP的配置项:

# application.yml
spring:
datasource:
 url:jdbc//localhost:3306/mydb
 username:root
 password:password
 driver-class-name:com.mysql.cj.jdbc.Driver
 hikari:
  # 连接池名称
  pool-name:HikariPool-MySQL
  # 最小空闲连接数
  minimum-idle:5
  # 最大连接数
  maximum-pool-size:20
  # 连接超时(毫秒)
  connection-timeout:30000
  # 空闲超时
  idle-timeout:600000
  # 最大生存时间
  max-lifetime:1800000
  # 连接测试查询
  connection-test-query:SELECT1
  # 启用连接测试
  validation-timeout:5000

HikariCP监控指标:

// 通过JMX访问HikariCP MBean
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mxBeanName =newObjectName("com.zaxxer.hikari:type=Pool (MySQL)");
HikariPoolMXBean poolProxy = JMX.newMXBeanProxy(mbs, mxBeanName, HikariPoolMXBean.class);

// 获取连接池状态
poolProxy.getActiveConnections();  // 当前活跃连接数
poolProxy.getIdleConnections();  // 当前空闲连接数
poolProxy.getTotalConnections();  // 总连接数
poolProxy.getThreadsAwaitingConnection(); // 等待连接的线程数

Druid是阿里巴巴开源的连接池,除了连接管理功能外还提供强大的监控功能。Druid的监控面板可以查看连接池状态、SQL执行耗时、连接泄漏追踪等。Druid的WallFilter提供SQL防火墙功能,防止SQL注入。

Druid配置示例:

spring:
datasource:
 url:jdbc//localhost:3306/mydb
 username:root
 password:password
 driver-class-name:com.mysql.cj.jdbc.Driver
 druid:
  # 连接池配置
  initial-size:5
  min-idle:5
  max-active:20
  max-wait:60000
  # 连接有效性检测
  validation-query:SELECT1
  test-while-idle:true
  test-on-borrow:false
  test-on-return:false
  # 泄漏检测
  remove-abandoned:true
  remove-abandoned-timeout:300
  log-abandoned:true
  # 监控配置
  filter:
   stat:
    enabled:true
    log-slow-sql:true
    slow-sql-millis:2000
   wall:
    enabled:true
    config:
     multi-statement-allow:true

C3P0是历史悠久的连接池实现,被Hibernate早期版本使用。C3P0采用同步锁实现,性能较差,在高并发场景下可能成为瓶颈。

// C3P0配置
ComboPooledDataSource cpds =newComboPooledDataSource();
cpds.setDriverClass("com.mysql.cj.jdbc.Driver");
cpds.setJdbcUrl("jdbc//localhost:3306/mydb");
cpds.setUser("root");
cpds.setPassword("password");

// 连接池参数
cpds.setInitialPoolSize(5);
cpds.setMinPoolSize(5);
cpds.setMaxPoolSize(20);
cpds.setMaxStatements(100);    // 缓存的PreparedStatement数量
cpds.setMaxIdleTime(300);    // 最大空闲时间(秒)
cpds.setCheckoutTimeout(3000);  // 获取连接超时(毫秒)
cpds.setUnreturnedConnectionTimeout(60); // 连接未返回超时

C3P0和DBCP由于性能问题已逐渐被HikariCP取代。新项目建议选择HikariCP,对监控有特殊需求可以选择Druid。

1.3 连接池与数据库性能的关系

连接池是应用层与数据库之间的桥梁,连接池的配置直接影响数据库的承载能力和响应时间。

连接池过小的表现是:并发请求增加时,可用连接迅速耗尽,请求开始排队等待;等待时间超过连接超时后抛出异常;数据库CPU使用率可能很低但响应时间很长。连接池过小限制了应用的并发处理能力。

连接池过大的表现是:数据库连接数大量增加,数据库内存占用增加;数据库并发处理压力增大,CPU使用率升高;连接数过多增加了数据库的管理开销。连接池过大可能压垮数据库。

最优连接数需要根据数据库服务器配置和应用特性确定。一般建议公式:连接数 = (核心数 * 2) + 有效磁盘数。假设8核CPU和1块SSD,最优连接数约为17。这个公式是起点,实际需要通过压测调整。

连接数计算参考表:

数据库类型 推荐最大连接数 备注
MySQL 100-500 取决于innodb_buffer_pool_size
PostgreSQL 100-300 推荐使用PgBouncer
Oracle 100-1000 取决于SGA大小
SQL Server 500-2000 取决于内存

2. 连接池耗尽原因分析

2.1 连接泄漏的场景与原因

连接泄漏是指应用程序获取连接后未正确归还,导致连接一直被占用无法释放。连接泄漏的最终结果是连接池耗尽,新请求无法获取连接。

最常见的泄漏场景是在finally块未执行归还操作。正确代码应该是:

Connection conn =null;
try{
  conn = dataSource.getConnection();
 // 使用连接执行SQL
  PreparedStatement ps = conn.prepareStatement(sql);
  ps.executeQuery();
}catch(SQLException e) {
 // 异常处理
}finally{
 if(conn !=null) {
   try{
      conn.close(); // 归还连接到连接池
    }catch(SQLException e) {
     // 忽略关闭异常
    }
  }
}

如果忘记在finally中归还,或者在finally中归还前就抛出异常导致归还语句未执行,就会造成泄漏。

异常处理导致的泄漏也常见。如果在获取连接后、使用前就抛出异常,连接没有进入使用逻辑,也就不会执行close()。

// 错误示例:在获取连接后进行参数校验
Connection conn = dataSource.getConnection();
if(conn ==null) {
 thrownewRuntimeException("Failed to get connection");
}

// 如果参数校验在conn获取之前,就会导致连接泄漏
if(!validateParams(params)) {
  conn.close(); // 如果忘记这行,就会泄漏
 thrownewIllegalArgumentException("Invalid params");
}

事务未提交或未回滚也会导致连接占用。

// 错误示例:忘记提交事务
Connection conn = dataSource.getConnection();
try{
  conn.setAutoCommit(false);
 // 执行多个SQL
  executeSql1(conn);
  executeSql2(conn);
 // 忘记提交或回滚
 // conn.commit(); // 如果忘记这行,连接会一直占用
}catch(SQLException e) {
  conn.rollback(); // 即使回滚,如果异常发生在commit之前
}finally{
  conn.close();
}

ResultSet和Statement未关闭也可能间接导致连接泄漏。

// 正确做法:使用try-with-resources
try(Connection conn = dataSource.getConnection();
  PreparedStatement ps = conn.prepareStatement(sql);
  ResultSet rs = ps.executeQuery()) {
 while(rs.next()) {
   // 处理结果
  }
}// 自动关闭rs、ps、conn

连接泄漏的排查方法:

// 启用HikariCP的泄漏检测
spring:
 datasource:
  hikari:
   leak-detection-threshold:60000 #60秒检测阈值

// Druid的泄漏检测
spring:
 datasource:
  druid:
   remove-abandoned:true
   remove-abandoned-timeout:60  #60秒
   log-abandoned:true

2.2 连接池配置不当

连接池参数配置不合理会导致性能问题或连接耗尽。

最大连接数设置过小是最常见的配置问题。

# 配置过小示例
spring:
datasource:
 hikari:
  maximum-pool-size:5# 对于高并发应用明显不足

# 正确配置
spring:
datasource:
 hikari:
  maximum-pool-size:50# 根据并发量调整

如果应用并发量较大,最小连接数无法满足请求,排队等待的请求会堆积,最终超时。

连接最大生存时间(maxLifetime)设置过长或过短都有问题。

# maxLifetime设置过长
spring:
datasource:
 hikari:
  max-lifetime:7200000# 2小时,可能超过数据库timeout

# 正确配置
spring:
datasource:
 hikari:
  max-lifetime:1800000# 30分钟,数据库timeout减去30秒

设置过长会导致连接在数据库端超时后仍在使用,可能触发连接错误。设置过短会导致频繁创建销毁连接,增加开销。

连接超时(connectionTimeout)设置过短会导致正常请求被误杀。

# 设置过短
spring:
datasource:
 hikari:
  connection-timeout:1000# 1秒,正常SQL无法完成

# 正确配置
spring:
datasource:
 hikari:
  connection-timeout:30000# 30秒,考虑SQL执行时间和网络延迟

如果SQL执行时间较长,短时间内获取不到连接就会超时。连接超时应该大于正常SQL执行时间的最大值,考虑网络延迟和数据库负载。

最小空闲连接数(minimumIdle)设置过大或过小都有问题。

# 设置过小
spring:
datasource:
 hikari:
  minimum-idle:1# 正常负载时连接不足

# 设置过大
spring:
datasource:
 hikari:
  minimum-idle:50# 即使没有请求也保持50个连接

# 正确配置
spring:
datasource:
 hikari:
  minimum-idle:10# 根据正常负载设置

设置过大会造成资源浪费,空闲连接占用连接数。设置过小在高并发时需要频繁创建新连接,增加延迟。对于稳定负载的应用,可以设置minimumIdle等于maximumPoolSize。

2.3 SQL执行慢导致连接堆积

SQL执行时间是影响连接周转率的关键因素。假设连接池大小为10,每秒可以处理10个请求,每个SQL执行时间100毫秒;如果SQL执行时间延长到1秒,每秒只能处理10个连接,连接周转率下降10倍。

慢SQL的成因包括:缺少索引导致全表扫描;SQL写法问题如SELECT *、嵌套子查询;表数据量过大;数据库服务器负载高;网络延迟增大。

连接堆积的表现是:应用层连接池可用连接数接近零;数据库端显示大量sleep状态的连接;请求排队增加,响应时间上升;严重时触发连接超时异常。

排查慢SQL的方法包括:开启数据库慢查询日志;使用EXPLAIN分析SQL执行计划;查看数据库监控的慢SQL排行榜;分析应用日志中SQL执行时间。

MySQL慢查询日志配置:

# my.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

PostgreSQL慢查询日志配置:

-- 修改配置
ALTERSYSTEMSETlog_min_duration_statement =1000; -- 1秒
ALTERSYSTEMSETlog_statement ='all';

-- 查看日志文件
SELECTpg_read_file('/var/log/postgresql/postgresql-14-slow.log');

EXPLAIN分析示例:

EXPLAINSELECT*FROMorders o
JOINcustomers cONo.customer_id = c.id
WHEREo.created_at >'2026-01-01'
ANDc.status ='active';

-- 分析结果
-- type: ALL 表示全表扫描,需要优化
-- key: 显示使用的索引
-- rows: 扫描的行数
-- Extra: Using filesort/Using temporary 需要优化

2.4 数据库本身连接数限制

数据库服务器有最大连接数限制,当应用连接池的总连接数接近这个限制时,新请求无法获取连接。

MySQL的max_connections参数控制最大连接数,默认151。生产环境通常需要调大,但过大的max_connections会消耗过多内存。每个MySQL连接约占用256KB-400KB的内存。

-- 查看当前最大连接数
SHOWVARIABLESLIKE'max_connections';

-- 设置最大连接数(临时)
SETGLOBALmax_connections =500;

-- 永久配置(my.cnf)
[mysqld]
max_connections = 500

-- 查看当前连接数
SHOWSTATUSLIKE'Threads_connected';
SHOWPROCESSLIST;

PostgreSQL的max_connections默认100。PostgreSQL采用进程模型,每个连接启动一个服务器进程,过大的连接数会消耗大量系统资源。PostgreSQL推荐使用连接池中间件如PgBouncer处理大量连接。

-- 查看当前最大连接数
SHOWmax_connections;

-- 修改postgresql.conf
max_connections = 200

-- 查看当前连接数
SELECTcount(*)FROMpg_stat_activity;

Oracle的processes参数控制最大进程数,包括用户进程和后台进程。

-- 查看当前设置
SHOWPARAMETER processes;

-- 修改
ALTERSYSTEMSETprocesses =500SCOPE=SPFILE;

连接数限制问题的表现是:应用连接池获取连接时抛出too many connections异常;数据库端无法新建连接;已建立的连接正常但新建连接失败。

3. 排查方法论与工具

3.1 连接状态监控

连接池通常提供管理接口或监控端点,可以实时查看连接池状态。

HikariCP通过JMX或Actuator端点暴露连接池信息。

# 启用Actuator端点
spring:
endpoints:
 web:
  exposure:
   include:health,info,metrics,hikaricp
metrics:
 enable:
  hikaricp:true

监控指标:

# 通过Actuator查询HikariCP指标
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active
curl http://localhost:8080/actuator/metrics/hikicp.connections.idle
curl http://localhost:8080/actuator/metrics/hikaricp.connections

# 通过JMX访问
jconsole
# 连接到Java应用后,查找com.zaxxer.hikari:type=Pool节点

Druid提供Web监控页面。

# 启用Druid监控
spring:
datasource:
 druid:
  stat-view-config:
   enabled:true
   url-pattern:/druid/*
   reset-enable:true
   login-password:admin
   login-username:admin

连接池状态的典型异常模式:

模式 特征 原因
连接池饱和 totalConnections等于maximumPoolSize,threadsAwaitingConnection大于0 并发过高或SQL执行过慢
连接泄漏 totalConnections持续增长接近maximumPoolSize 代码未正确关闭连接
连接耗尽 threadsAwaitingConnection大于0且持续增长 配置不当或数据库限制

告警阈值建议:activeConnections持续超过maximumPoolSize的80%应发出预警;threadsAwaitingConnection大于0应发出告警;totalConnections接近maximumPoolSize应立即告警。

3.2 线程堆栈分析

线程堆栈分析是排查连接泄漏和死锁问题的重要手段。当应用出现连接等待或挂起时,获取线程堆栈可以定位问题代码。

使用jstack(Java)获取线程堆栈:

# 获取线程堆栈
jstack -l PID > threaddump.txt

# 强制获取(线程可能hung住)
jstack -F PID > threaddump.txt

# 多次dump对比
jstack -l PID > threaddump_$(date +%H%M%S).txt

堆栈分析的重点:查找处于WAITING或BLOCKED状态的线程,分析等待原因;查找持有锁的线程,分析是否导致其他线程阻塞;查找SQL执行相关的线程,分析SQL执行时间。

典型的连接泄漏堆栈特征:

"http-nio-8080-exec-1"#123daemon prio=5 os_prio=0 tid=0x12345678
 java.lang.Thread.State: WAITING
  at java.lang.Object.wait(Native Method)
  at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:xxx)
  ...
  at com.example.service.OrderService.getOrders(OrderService.java:45)

查看等待连接的线程:

# 筛选等待连接的线程
grep -A 20"waiting for connection"threaddump.txt

# 查找HikariPool相关调用
grep -B 5 -A 15"HikariPool"threaddump.txt

典型的死锁堆栈特征:

Found one Java-level deadlock:
=============================
"Thread-1":
 waitingforlock on java.lang.Object@12345678
 owned by Thread-2

"Thread-2":
 waitingforlock on java.lang.Object@87654321
 owned by Thread-1

jstack -l会自动检测并报告死锁。

3.3 数据库会话分析

数据库端的会话信息可以反映连接池的连接使用情况。

MySQL会话查询:

SHOWPROCESSLIST;

-- 详细版本
SHOWFULLPROCESSLIST;

-- Information Schema方式
SELECT*FROMinformation_schema.PROCESSLIST
WHERECOMMAND !='Sleep'
ORDERBYTIMEDESC;

-- 按用户统计连接数
SELECTuser,COUNT(*)ascount
FROMinformation_schema.PROCESSLIST
GROUPBYuser;

-- 按数据库统计连接数
SELECTdb,COUNT(*)ascount
FROMinformation_schema.PROCESSLIST
GROUPBYdb;

PROCESSLIST关键列说明:

说明
Id 连接ID
User 用户名
Host 客户端地址
db 当前数据库
Command 连接状态(Sleep/Query)
Time 持续时间(秒)
State 当前状态
Info 执行的SQL

TIME列持续增长的连接可能是慢SQL或连接泄漏。

-- 查找长时间运行的查询
SELECT*FROMinformation_schema.PROCESSLIST
WHERECOMMAND ='Query'
ANDTIME>60
ORDERBYTIMEDESC;

-- 查找未释放的连接
SELECT*FROMinformation_schema.PROCESSLIST
WHERECOMMAND ='Sleep'
ANDTIME>300;

PostgreSQL会话查询:

-- 查看所有会话
SELECT*FROMpg_stat_activity;

-- 查找活动查询
SELECTpid, usename, query_start, state,query
FROMpg_stat_activity
WHEREstate ='active'
ORDERBYquery_start;

-- 查找等待中的会话
SELECTpid, usename, wait_event_type, wait_event
FROMpg_stat_activity
WHEREwait_eventISNOTNULL;

-- 杀死长时间运行的查询
SELECTpg_cancel_backend(pid); -- 温和取消
SELECTpg_terminate_backend(pid); -- 强制终止

3.4 慢查询日志

慢查询日志是定位SQL性能问题的基本工具。

MySQL慢查询日志分析:

# 使用mysqldumpslow分析
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

# 使用pt-query-digest(Percona Toolkit)
pt-query-digest /var/log/mysql/slow.log

# 分析结果示例
# Query 1: 0.1234s
# SELECT * FROM orders WHERE status = ?
# Time: 2026-03-20T1030
# EXPLAIN: type=ALL, rows=1000000

PostgreSQL慢查询日志分析:

-- 使用pgBadger分析
pgbadger /var/log/postgresql/postgresql-14-slow.log

-- 手动分析
SELECTquery, calls, mean_time, total_time
FROMpg_stat_statements
ORDERBYtotal_timeDESC
LIMIT10;

4. 优化方案与配置建议

4.1 连接池参数调优

HikariCP推荐配置(针对8核CPU和8GB内存的服务器):

spring:
datasource:
 hikari:
  # 连接池大小计算:(核心数 * 2) + 有效磁盘数 = 17
  # 考虑到有SSD,可以设置稍大一些
  maximum-pool-size:20

  # 最小空闲连接数
  # 稳定负载设置等于maximum-pool-size
  # 波动负载设置小于maximum-pool-size
  minimum-idle:10

  # 连接超时:考虑SQL执行时间、网络延迟
  connection-timeout:30000

  # 空闲超时:设置为maxLifetime的一半
  idle-timeout:600000

  # 最大生存时间:数据库timeout减去30秒
  # MySQL默认timeout为8小时
  max-lifetime:1800000

  # 连接测试查询
  connection-test-query:SELECT1

  # 泄漏检测阈值
  leak-detection-threshold:60000

最大连接数的计算方法:

# 应用并发线程数 × 每个线程的连接数 + 预留连接数
# 假设应用最大并发100,每线程1个连接,预留10个连接
max_connections =100+10=110

# 考虑数据库服务器能力
# MySQL: max_connections = min(110, 500)
# PostgreSQL: max_connections = min(110, 100) 推荐使用PgBouncer

连接超时的设置原则:connectionTimeout应大于SQL最大执行时间;考虑网络延迟和数据库负载;过短会误杀正常请求,过长会增加用户等待时间。

空闲连接管理的设置原则:minimumIdle应设置足够应对正常负载;如果流量稳定可以设置minimumIdle=maximumPoolSize;如果流量波动大可以设置较小的minimumIdle,让连接池弹性伸缩。

4.2 SQL优化

SQL优化是解决连接池耗尽问题的根本途径。

索引优化的原则:分析慢查询的执行计划;为WHERE、JOIN、ORDER BY涉及的列创建索引;避免过多索引影响写入性能;定期分析表统计信息优化器选择。

-- 创建索引示例
CREATEINDEXidx_orders_customer_idONorders(customer_id);
CREATEINDEXidx_orders_statusONorders(status);
CREATEINDEXidx_orders_created_atONorders(created_at);

-- 复合索引示例
CREATEINDEXidx_orders_cust_statusONorders(customer_id,status);

-- 查看索引使用情况
EXPLAINSELECT*FROMordersWHEREcustomer_id =123;

-- 分析表统计信息
ANALYZETABLEorders;

SQL写法优化:

-- 避免SELECT *
SELECTorder_id, customer_id, total_amount, created_at
FROMordersWHEREorder_id =123;

-- 使用LIMIT分页
SELECT*FROMorders
ORDERBYcreated_atDESC
LIMIT0,20;

-- 避免嵌套子查询,改用JOIN
-- 低效
SELECT*FROMorders
WHEREcustomer_idIN(
 SELECTidFROMcustomersWHEREstatus='active'
);

-- 高效
SELECTo.*FROMorders o
INNERJOINcustomers cONo.customer_id = c.id
WHEREc.status ='active';

-- 使用批量操作
INSERTINTOorder_items (order_id, product_id, quantity)
VALUES(1,101,2), (1,102,1), (1,103,3);

事务优化原则:保持事务短小,减少锁定时间;避免在事务中执行不必要的SQL;合理使用隔离级别,不是所有场景都需要高隔离级别;使用只读事务优化只读查询。

// 正确的事务处理
@Transactional
publicvoidcreateOrder(OrderDTO orderDTO){
 // 验证
  validateOrder(orderDTO);

 // 插入订单
  Order order =newOrder();
  order.setCustomerId(orderDTO.getCustomerId());
  order.setStatus("pending");
  orderRepository.save(order);

 // 扣减库存(必须在事务内)
  inventoryService.decreaseStock(orderDTO.getItems());

 // 更新订单状态
  order.setStatus("confirmed");
  orderRepository.save(order);
}

4.3 数据库参数调整

数据库参数的合理配置可以提升连接处理能力和稳定性。

MySQL关键参数:

[mysqld]
# 连接相关
max_connections = 500
wait_timeout = 28800
interactive_timeout = 28800
max_connect_errors = 10000

# 缓冲池配置
innodb_buffer_pool_size = 4G # 建议为系统内存的60-80%
innodb_buffer_pool_instances = 4

# 连接优化
thread_cache_size = 50
table_open_cache = 4000
sort_buffer_size = 2M
join_buffer_size = 2M

# 日志配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

PostgreSQL关键参数:

-- 连接配置
ALTERSYSTEMSETmax_connections =200;
ALTERSYSTEMSETsuperuser_reserved_connections =3;

-- 内存配置
ALTERSYSTEMSETshared_buffers ='2GB'; -- 建议为系统内存的25%
ALTERSYSTEMSETeffective_cache_size ='6GB'; -- 建议为系统内存的75%
ALTERSYSTEMSETwork_mem ='64MB';
ALTERSYSTEMSETmaintenance_work_mem ='512MB';

-- 查询优化
ALTERSYSTEMSETrandom_page_cost =1.1; -- SSD设置为1.1,机械硬盘设置为4
ALTERSYSTEMSETeffective_io_concurrency =200; -- SSD设置为200

连接池中间件是解决大量连接问题的方案。PgBouncer可以在应用和数据库之间提供连接池功能,应用连接PgBouncer,PgBouncer复用少量真实连接访问数据库。

PgBouncer配置:

[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt

# 连接池模式
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3

# 超时配置
server_idle_timeout = 600
server_lifetime = 3600
server_connect_timeout = 15

5. 实战案例与证据链

5.1 典型耗尽场景复现

某电商系统在促销期间出现大量数据库连接超时错误,导致部分用户下单失败。故障持续约15分钟,影响订单量约2000单。

故障现象:应用日志中出现大量SQLException,消息包含timeout、connection字样;Druid监控显示连接池activeConnections达到最大值30,threadsAwaitingConnection持续大于0;数据库端SHOW PROCESSLIST显示约25个连接处于Sleep状态。

排查过程:获取应用线程堆栈发现大量线程在等待获取连接,堆栈显示都在执行订单创建相关的SQL;查询Druid监控的SQL统计,发现有一个更新订单状态的SQL平均执行时间从正常的50毫秒上升到800毫秒;进一步分析该SQL,发现缺少索引导致全表扫描。

-- 问题SQL
UPDATEordersSETstatus='paid', paid_at =NOW()
WHEREorder_id = ?;

-- 分析执行计划
EXPLAINUPDATEordersSETstatus='paid', paid_at =NOW()
WHEREorder_id = ?;
-- 结果:type=ALL, key=NULL, rows=1000000

-- 发现缺少主键索引
SHOWINDEXFROMorders;
-- 结果:order_id字段没有索引

故障根因:订单表在促销前缺少分表索引,数据量超过1000万行后查询变慢;促销期间并发量增加,耗时的SQL占用连接时间增长;可用连接迅速耗尽,新请求开始排队等待。

5.2 优化前后性能对比

针对故障原因,团队实施了三项优化措施:添加订单表索引;调整连接池参数;优化慢SQL。

索引优化:为订单表添加(order_id, status)组合索引;将订单查询从全表扫描改为索引扫描。

-- 添加索引
ALTERTABLEordersADDINDEXidx_order_id_status (order_id,status);

-- 验证索引
EXPLAINSELECT*FROMordersWHEREorder_id =123;
-- 结果:type=ref, key=idx_order_id_status, rows=1

连接池调优:将maximumPoolSize从30调整到50;将minimumIdle从5调整到20;将connectionTimeout从10秒调整到30秒。

spring:
datasource:
 druid:
  max-active:50
  min-idle:20
  max-wait:30000

慢SQL优化:分析其他慢SQL并优化;使用EXPLAIN验证优化效果;建立慢SQL监控告警机制。

-- 批量优化其他慢SQL
CREATEINDEXidx_orders_customer_idONorders(customer_id);
CREATEINDEXidx_orders_created_atONorders(created_at);

-- 优化订单查询
SELECTo.*, c.nameascustomer_name
FROMorders o
INNERJOINcustomers cONo.customer_id = c.id
WHEREo.order_id = ?;

优化效果:同等促销负载下,连接池activeConnections峰值从30降低到18,threadsAwaitingConnection降为0;数据库连接超时错误消失,订单成功率从98.2%提升到99.8%;系统平均响应时间从450毫秒降低到120毫秒。

5.3 连接池监控仪表板

建立连接池监控仪表板可以实时掌握连接池状态。

Prometheus监控配置:

# prometheus.yml
scrape_configs:
-job_name:'spring-boot-actuator'
 metrics_path:'/actuator/prometheus'
 static_configs:
  -targets:['localhost:8080']

Grafana仪表板查询:

# 连接池使用率
hikaricp_connections_active / hikaricp_connections * 100

# 等待连接的线程数
hikaricp_threads_awaiting_connection

# 连接获取时间
hikaricp_connection_timeout_total

# 连接泄漏数
hikaricp_leased_connections

5.4 连接池高可用设计

连接池的高可用设计可以避免单点故障导致的数据库访问中断。

主备切换方案:

# Spring Boot多数据源配置
spring:
datasource:
 # 主库
 primary:
  url:jdbc//master:3306/mydb
  username:root
  password:password
  hikari:
   pool-name:HikariPool-Primary
   maximum-pool-size:20
 # 备库
 secondary:
  url:jdbc//slave:3306/mydb
  username:root
  password:password
  hikari:
   pool-name:HikariPool-Secondary
   maximum-pool-size:10

读写分离方案:

// 读写分离路由配置
@Configuration
publicclassDataSourceConfig{
 @Bean
 publicDataSourceroutingDataSource(
      @Qualifier("masterDataSource")DataSource masterDataSource,
      @Qualifier("slaveDataSource")DataSource slaveDataSource){

    RoutingDataSource routingDataSource =newRoutingDataSource();
    Map targetDataSources =newHashMap<>();
    targetDataSources.put("master", masterDataSource);
    targetDataSources.put("slave", slaveDataSource);
    routingDataSource.setTargetDataSources(targetDataSources);
    routingDataSource.setDefaultTargetDataSource(masterDataSource);

   returnroutingDataSource;
  }
}

// 使用AOP实现读写分离
@Aspect
@Component
publicclassReadWrite分离Aspect{
 @Around("execution(* com.example.repository.*Repository.*(..))")
 publicObjectrouteRead(JoinPoint joinPoint){
    DataSourceContextHolder.setDataSource("slave");
   try{
     returnjoinPoint.proceed();
    }finally{
      DataSourceContextHolder.clear();
    }
  }
}

5.5 连接池性能测试

在生产环境部署前,应该进行连接池性能测试,确保配置能够满足业务需求。

性能测试脚本示例:

// 使用JMH进行连接池性能测试
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
publicclassConnectionPoolBenchmark{
 privateDataSource dataSource;

 @Setup
 publicvoidsetup(){
    HikariConfig config =newHikariConfig();
    config.setJdbcUrl("jdbc//localhost:3306/mydb");
    config.setUsername("root");
    config.setPassword("password");
    config.setMaximumPoolSize(20);
    config.setMinimumIdle(5);
    config.setConnectionTimeout(30000);
    config.setIdleTimeout(600000);
    config.setMaxLifetime(1800000);
    dataSource =newHikariDataSource(config);
  }

 @Benchmark
 publicObjectgetConnection(Blackhole blackhole)throwsSQLException{
   try(Connection conn = dataSource.getConnection()) {
      blackhole.consume(conn);
     returnconn;
    }
  }

 @TearDown
 publicvoidtearDown(){
   if(dataSourceinstanceofHikariDataSource) {
      ((HikariDataSource) dataSource).close();
    }
  }
}

性能测试指标:

指标 目标值 说明
吞吐量 >1000 ops/s 每秒获取连接次数
平均响应时间 <5ms 获取连接的平均时间
99%响应时间 <50ms 99%分位数响应时间
错误率 0% 获取连接失败率

5.6 常见错误场景与解决方案

连接池耗尽问题的常见错误场景及对应的解决方案:

场景一:连接池配置过小

# 错误配置
spring:
datasource:
 hikari:
  maximum-pool-size:5# 过小

# 正确配置
spring:
datasource:
 hikari:
  maximum-pool-size:50# 根据并发量调整

场景二:SQL执行时间过长

// 错误代码
try{
  conn = dataSource.getConnection();
  Thread.sleep(10000); // 模拟慢SQL
  conn.createStatement().executeQuery("SELECT * FROM huge_table");
}finally{
  conn.close();
}

// 正确代码:优化SQL执行时间
// 1. 添加索引
// 2. 使用分页查询
// 3. 设置查询超时
PreparedStatement ps = conn.prepareStatement(sql);
ps.setQueryTimeout(30); // 30秒超时

场景三:连接未关闭

// 错误代码
Connection conn = dataSource.getConnection();
executeQuery(conn); // 使用后未关闭

// 正确代码:使用try-with-resources
try(Connection conn = dataSource.getConnection()) {
  executeQuery(conn);
} // 自动关闭

场景四:数据库连接数限制

-- 检查MySQL最大连接数
SHOWVARIABLESLIKE'max_connections';

-- 修改最大连接数(临时)
SETGLOBALmax_connections =500;

-- 修改最大连接数(永久,需在my.cnf配置)
[mysqld]
max_connections = 500

总结

数据库连接池耗尽是影响应用可用性的严重问题。问题的根因通常包括连接泄漏、连接池配置不当、SQL执行慢、数据库连接数限制。

排查连接池问题需要结合应用层和数据库层的数据。应用层关注连接池监控和线程堆栈,数据库层关注会话信息和慢查询日志。数据综合分析才能定位真正原因。

优化工作需要从多个维度入手。代码层面确保连接正确释放,推荐使用try-with-resources;配置层面合理设置连接池参数,考虑业务特性和数据库能力;SQL层面消除性能瓶颈,添加必要索引;数据库层面确保资源配置充足,调整服务器参数。

预防胜于治疗。建议建立连接池监控告警机制,及时发现异常;定期分析慢查询日志,持续优化SQL性能;做好容量规划,确保连接池配置与业务规模匹配;建立连接泄漏检测机制,在泄漏早期发现并修复。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据库
    +关注

    关注

    7

    文章

    4078

    浏览量

    68524
  • 应用程序
    +关注

    关注

    38

    文章

    3346

    浏览量

    60410

原文标题:生产环境数据库连接池耗尽:全流程排查与性能优化实战

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    关于数据库连接池总结

    数据库连接池小结
    发表于 04-30 08:07

    数据库连接池C3p0的使用方法

    数据库连接池C3p0的使用
    发表于 06-04 17:31

    数据库连接池技术在WEB系统开发中的应用

    在基于JDBC的数据库应用开发中,数据库连接的管理是决定该应用性能的一个重要因素。在对数据库连接
    发表于 09-11 16:25 14次下载

    数据库连接池技术在WEB系统开发中的应用

    在基于JDBC的数据库应用开发中,数据库连接的管理是决定该应用性能的一个重要因素。在对数据库连接
    发表于 09-12 17:11 18次下载

    什么是数据库连接池

    什么是数据库连接池       数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤
    发表于 06-17 07:42 1441次阅读

    关于 Redis连接池解析

    一、关于连接池 一个数据库服务器只拥有有限的资源,并且如果你没有充分使用这些资源,你可以通过使用更多的连接来提高吞吐量。一旦所有的资源都在使用,那么你就不能通过增加更多的连接来提高吞吐
    发表于 10-12 16:02 0次下载

    轨道交通系统中多客户端连接池动态分配策略

    针对轨道交通集群调度系统中数据库连接池参数一次性设定后不可修改的问题,设计面向多客户端的数据库连接池动态分配策略。通过使用动态分配算法,根据每个客户端访问频率的不同为当前客户端分配最优
    发表于 02-06 16:39 0次下载
    轨道交通系统中多客户端<b class='flag-5'>连接池</b>动态分配策略

    连接池工作原理

    连接池技术的核心思想是连接复用,通过建立一个数据库连接池以及一套连接使用、分配和管理策略,使得该连接池
    的头像 发表于 03-22 16:16 8456次阅读

    数据库连接池的优点

    本视频主要详细介绍了数据库连接池的优点,分别是资源重用、更快的系统响应速度、统一的连接管理,避免数据库连接泄漏、新的资源分配手段。
    的头像 发表于 03-22 16:24 1.1w次阅读

    数据库连接池工作机制

    数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再
    的头像 发表于 03-22 16:28 4452次阅读

    数据库连接池的设置怎么确定大小

    数据库连接池的配置是开发者们常常搞出坑的地方,在配置数据库连接池时,有几个可以说是和直觉背道而驰的原则需要明确。
    的头像 发表于 05-04 14:23 3506次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>连接池</b>的设置怎么确定大小

    教你怎么配置数据库连接池,保证无忧虑

    我在研究HikariCP(一个数据库连接池)时无意间在HikariCP的Github wiki上看到了一篇文章(即前面给出的链接),这篇文章有力地消除了我一直以来的疑虑,看完之后感觉神清气爽。故在此做译文分享。
    发表于 08-10 16:34 1625次阅读
    教你怎么配置<b class='flag-5'>数据库</b><b class='flag-5'>连接池</b>,保证无忧虑

    MySQL数据库性能优化的意义及其措施

    数据库性能优化的常见手段有很多,比如添加索引、分库分表、优化连接池
    的头像 发表于 02-03 14:12 2164次阅读

    了解连接池、线程、内存、异步请求

    可被重复使用像常见的线程、内存连接池、对象都具有以上的共同特点。 连接池 什么是数据库
    的头像 发表于 11-09 14:44 2408次阅读
    了解<b class='flag-5'>连接池</b>、线程<b class='flag-5'>池</b>、内存<b class='flag-5'>池</b>、异步请求<b class='flag-5'>池</b>

    MySQL与Redis数据库连接池应用

    一、概念 数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个
    的头像 发表于 11-10 16:40 1444次阅读
    MySQL与Redis<b class='flag-5'>数据库</b><b class='flag-5'>连接池</b>应用