Соединение Java ConnectionPool не закрывается, застрял в "спящем режиме"

StackOverflow https://stackoverflow.com/questions/53128

  •  09-06-2019
  •  | 
  •  

Вопрос

У меня есть веб-приложение, которое использует запросы JNDI для получения подключения к базе данных.

Соединение работает нормально и возвращает запрос без проблем.Проблема в том, что соединение не закрывается должным образом и застряло в "спящем" режиме (по словам администратора mysql).Это означает, что они становятся непригодными для использования, и тогда у меня заканчиваются соединения.

Кто-нибудь может дать мне несколько советов относительно того, что я могу сделать, чтобы соединение успешно вернулось в пул.

public class DatabaseBean {

private static final Logger logger = Logger.getLogger(DatabaseBean.class);

private Connection conn;
private PreparedStatement prepStmt;

/**
 * Zero argument constructor
 * Setup generic databse connection in here to avoid redundancy
 * The connection details are in /META-INF/context.xml
 */
public DatabaseBean() {
    try {
        InitialContext initContext = new InitialContext();
        DataSource ds = (DataSource) initContext.lookup("java:/comp/env/jdbc/mysite");
        conn = ds.getConnection();
    }
    catch (SQLException SQLEx) {
        logger.fatal("There was a problem with the database connection.");
        logger.fatal(SQLEx);
        logger.fatal(SQLEx.getCause());
    }
    catch (NamingException nameEx) {
        logger.fatal("There was a naming exception");
        logger.fatal(nameEx);
        logger.fatal(nameEx.getCause());
    }
}

/**
 * Execute a query. Do not use for statements (update delete insert etc).
 *
 * @return A ResultSet of the execute query. A set of size zero if no results were returned. It is never null.
 * @see #executeUpdate() for running update, insert delete etc.
 */

public ResultSet executeQuery() {
    ResultSet result = null;
    try {
        result = prepStmt.executeQuery();
        logger.debug(prepStmt.toString());
    }
    catch (SQLException SQLEx) {
        logger.fatal("There was an error running a query");
        logger.fatal(SQLEx);
    }
    return result;
}

ОТРЕЗАТЬ

public void close() {
    try {
        prepStmt.close();
        prepStmt = null;

        conn.close();
        conn = null;
    } catch (SQLException SQLEx) {
        logger.warn("There was an error closing the database connection.");
    }
}
}

Это находится внутри javabean, который использует подключение к базе данных.

public LinkedList<ImportantNoticeBean> getImportantNotices() {

    DatabaseBean noticesDBBean = new DatabaseBean();
    LinkedList<ImportantNoticeBean> listOfNotices = new LinkedList<ImportantNoticeBean>();

    try {
        PreparedStatement preStmt = noticesDBBean.getConn().prepareStatement("SELECT pseudonym, message, date_to, date_from " +
                "FROM importantnotices, users " +
                "WHERE importantnotices.username = users.username " +
                "AND NOW() >= date_from AND NOW() <= date_to;");

        noticesDBBean.setPrepStmt(preStmt);
        ResultSet result = noticesDBBean.executeQuery();

        while (result.next()) {
            ImportantNoticeBean noticeBean = new ImportantNoticeBean();

            noticeBean.setAuthor(result.getString("pseudonym"));
            noticeBean.setMessage(result.getString("message"));
            noticeBean.setDateTo(result.getDate("date_to"));
            noticeBean.setDateFrom(result.getDate("date_from"));

            listOfNotices.add(noticeBean);
        }

        result.close();

    } catch (SQLException SQLEx) {
        logger.error("There was an error in ImportantNoticesBean.getImportantNotices()");
        logger.error(SQLEx);
    } finally {
        noticesDBBean.close();
    }
    return listOfNotices;
}

<Context reloadable="true">

    <Resource name="jdbc/mysite"
              auth="Container"
              type="javax.sql.DataSource"
              username="user"
              password="password"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/mysite"
              maxActive="10"
              maxIdle="5"
              maxWait="6000"
              removeAbandoned="true"
              logAbandoned="false"
              removeAbandonedTimeout="20"
            />
</Context>
Это было полезно?

Решение 4

Проблема в том, что соединение не закрывается должным образом и застряло в "спящем" режиме

На самом деле это было верно только наполовину.

Проблема, с которой я столкнулся, на самом деле заключалась в том, что каждое приложение определяло новое соединение с сервером базы данных.Поэтому каждый раз, когда я закрывал все подключения, приложение A создавало кучу новых подключений в соответствии с его конфигурационным файлом WEB.xml и работало успешно.Приложение B сделало бы то же самое.Проблема в том, что они независимые пулы которые пытаются захватить до определенного сервером предела.Я думаю, это своего рода гоночное условие.Таким образом, когда приложение A завершает работу с подключениями, оно ожидает, чтобы использовать их снова, пока не истечет тайм-аут, в то время как приложению B, которому сейчас требуется соединение, отказано в ресурсах, даже если приложение A завершило работу с подключениями и должно вернуться в пул.По истечении тайм-аута соединение освобождается, и B (или C и т.д.) Может снова подключиться к нему.

например ,если ограничение равно 10 (ограничение профиля MySQL) и каждое приложение настроено на использование максимум 10, то будет предпринято 20 попыток подключения.Очевидно, что это плохая ситуация.

Решение состоит в том, чтобы использовать RTFM и поместить сведения о подключении в нужном месте.Это действительно затрудняет совместное размещение, но есть способы обойти это (например, создание ссылок на другие xml-файлы из контекста).

Просто чтобы быть откровенным:Я поместил данные о подключении в раздел WEB.xml для каждого приложения, и они поссорились из-за этого.

Другие советы

Кажется, вы правильно закрываете соединение - за исключением случая, когда prepStmt.close() выдает SQLException, я не могу найти утечку соединения.

Какую реализацию пула вы используете?Когда вы закрываете соединение, пулу не нужно немедленно закрывать базовое соединение MySQL - в конце концов, это точка пула соединений!Таким образом, со стороны MySQL соединения будут выглядеть живыми, хотя ваше приложение не использует никаких;они могут просто храниться в пуле подключений TC.

Возможно, вы захотите поэкспериментировать с настройками пула подключений.Попросите его уменьшить пул, когда система простаивает.Или попросите его периодически обновлять все подключения.Или иметь строгую верхнюю границу количества одновременных подключений, которые он когда-либо получает от MySQL и т.д.

Один из способов проверить, есть ли в вашем коде утечка соединения, - заставить ds.getConnection() всегда открывать новое физическое соединение, а conn.close() - разорвать соединение (если в вашем пуле подключений есть для этого настройки).Затем, если вы понаблюдаете за подключениями на стороне MySQL, возможно, вам удастся выяснить, действительно ли в коде произошла утечка соединения или нет.

Это похожий вопрос - Настройки пула подключений для Tomcat

Это мой ответ на этот вопрос, и он устранил проблему для другого парня.Это тоже может помочь вам.

Документация Tomcat

DBCP использует пул подключений к базе данных Jakarta-Commons.Он опирается на ряд общедоступных компонентов:

* Jakarta-Commons DBCP
* Jakarta-Commons Collections
* Jakarta-Commons Pool

Я использую тот же пул подключений, и я устанавливаю эти свойства, чтобы предотвратить то же самое, что просто не настроено через tomcat.Но если первое не сработает, попробуйте вот это.

testWhileIdle=true
timeBetweenEvictionRunsMillis=300000

Ладно, возможно, я с этим разберусь.Я изменил ресурс конфигурации базы данных на следующий:

*SNIP*
maxActive="10"
maxIdle="5"
maxWait="7000"
removeAbandoned="true"
logAbandoned="false"
removeAbandonedTimeout="3"
*SNIP*

На данный момент это работает достаточно хорошо.Что происходит, afaik, так это то, что как только я достигаю десяти подключений, Tomcat проверяет наличие заброшенных подключений (время простоя> 3).Он делает это в пакетном задании каждый раз, когда достигается максимальное количество подключений.Потенциальная проблема с этим заключается в том, что мне нужно, чтобы одновременно выполнялось более 10 запросов (не уникальных для меня).Важно то, что removeAbandonedTimeout меньше, чем maxWait.

Это то, что должно происходить?т.е. Именно так должен работать пул?Если это так, то, по крайней мере, мне кажется, что вы бы подождали, пока что-то (соединение) не будет разорвано, прежде чем исправлять, а не позволять ему "разорваться" в первую очередь.Может быть, я до сих пор этого не понимаю.

Одна вещь, которую @binil пропустил, вы не закрываете результирующий набор в случае исключения.В зависимости от реализации драйвера это может привести к тому, что соединение останется открытым.Переместите вызов result.close() в блок finally.

Я использую ту же конфигурацию, что и вы.Если соединение в mysql administrator (Windows) показывает, что оно находится в спящем режиме, это означает только, что оно объединено в пул, но не используется.Я проверил это, запустив тестовую программу с несколькими потоками, выполняющими случайные запросы к Mysql.если это поможет, вот моя конфигурация:

        defaultAutoCommit="false"
        defaultTransactionIsolation="REPEATABLE_READ"
        auth="Container"
        type="javax.sql.DataSource"
        logAbandoned="true" 
          removeAbandoned="true"
        removeAbandonedTimeout="300" 
        maxActive="-1"
        initialSize="15"
        maxIdle="10"
        maxWait="10000" 
        username="youruser"
        password="youruserpassword"
        driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://yourhost/yourdatabase"/>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top