← Назад к вопросам

Что будет, если не закрыть соединение с базой данных, после использования

2.3 Middle🔥 201 комментариев
#Stream API и функциональное программирование#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Незакрытые соединения с БД: последствия

Если не закрыть соединение с БД — это приведёт к catastrophic последствиям в production. Это одна из самых частых причин падения приложений, потому что проблема проявляется не сразу, а накапливается со временем.

Что происходит пошагово

1. Connection leak

Каждый раз когда ты открываешь соединение и не закрываешь:

// НЕПРАВИЛЬНО
Connection conn = DriverManager.getConnection("jdbc:postgresql://localhost/mydb");
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM users");
while (rs.next()) {
    System.out.println(rs.getString("name"));
}
// Ты забыл закрыть rs, statement и conn!
// Соединение остаётся в памяти

До нескольких заявок, приложение работает нормально. Но потом:

2. Истощение пула соединений

Операционная система выделяет ограниченное количество соединений (обычно 100-1000):

Время: 0s
Открытых соединений: 0/20 (limit)

Время: 1s (100 запросов)
Открытых соединений: 100/20 (limit)
↓
"Too many open files" ошибка!

3. Ошибка в логе

java.sql.SQLException: Cannot get a connection, pool error Timeout waiting 
for idle object

at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:181)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:88)

4. Application Freeze

Все новые запросы, которые требуют БД, зависнут:

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        // Вызов зависает потому что нет свободных соединений!
        User user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
}

Пользователи видят:

Timeout after 30 seconds waiting for connection

IllegalStateException: HikariPool is closed


#### 5. **Application Restart Required**

Один способ "исправить" — перезагрузить приложение. Но если код не исправлен, проблема повторится через несколько минут.

### Примеры утечек (leak patterns)

#### **Антипаттерн 1: Забыли закрыть в try-catch**

```java

// ПЛОХО public List<User> getAllUsers() { Connection conn = null; try { conn = dataSource.getConnection(); // ... return users; } catch (SQLException e) { logger.error("Error", e); return Collections.emptyList(); } // ОПА! conn НЕ закрыт! }

Антипаттерн 2: Исключение перед close()

// ПЛОХО public void updateUser(User user) { Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement("UPDATE users SET name = ?"); stmt.setString(1, user.getName()); stmt.executeUpdate(); // Если здесь исключение stmt.close(); // Это не выполнится! conn.close(); // И это тоже! }

Антипаттерн 3: Вложенные ресурсы

// ПЛОХО

Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(query);
ResultSet rs = stmt.executeQuery();

// Если закрыть только conn, то stmt и rs остаются открытыми

conn.close();

ПРАВИЛЬНЫЕ подходы

Способ 1: Try-with-resources (Java 7+)

Это best practice. Автоматически закрывает все ресурсы:

// ПРАВИЛЬНО public List<User> getAllUsers() { String query = "SELECT id, name FROM users"; List<User> users = new ArrayList<>();

// Всё закроется автоматически, даже при исключении!
try (Connection conn = dataSource.getConnection();
     PreparedStatement stmt = conn.prepareStatement(query);
     ResultSet rs = stmt.executeQuery()) {
    
    while (rs.next()) {
        users.add(new User(rs.getLong("id"), rs.getString("name")));
    }
} catch (SQLException e) {
    logger.error("Database error", e);
}

return users;

}

Как это работает: класс реализует AutoCloseable интерфейс:

public interface AutoCloseable { void close() throws Exception; }

// Connection, PreparedStatement, ResultSet все реализуют AutoCloseable

Java автоматически вызывает .close() при выходе из try блока.

Способ 2: Traditional try-finally

Для старых версий Java:

// ПРАВИЛЬНО (для Java < 7)

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;

try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(query);
rs = stmt.executeQuery();
// ...
} catch (SQLException e) {
logger.error("Error", e);
} finally {
// Закрываем в обратном порядке!
if (rs != null) try { rs.close(); } catch (SQLException e) { }
if (stmt != null) try { stmt.close(); } catch (SQLException e) { }
if (conn != null) try { conn.close(); } catch (SQLException e) { }

}

Примечание: важен порядок! Закрываем в обратном порядке открытия (LIFO).

Способ 3: Connection pooling (рекомендуется)

Примечание: не открываешь/закрываешь соединение, а берёшь из пула:

@Configuration

public class DataSourceConfig { @Bean public DataSource dataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:postgresql://localhost/mydb"); config.setUsername("user"); config.setPassword("password"); config.setMaximumPoolSize(20); // Max 20 соединений config.setMinimumIdle(5); // Min 5 всегда открыто config.setConnectionTimeout(30000); config.setIdleTimeout(600000);

    return new HikariDataSource(config);
}

}

// Использование с Spring Data JPA (сам управляет соединениями)

@Repository

public interface UserRepository extends JpaRepository<User, Long> { List<User> findByName(String name); }

// Spring автоматически: // 1. Берёт соединение из пула // 2. Выполняет запрос // 3. Возвращает соединение в пул

Обнаружение утечек

Метод 1: Логирование

HikariCP выводит предупреждения:

WARN HikariPool - Connection from pool was not returned 
(waited 30 seconds for idle connection)

Метод 2: Мониторинг метрик

// Spring Boot Actuator

@Component

public class DataSourceMetrics { @Autowired private DataSource dataSource;

@Scheduled(fixedRate = 60000)  // Каждую минуту
public void logPoolStats() {
    if (dataSource instanceof HikariDataSource) {
        HikariDataSource hikari = (HikariDataSource) dataSource;
        int active = hikari.getHikariPoolMXBean().getActiveConnections();
        int idle = hikari.getHikariPoolMXBean().getIdleConnections();
        logger.info("DB Pool - Active: {}, Idle: {}", active, idle);
    }
}

}

// Вывод в логах: // DB Pool - Active: 3, Idle: 7 // DB Pool - Active: 8, Idle: 2 // DB Pool - Active: 20, Idle: 0 // ОПА! Полный пул!

Метод 3: Prometheus метрики

// Точка доступа /actuator/prometheus покажет:

hikaricp_connections_total{pool="HikariPool-1"} 20
hikaricp_connections_active{pool="HikariPool-1"} 20
hikaricp_connections_idle{pool="HikariPool-1"} 0
hikaricp_connections_pending{pool="HikariPool-1"} 5

Production сценарий: что произойдёт

Время: 00:00 - Приложение стартует
Обработано запросов: 0
Открытых соединений: 0/20

Время: 00:05 - Начинает расти трафик
Обработано запросов: 500
Открытых соединений: 5/20 (норма)

Время: 00:15 - Трафик растёт
Обработано запросов: 2000
Открытых соединений: 15/20 (высоко)

Время: 00:20 - Утечки накапливаются
Обработано запросов: 3500
Открытых соединений: 20/20 (ПОЛНЫЙ ПУЛ!)

Время: 00:21
Ошибка: "Cannot get a connection, pool error Timeout"
Все пользователи видят 500 ошибку
Автоматическое восстановление: нет
Решение: перезагрузить приложение

Debugging утечек

// Включи логирование утечек в HikariCP

HikariConfig config = new HikariConfig();
config.setLeakDetectionThreshold(30000);  // 30 сек

// Логирует: WARNING: Connection leak detected

Чек-лист для проверки утечек

  • Все database операции используют try-with-resources
  • Нет забытых .close() вызовов
  • ResultSet, PreparedStatement, Connection закрываются в правильном порядке
  • Spring Data JPA/Hibernate управляют соединениями (не ты)
  • Connection pooling настроен с reasonable limits
  • Мониторинг pool metrics включён
  • Load тесты показывают что pool не истощается

Вывод

Незакрытые соединения — это медленная смерть приложения.

Проблемы которые возникают:

  1. Connection leak — соединения копятся в памяти
  2. Pool exhaustion — БД может обслужить только 100-1000 соединений
  3. Application hang — новые запросы зависают в очереди
  4. Cascading failures — система падает и требует рестарта

Решение просто: ВСЕГДА используй try-with-resources:

try (Connection conn = dataSource.getConnection()) { // Работаешь с БД

} catch (SQLException e) {
// Ошибка обработана

} // Соединение ГАРАНТИРОВАННО закрыто

Это одна из тех ошибок, которые проходят code review, но разрушают production.

Что будет, если не закрыть соединение с базой данных, после использования | PrepBro