vlambda博客
学习文章列表

数据库连接池是如何管理 Connection 的?

为什么有这个疑惑,两个原因。

第一、有个生产环境应用运行一段时间就会报 Connection closed 的异常;

第二、从架构师封装的框架里拿了 Connection,我就问他要不要手动 close 掉,他的回答是关不关都可以。这个答案让我非常困惑。


数据库连接池的种类挺多的,如 C3P0、DBCP、Druid、HikariCP 等。其中 Druid 和 HikariCP 特点最明显,前者功能最全、为监控而生;后者性能最佳。今天得空,就翻了下 Druid 的源码去寻找答案。


先说个背景知识,JDK 1.4 推出了 javax.sql.DataSource 接口,Connection 的工厂;官方文档给出说明,实现这个接口主要来处理 Connection 相关的基础功能、Connection 连接池、分布式事务。


Druid 的核心类之一 DruidDataSource,间接实现了 DataSource 接口。该类 getConnection 方法调用了 getConnectionDirect 方法,加了点注释如下
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
        int notFullTimeoutRetryCnt = 0;
        for (;;) {
            // 超时次数与连接池是否已满处理
            DruidPooledConnection poolableConnection;
            try {
                poolableConnection = getConnectionInternal(maxWaitMillis);
            } catch (GetConnectionTimeoutException ex) {
                if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
                    notFullTimeoutRetryCnt++;
                    if (LOG.isWarnEnabled()) {
                        LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
                    }
                    continue;
                }
                throw ex;
            }

            //是否检测 Connection 有效性
            if (testOnBorrow) {
                //使用 validationQuery 配置的 sql 检测 Connection 是否有效
                boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                if (!validate) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("skip not validate connection.");
                    }
                    //丢弃 Connection
                    discardConnection(poolableConnection.holder);
                    continue;
                }
            } else {
                //Connection已关闭,丢弃
                if (poolableConnection.conn.isClosed()) {
                    discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
                    continue;
                }
                //申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery sql 检测连接是否有效
                if (testWhileIdle) {
                    final DruidConnectionHolder holder = poolableConnection.holder;
                    long currentTimeMillis             = System.currentTimeMillis();
                    long lastActiveTimeMillis          = holder.lastActiveTimeMillis;
                    long lastExecTimeMillis            = holder.lastExecTimeMillis;
                    long lastKeepTimeMillis            = holder.lastKeepTimeMillis;

                    if (checkExecuteTime
                            && lastExecTimeMillis != lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastExecTimeMillis;
                    }

                    if (lastKeepTimeMillis > lastActiveTimeMillis) {
                        lastActiveTimeMillis = lastKeepTimeMillis;
                    }

                    long idleMillis                    = currentTimeMillis - lastActiveTimeMillis;

                    long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;

                    if (timeBetweenEvictionRunsMillis <= 0) {
                        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
                    }

                    if (idleMillis >= timeBetweenEvictionRunsMillis
                            || idleMillis < 0) {
                        boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
                        if (!validate) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("skip not validate connection.");
                            }

                            discardConnection(poolableConnection.holder);
                             continue;
                        }
                    }
                }
            }
            //关闭长时间不使用的 Connection 的处理逻辑
            if (removeAbandoned) {
                StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
                poolableConnection.connectStackTrace = stackTrace;
                poolableConnection.setConnectedTimeNano();
                poolableConnection.traceEnable = true;

                activeConnectionLock.lock();
                try {
                    activeConnections.put(poolableConnection, PRESENT);
                } finally {
                    activeConnectionLock.unlock();
                }
            }
            //设置是否自动提交
            if (!this.defaultAutoCommit) {
                poolableConnection.setAutoCommit(false);
            }

            return poolableConnection;
        }
    }

从上面 getConnectionDirect 的代码可以看出,想要解决 Connection closed 的问题,可以

  • 开启 testOnBorrow,配上 validationQuery 执行的 sql;
  • 开启 testWhileIdle,配上 timeBetweenEvictionRunsMillis 空闲时间,超过进行 Connection 校验。



疑惑二,是否要手动关闭拿到的 Connection,即是否要调用 Connection 的 close() 的方法?

会对这个问题产生疑惑的原因就是,我把 Connection 关闭了,不就发挥不了数据库连接池的作用了吗?再次获取 Connection 还需要重新连接数据库。

要解答这个问题,还是以 Druid 为例,上面 getConnection 方法返回的是  DruidPooledConnection 对象,该对象的 close 方法如下
@Override
public void close() throws SQLException {
    if (this.disable) {
        return;
    }

    DruidConnectionHolder holder = this.holder;
    if (holder == null) {
        if (dupCloseLogEnable) {
            LOG.error("dup close");
        }
        return;
    }

    DruidAbstractDataSource dataSource = holder.getDataSource();
    //持有连接的线程与当前线程是否相同的判断及相应处理
    boolean isSameThread = this.getOwnerThread() == Thread.currentThread();
    if (!isSameThread) {
        dataSource.setAsyncCloseConnectionEnable(true);
    }

    if (dataSource.isAsyncCloseConnectionEnable()) {
        syncClose();
        return;
    }

    for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {
        listener.connectionClosed(new ConnectionEvent(this));
    }

    //监控链的处理
    List<Filter> filters = dataSource.getProxyFilters();
    if (filters.size() > 0) {
        FilterChainImpl filterChain = new FilterChainImpl(dataSource);
        filterChain.dataSource_recycle(this);
    } else {
        //连接回收
        recycle();
    }

    this.disable = true;
}

//Connection 回收方法
public void recycle() throws SQLException {
    if (this.disable) {
        return;
    }

    DruidConnectionHolder holder = this.holder;
    if (holder == null) {
        if (dupCloseLogEnable) {
            LOG.error("dup close");
        }
        return;
    }

    if (!this.abandoned) {
        DruidAbstractDataSource dataSource = holder.getDataSource();
        //连接池回收该连接,但未关闭物理 Connection,可以通过配置进一步校验它的有效性
        dataSource.recycle(this);
    }

    this.holder = null;
    conn = null;
    transactionInfo = null;
    closed = true;
}

close 方法里主要提现的是 Connection 的回收逻辑,未并非是真的 close 物理连接。

所以我认为这个问题的答案是,必须调用 Connection 的 close 方法进行回收,这里并不会真的如其方法名那样 close 与数据库的物理连接,所以不会导致无法重用 Connection。相反地,如果不回收 Connection,可能会导致连接泄漏的问题,尽管 removeAbandoned 相关配置可以回收掉长时间连接的 Connection,但官方不推荐生产使用、仅用来排查连接泄漏问题。


 |      |