Vấn đề
Ứng dụng của bạn đang "khát" kết nối cơ sở dữ liệu, nhưng pool đã cạn kiệt. Mọi kết nối hiện tại đều đang bận. Theo mặc định, HikariCP sẽ đợi trong 30 giây (30.000ms) để một kết nối được trả lại pool. Nếu khoảng thời gian đó kết thúc mà không có kết nối nào trống, ứng dụng của bạn sẽ ném ra lỗi SQLTransientConnectionException và yêu cầu bị thất bại.
java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms
at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:696)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:197)
Tại sao hết kết nối?
- Rò rỉ mã nguồn (Leaky Code): Các kết nối được mượn nhưng không bao giờ được trả lại pool, thường do thiếu khối
finallytrong mã JDBC cũ. - Truy vấn bị nghẽn (Bottleneck Queries): Một vài truy vấn mất 5–10 giây có thể nhanh chóng làm bão hòa một pool nhỏ.
- Pool không đủ dung lượng: Bạn đang sử dụng kích thước mặc định là 10 kết nối, nhưng lưu lượng truy cập của bạn yêu cầu 25.
- Giao dịch bị phình to (Transaction Bloat): Giữ kết nối cơ sở dữ liệu trong khi chờ phản hồi từ một REST API bên thứ ba chậm chạp.
Giải pháp 1: Phát hiện và ngăn chặn rò rỉ kết nối
Rò rỉ là nguyên nhân chính khiến pool luôn trong tình trạng cạn kiệt ngay cả khi lưu lượng truy cập thấp. Mặc dù Spring Data JPA xử lý hầu hết việc này cho bạn, nhưng logic JDBC tùy chỉnh thường bỏ lỡ lệnh đóng kết nối. HikariCP có một công cụ phát hiện tích hợp để tìm chính xác nơi bắt đầu rò rỉ.
Thêm thuộc tính này vào tệp application.properties của bạn:
# Nếu một kết nối được mượn lâu hơn 2 giây, ghi nhật ký stack trace
spring.datasource.hikari.leak-detection-threshold=2000
Khi cài đặt này hoạt động, nhật ký (logs) của bạn sẽ chỉ ra chính xác phương thức đã mượn kết nối. Hãy tìm thông báo "Apparent connection leak detected" kèm theo một stack trace. Sửa mã nguồn và lỗi sẽ biến mất.
Giải pháp 2: Điều chỉnh kích thước Pool hợp lý
Kích thước pool mặc định là 10 khá khiêm tốn. Đối với một microservice thực tế dưới tải trung bình, bạn có thể cần tăng con số này lên. Tuy nhiên, đừng quá lạm dụng bằng cách đặt con số khổng lồ như 500; CPU của cơ sở dữ liệu có thể sẽ bị nghẽn do việc chuyển đổi ngữ cảnh (context switching).
# Tăng số lượng kết nối tối đa dựa trên phần cứng DB (ví dụ: 20-50)
spring.datasource.hikari.maximum-pool-size=30
# Giữ một vài kết nối luôn sẵn sàng
spring.datasource.hikari.minimum-idle=10
# Thời gian máy khách chờ trước khi từ bỏ (30 giây thường là ổn)
spring.datasource.hikari.connection-timeout=30000
Lưu ý: Luôn kiểm tra khả năng chịu tải của máy chủ cơ sở dữ liệu. Nếu bạn có 5 instance ứng dụng, mỗi instance đặt maximum-pool-size là 30, cơ sở dữ liệu của bạn phải có khả năng xử lý ít nhất 150 kết nối đồng thời.
Giải pháp 3: Tách biệt Giao dịch khỏi các tác vụ I/O bên ngoài
Một trong những sai lầm phổ biến nhất là bao bọc toàn bộ phương thức service trong @Transactional khi nó thực hiện các cuộc gọi mạng. Nếu một API từ xa mất 5 giây để phản hồi, kết nối cơ sở dữ liệu của bạn sẽ nằm yên vô dụng trong 5 giây đó.
Cách làm sai:
@Transactional
public void completePurchase(Order order) {
orderRepo.save(order); // Kết nối được lấy tại đây
// NGUY HIỂM: Kết nối bị giữ trong khi chờ cổng thanh toán phản hồi
paymentService.chargeCreditCard(order.getAmount());
orderRepo.updateStatus(order, "PAID");
} // Kết nối cuối cùng được giải phóng tại đây
Cách làm đúng: Giữ cho các giao dịch của bạn thật tinh gọn. Thực hiện ghi vào cơ sở dữ liệu, commit nó, gọi API, sau đó bắt đầu một giao dịch thứ hai để cập nhật trạng thái. Điều này giải phóng kết nối cho những người dùng khác trong khi cuộc gọi API đang diễn ra.
Giải pháp 4: Theo dõi sức khỏe của Pool
Đừng đoán mò mà hãy bắt đầu đo lường. Bằng cách sử dụng Spring Boot Actuator, bạn có thể thấy chính xác có bao nhiêu kết nối đang hoạt động hoặc đang chờ xử lý trong thời gian thực. Thêm đoạn sau vào pom.xml của bạn:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Kích hoạt các chỉ số (metrics) trong cấu hình của bạn:
management.endpoints.web.exposure.include=metrics
management.endpoint.metrics.enabled=true
Kiểm tra endpoint /actuator/metrics/hikaricp.connections.pending. Nếu số lượng 'pending' liên tục cao hơn 0, đó là dấu hiệu rõ ràng cho thấy pool của bạn quá nhỏ hoặc các truy vấn của bạn quá chậm để đáp ứng nhu cầu.

