调整 JDBC 连接池大小的最佳实践

1、简介

本文将带你了解调整 JDBC 连接池大小的最佳策略。

2、什么是 JDBC 连接池,为什么要使用它?

JDBC 连接池是一种用于有效管理数据库连接的机制。创建数据库连接需要几个耗时的步骤,如

  • 打开数据库连接
  • 验证用户身份
  • 创建用于通信的 TCP Sokcet 套接字
  • 通过套接字收发数据
  • 关闭连接和 TCP Sokcet 套接字

为每个用户请求重复这些步骤的效率可能会很低,尤其是对于有许多用户的应用。JDBC 连接池通过提前创建一个可重复使用的连接池来解决这个问题。当应用程序启动时,它会在池中创建并维护数据库连接。池连接管理器负责管理这些连接并处理其生命周期。

当客户端请求连接时,连接池管理器会从连接池中提供一个连接,而不是创建一个新的连接。一旦客户端完成操作,连接就会返回池中重复使用,而不是关闭。这种连接的重复使用节省了时间和资源,大大提高了应用程序的性能。

3、为什么 JDBC 连接池的大小对应用来说很重要?

确定 JDBC 连接池的最佳大小对于平衡性能和资源利用率至关重要。较小的连接池可能会加快连接访问速度,但如果没有足够的连接来满足所有请求,则可能导致延迟。相反,较大的连接池可确保有更多连接可用,减少在队列中花费的时间,但可能会降低连接表的访问速度。

“连接表” 通常指的是管理和跟踪数据库连接的内部数据结构。它记录了当前连接的状态(如空闲、使用中等)以及相关的元数据,以便有效地分配和管理连接。这个“表”并不是数据库中的实际表,而是连接池实现中的一个逻辑概念。

下表总结了在确定连接池大小时需要考虑的利弊:

连接池大小 优点 缺点
较小的连接池 更快地访问连接表 可能需要更多连接来满足请求。请求在队列中停留的时间可能会更长
较大的连接池 有更多连接来满足请求。请求在队列中花费的时间减少(或没有) 降低访问连接表的速度

4、确定 JDBC 连接池大小时应考虑的要点

在确定池大小时,需要考虑几个因素。首先,应该评估平均事务响应时间和花费在数据库查询上的时间。负载测试可以帮助确定这些时间,建议在计算连接池大小时再增加 25% 的容量,以应对意外负载。其次,连接池应能根据实际需要进行增减。我们可以使用日志或 JMX 监视来监控系统,从而动态调整池的大小。

此外,还应考虑每次页面加载执行多少次查询以及每次查询的持续时间。为了获得最佳性能,我们可以从少量连接开始,然后逐渐增加。每个节点 816 个连接的池通常是最佳的。我们还可以根据监控统计数据调整空闲超时(Idle Timeout)和池大小调整数量(Pool Resize Quantity)值。

5、JDBC 连接池的基本控制设置

这些基本设置可控制池的大小:

连接池属性 说明
初始和最少连接数 池创建时的大小及其允许的最小连接数量
池最大连接数 连接池可维护的最大连接数量
池大小调整数量 空闲超时时要移除的连接数。闲置时间超过超时的连接将被移除,一旦连接池达到初始和最小连接池大小,就会停止移除。
最大空闲连接数 池中允许的最大闲置连接数。如果闲置连接数超过此限制,多余的连接将被关闭,从而释放资源
最小空闲连接数 在连接池中保留的空闲连接的最小数量
最大等待时间 应用等待连接可用的最长时间
验证查询 用于验证连接的 SQL 查询,然后再将其交给应用

6、调整 JDBC 连接池大小的最佳实践

以下是调整 JDBC 连接池以确保与数据库实例之间健康连接的一些最佳实践。

6.1、验证连接的 SQL 语句

首先,添加一个验证 SQL 查询来启用连接验证。它能确保池中的连接定期接受测试并保持健康。它还能快速检测并记录数据库故障,以便管理员立即采取行动。

选择最佳验证方法(如元数据或表查询),在充分监控后,关闭连接验证可进一步提高性能。

下面是一个使用最流行的 JDBC 连接池(即 Hikari)进行配置的示例:

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:your_database_url");
config.setUsername("your_database_username");
config.setPassword("your_database_password");
config.setConnectionTestQuery("SELECT 1");  // 验证连接的 SQL 语句
HikariDataSource dataSource = new HikariDataSource(config);

6.2、最大连接池大小

我们还可以调整最大连接池大小,使其足够满足应用的用户数量。这对处理突发负载是必要的。即使在非生产环境中,也可能需要多个同时连接。

config.setMaximumPoolSize(20);

6.3、最小连接池大小

接下来,应该调整最小连接池大小以适应应用程序的负载。如果多个应用程序/服务器连接到同一个数据库,确保有足够的最小连接池大小可以保证保留的数据库连接可用:

config.setMinimumIdle(10);

6.4、连接超时

我们可以考虑的另一个方面是配置以毫秒为单位的阻塞超时,这样连接请求在等待连接可用时就不会无限期地等待。建议使用 35 秒的合理值:

config.setConnectionTimeout(5000);

6.5、空闲超时

将空闲超时设置为适合应用程序的值。对于共享数据库,较低的空闲超时(如 30 秒)可以为其他应用程序提供空闲连接。对于专用数据库,较高的值(如 120 秒)可防止频繁创建连接:

config.setIdleTimeout(30000);

// 或

config.setIdleTimeout(120000);

6.6、线程池调整

使用负载/压力测试来估算最小和最大池大小。估算池大小的常用公式是 连接数 = (2 * 核数) + 磁盘数 。该公式提供了一个起点,但根据 I/O 阻塞和其他因素,需求可能会有所不同。从小规模连接池开始,根据测试情况逐步增加。pg_stat_activity 等监控工具可以帮助确定连接是否闲置过多,从而表明需要减少池的大小:

int coreCount = Runtime.getRuntime().availableProcessors();
int numberOfDisks = 2; // 假设有两个磁盘
int connections = (2 * coreCount) + numberOfDisks;
config.setMaximumPoolSize(connections);
config.setMinimumIdle(connections / 2);

6.7、事务隔离级别

选择能满足并发性和一致性需求的最佳隔离级别。除非必要,否则避免指定隔离级别。如果指定了隔离级别,请将 “保证隔离级别(Isolation Level Guaranteed)” 设置为 false,以用于不以编程方式更改隔离级别的应用:

try (Connection conn = dataSource.getConnection()) {
    conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
} catch (SQLException e) {
    e.printStackTrace();
}

7、总结

本文介绍了调整 JDBC 连接池大小的最佳实践,通过了解影响连接池大小的因素,并遵循调整和监控的最佳实践,可以确保数据库连接的健康和提高的应用程序性能


Ref:https://www.baeldung.com/java-best-practices-jdbc-connection-pool