c3p0连接池耗尽致应用挂起及代码优化相关技术咨询
C3P0连接池耗尽问题排查与优化方案
先贴出你的代码:
import com.mchange.v2.c3p0.ComboPooledDataSource; import java.sql.Connection; import java.sql.SQLException; import java.io.IOException; import com.mchange.v2.c3p0.PropertyVetoException; public class DBHandler { private static DBHandler datasource; private static ComboPooledDataSource cpds; private static String user,ip; private static String db=""; static DBHandler database; static Connection con; static { ip="my ip address"; db="test"; } public static Connection dbconnection(){ try { if(database==null){ database=new DBHandler(); } if(cpds.getNumConnections()==cpds.getMaxPoolSize()){ //cpds.close();//what will happen if i use close() like this. database=new DBHandler();//will this line creates a new datasource } return database.getConnection(); } catch (Exception e) { } return null; } DBHandler() throws IOException, SQLException, PropertyVetoException { String url; if(ip.startsWith("sqlserver")) { url="jdbc:" +ip+";databaseName="+db+";selectMethod=cursor"; user="user"; } else { url="jdbc:" + ip + "/"+db; user="root"; } cpds = new ComboPooledDataSource(); cpds.setDriverClass("com.microsoft.sqlserver.jdbc.SQLServerDriver"); //loads the jdbc driver cpds.setJdbcUrl(url); cpds.setUser("user"); cpds.setPassword("password"); cpds.setMinPoolSize(2); cpds.setAcquireIncrement(1); cpds.setMaxPoolSize(15); cpds.setMaxIdleTime(120); } public static DBHandler getInstance() throws IOException, SQLException, PropertyVetoException { if (datasource == null) { datasource = new DBHandler(); return datasource; } else { return datasource; } } public Connection getConnection() throws SQLException { Connection con=null; con= this.cpds.getConnection(); return con; } }
核心问题分析
你的应用连接耗尽挂起,根源是连接泄漏+连接池实现逻辑错误:
- 当连接池满时,你重新创建
DBHandler实例,但因为cpds是静态变量,这会直接覆盖旧的连接池引用,旧池持有的数据库连接会因失去引用被丢弃,却没有主动关闭,导致数据库端连接被持续占用。 - 大概率你在使用连接后,没有在
finally块中正确关闭Connection、Statement、ResultSet,导致连接无法归还到池中。
疑问解答
疑问1:创建新DBHandler实例会生成新数据源还是同一连接池的引用?
因为cpds是静态成员变量,每次调用new DBHandler()时,构造函数都会执行cpds = new ComboPooledDataSource(),这会直接覆盖全局的cpds引用,生成全新的数据源。但旧数据源会被GC回收,它持有的数据库连接却不会被自动关闭,这些连接会一直占用数据库资源,反而加速连接耗尽——这是完全错误的做法,绝对不能这么干。
疑问2:如何实现通用的空闲连接关闭方法?setMaxIdleTime是否生效?
C3P0的setMaxIdleTime是完全生效的,它会自动关闭空闲时间超过设定值(你这里是120秒)的连接,不需要手动实现空闲连接关闭逻辑。你当前的问题不是空闲连接没被回收,而是使用后的连接没有正确归还到池中,或者连接池被错误重复创建导致的连接泄漏。
解决方案(纯核心技术实现)
1. 修复连接池单例实现
确保整个应用只有一个连接池实例,修改为线程安全的单例模式:
import com.mchange.v2.c3p0.ComboPooledDataSource; import java.sql.Connection; import java.sql.SQLException; import java.io.IOException; import com.mchange.v2.c3p0.PropertyVetoException; public class DBHandler { // 线程安全的单例实例 private static volatile DBHandler instance; // 非静态连接池,每个单例对应唯一池 private final ComboPooledDataSource cpds; private static String ip; private static String db; static { ip = "my ip address"; db = "test"; } // 私有构造函数,禁止外部实例化 private DBHandler() throws IOException, SQLException, PropertyVetoException { String url; String user; if(ip.startsWith("sqlserver")) { url = "jdbc:" + ip + ";databaseName=" + db + ";selectMethod=cursor"; user = "user"; } else { url = "jdbc:" + ip + "/" + db; user = "root"; } cpds = new ComboPooledDataSource(); cpds.setDriverClass("com.microsoft.sqlserver.jdbc.SQLServerDriver"); cpds.setJdbcUrl(url); cpds.setUser(user); cpds.setPassword("password"); cpds.setMinPoolSize(2); cpds.setAcquireIncrement(1); cpds.setMaxPoolSize(15); cpds.setMaxIdleTime(120); // 新增:设置获取连接超时,避免挂起 cpds.setCheckoutTimeout(5000); } // 双重检查锁实现线程安全单例 public static DBHandler getInstance() throws IOException, SQLException, PropertyVetoException { if (instance == null) { synchronized (DBHandler.class) { if (instance == null) { instance = new DBHandler(); } } } return instance; } public Connection getConnection() throws SQLException { return cpds.getConnection(); } // 应用退出时调用,关闭连接池 public void closePool() { if (cpds != null) { cpds.close(); } } }
2. 确保连接被正确归还
使用连接时,必须在finally块中按逆序关闭资源,确保连接归还到池中:
// 示例:正确使用连接的模板 public void executeSampleQuery() { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { conn = DBHandler.getInstance().getConnection(); stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT * FROM test_table"); // 处理结果集逻辑 } catch (Exception e) { e.printStackTrace(); } finally { // 按ResultSet -> Statement -> Connection的顺序关闭 try { if (rs != null) rs.close(); } catch (SQLException e) {} try { if (stmt != null) stmt.close(); } catch (SQLException e) {} try { if (conn != null) conn.close(); } catch (SQLException e) {} } }
3. 实现连接池状态定时监控(可选)
如果需要主动监控连接池状态,可以用ScheduledExecutorService定时检查,辅助排查问题:
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class DBHandler { // ... 其他代码 ... private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private DBHandler() throws IOException, SQLException, PropertyVetoException { // ... 原有构造代码 ... // 每5分钟打印一次连接池状态 scheduler.scheduleAtFixedRate(() -> { if (cpds != null) { try { System.out.println("当前总连接数: " + cpds.getNumConnections()); System.out.println("空闲连接数: " + cpds.getNumIdleConnections()); System.out.println("忙碌连接数: " + cpds.getNumBusyConnections()); } catch (SQLException e) { e.printStackTrace(); } } }, 0, 5, TimeUnit.MINUTES); } // 关闭池时同时关闭调度器 public void closePool() { scheduler.shutdown(); if (cpds != null) { cpds.close(); } } }
4. 禁止在业务逻辑中关闭连接池
你之前注释的cpds.close()绝对不能在dbconnection方法中调用——调用该方法会关闭整个连接池,所有连接都会被销毁,后续获取连接会直接抛出异常。
内容的提问来源于stack exchange,提问作者mani
相关产品推荐
相关产品推荐

