乐趣区

关于druid:聊聊druid的handleException

本文次要钻研一下 druid 的 handleException

prepareStatement

com/alibaba/druid/pool/DruidPooledConnection.java

    public PreparedStatement prepareStatement(String sql) throws SQLException {checkState();

        PreparedStatementHolder stmtHolder = null;
        PreparedStatementKey key = new PreparedStatementKey(sql, getCatalog(), MethodType.M1);

        boolean poolPreparedStatements = holder.isPoolPreparedStatements();

        if (poolPreparedStatements) {stmtHolder = holder.getStatementPool().get(key);
        }

        if (stmtHolder == null) {
            try {stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql));
                holder.getDataSource().incrementPreparedStatementCount();
            } catch (SQLException ex) {handleException(ex, sql);
            }
        }

        initStatement(stmtHolder);

        DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement(this, stmtHolder);

        holder.addTrace(rtnVal);

        return rtnVal;
    }

DruidPooledConnection 的 prepareStatement 会 catch 住 SQLException 而后执行 handleException

executeQuery

com/alibaba/druid/pool/DruidPooledPreparedStatement.java

    public ResultSet executeQuery() throws SQLException {checkOpen();

        incrementExecuteQueryCount();
        transactionRecord(sql);

        oracleSetRowPrefetch();

        conn.beforeExecute();
        try {ResultSet rs = stmt.executeQuery();

            if (rs == null) {return null;}

            DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);
            addResultSetTrace(poolableResultSet);

            return poolableResultSet;
        } catch (Throwable t) {errorCheck(t);

            throw checkException(t);
        } finally {conn.afterExecute();
        }
    }

executeQuery 在 catch 到 Throwable 时会执行 throw checkException(t)

checkException

com/alibaba/druid/pool/DruidPooledStatement.java

    protected SQLException checkException(Throwable error) throws SQLException {
        String sql = null;
        if (this instanceof DruidPooledPreparedStatement) {sql = ((DruidPooledPreparedStatement) this).getSql();}

        handleSocketTimeout(error);

        exceptionCount++;
        return conn.handleException(error, sql);
    }

checkException 这里会执行 conn.handleException(error, sql)

handleException

    public SQLException handleException(Throwable t, String sql) throws SQLException {
        final DruidConnectionHolder holder = this.holder;

        //
        if (holder != null) {DruidAbstractDataSource dataSource = holder.getDataSource();
            dataSource.handleConnectionException(this, t, sql);
        }

        if (t instanceof SQLException) {throw (SQLException) t;
        }

        throw new SQLException("Error", t);
    }

handleException 这里是委托给了 dataSource.handleConnectionException(this, t, sql);

handleConnectionException

com/alibaba/druid/pool/DruidDataSource.java

    public void handleConnectionException(DruidPooledConnection pooledConnection,
                                          Throwable t,
                                          String sql) throws SQLException {final DruidConnectionHolder holder = pooledConnection.getConnectionHolder();
        if (holder == null) {return;}

        errorCountUpdater.incrementAndGet(this);
        lastError = t;
        lastErrorTimeMillis = System.currentTimeMillis();

        if (t instanceof SQLException) {SQLException sqlEx = (SQLException) t;

            // broadcastConnectionError
            ConnectionEvent event = new ConnectionEvent(pooledConnection, sqlEx);
            for (ConnectionEventListener eventListener : holder.getConnectionEventListeners()) {eventListener.connectionErrorOccurred(event);
            }

            // exceptionSorter.isExceptionFatal
            if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) {handleFatalError(pooledConnection, sqlEx, sql);
            }

            throw sqlEx;
        } else {throw new SQLException("Error", t);
        }
    }

handleConnectionException 办法在 exceptionSorter.isExceptionFatal(sqlEx) 为 true 时会执行 handleFatalError

isExceptionFatal

com/alibaba/druid/pool/vendor/MySqlExceptionSorter.java

public class MySqlExceptionSorter implements ExceptionSorter {
    @Override
    public boolean isExceptionFatal(SQLException e) {if (e instanceof SQLRecoverableException) {return true;}

        final String sqlState = e.getSQLState();
        final int errorCode = e.getErrorCode();

        if (sqlState != null && sqlState.startsWith("08")) {return true;}

        switch (errorCode) {
            // Communications Errors
            case 1040: // ER_CON_COUNT_ERROR
            case 1042: // ER_BAD_HOST_ERROR
            case 1043: // ER_HANDSHAKE_ERROR
            case 1047: // ER_UNKNOWN_COM_ERROR
            case 1081: // ER_IPSOCK_ERROR
            case 1129: // ER_HOST_IS_BLOCKED
            case 1130: // ER_HOST_NOT_PRIVILEGED
                // Authentication Errors
            case 1045: // ER_ACCESS_DENIED_ERROR
                // Resource errors
            case 1004: // ER_CANT_CREATE_FILE
            case 1005: // ER_CANT_CREATE_TABLE
            case 1015: // ER_CANT_LOCK
            case 1021: // ER_DISK_FULL
            case 1041: // ER_OUT_OF_RESOURCES
                // Out-of-memory errors
            case 1037: // ER_OUTOFMEMORY
            case 1038: // ER_OUT_OF_SORTMEMORY
                // Access denied
            case 1142: // ER_TABLEACCESS_DENIED_ERROR
            case 1227: // ER_SPECIFIC_ACCESS_DENIED_ERROR

            case 1023: // ER_ERROR_ON_CLOSE

            case 1290: // ER_OPTION_PREVENTS_STATEMENT
                return true;
            default:
                break;
        }

        // for oceanbase
        if (errorCode >= -9000 && errorCode <= -8000) {return true;}

        String className = e.getClass().getName();
        if (className.endsWith(".CommunicationsException")) {return true;}

        String message = e.getMessage();
        if (message != null && message.length() > 0) {if (message.startsWith("Streaming result set com.mysql.jdbc.RowDataDynamic")
                    && message.endsWith("is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries.")) {return true;}

            final String errorText = message.toUpperCase();

            if ((errorCode == 0 && (errorText.contains("COMMUNICATIONS LINK FAILURE")) //
                    || errorText.contains("COULD NOT CREATE CONNECTION")) //
                    || errorText.contains("NO DATASOURCE") //
                    || errorText.contains("NO ALIVE DATASOURCE")) {return true;}
        }

        Throwable cause = e.getCause();
        for (int i = 0; i < 5 && cause != null; ++i) {if (cause instanceof SocketTimeoutException) {return true;}

            className = cause.getClass().getName();
            if (className.endsWith(".CommunicationsException")) {return true;}

            cause = cause.getCause();}

        return false;
    }

    @Override
    public void configFromProperties(Properties properties) {}}

MySqlExceptionSorter 针对指定错误码来判断是否 fatal

handleFatalError

com/alibaba/druid/pool/DruidDataSource.java

    protected final void handleFatalError(DruidPooledConnection conn,
                                          SQLException error,
                                          String sql) throws SQLException {
        final DruidConnectionHolder holder = conn.holder;

        if (conn.isTraceEnable()) {activeConnectionLock.lock();
            try {if (conn.isTraceEnable()) {activeConnections.remove(conn);
                    conn.setTraceEnable(false);
                }
            } finally {activeConnectionLock.unlock();
            }
        }

        long lastErrorTimeMillis = this.lastErrorTimeMillis;
        if (lastErrorTimeMillis == 0) {lastErrorTimeMillis = System.currentTimeMillis();
        }

        if (sql != null && sql.length() > 1024) {sql = sql.substring(0, 1024);
        }

        boolean requireDiscard = false;
        final ReentrantLock lock = conn.lock;
        lock.lock();
        try {if ((!conn.isClosed()) || !conn.isDisable()) {conn.disable(error);
                requireDiscard = true;
            }

            lastFatalErrorTimeMillis = lastErrorTimeMillis;
            fatalErrorCount++;
            if (fatalErrorCount - fatalErrorCountLastShrink > onFatalErrorMaxActive) {onFatalError = true;}
            lastFatalError = error;
            lastFatalErrorSql = sql;
        } finally {lock.unlock();
        }

        if (onFatalError && holder != null && holder.getDataSource() != null) {ReentrantLock dataSourceLock = holder.getDataSource().lock;
            dataSourceLock.lock();
            try {emptySignal();
            } finally {dataSourceLock.unlock();
            }
        }

        if (requireDiscard) {if (holder.statementTrace != null) {holder.lock.lock();
                try {for (Statement stmt : holder.statementTrace) {JdbcUtils.close(stmt);
                    }
                } finally {holder.lock.unlock();
                }
            }

            this.discardConnection(holder);
        }

        // holder.
        LOG.error("{conn-" + holder.getConnectionId() + "} discard", error);
    }

handleFatalError 办法这里会执行 conn.disable(error),而后标记 requireDiscard 为 true,最初执行 discardConnection

discardConnection

com/alibaba/druid/pool/DruidDataSource.java

    public void discardConnection(DruidConnectionHolder holder) {if (holder == null) {return;}

        Connection conn = holder.getConnection();
        if (conn != null) {JdbcUtils.close(conn);
        }

        lock.lock();
        try {if (holder.discard) {return;}

            if (holder.active) {
                activeCount--;
                holder.active = false;
            }
            discardCount++;

            holder.discard = true;

            if (activeCount <= minIdle) {emptySignal();
            }
        } finally {lock.unlock();
        }
    }

discardConnection 这里会通过 JdbcUtils.close(conn) 敞开连贯,而后加锁判断是否小于等于 minIdle,若为 true 则执行 emptySignal

小结

druid 会在 prepareStatement 或者执行 prepareStatement 出现异常的时候执行 conn.handleException,它委托给 dataSource.handleConnectionException,后者会在 exceptionSorter.isExceptionFatal(sqlEx) 为 true 时会执行 handleFatalError,handleFatalError 办法这里会执行 conn.disable(error),而后标记 requireDiscard 为 true,最初执行 discardConnection 来敞开连贯。通过这样子来疾速清理不可用的连贯,防止连接池的连贯不可用。

退出移动版