数据库连接池(Database Connection Pool)
基本信息
是一种用于管理数据库连接的技术。它通过预先创建一定数量的数据库连接,并将其缓存在池中,供多个客户端或应用程序使用,从而减少了每次请求时连接数据库的开销。
主要特点
-
连接复用:连接池维护一组数据库连接,当应用程序需要数据库连接时,连接池会提供一个空闲连接;当应用程序完成数据库操作后,连接会被返回连接池,以便下次使用。
-
性能优化:减少了频繁打开和关闭数据库连接的开销,提高了数据库操作的效率。尤其在高并发环境下,能够显著提升应用程序的性能。
-
配置灵活性:连接池一般允许配置最大连接数、最小连接数、最大等待时间等参数,根据应用需求进行调整。
连接池的工作流程
-
初始化:当连接池启动时,会创建一定数量的数据库连接,并将它们放入池中。这个数量通常是根据应用的需求进行设置的。
-
获取连接:当应用程序需要执行数据库操作时,它从连接池中获取一个可用连接。如果池中没有空闲连接,它可以根据配置等待或者创建新的连接(如果池没有达到最大连接数)。
-
使用连接:应用程序通过获取的连接执行数据库操作(例如,查询、插入、更新等)。
-
归还连接:操作完成后,应用程序将连接归还给连接池,连接池将该连接标记为可用状态,等待下次使用。
-
关闭连接:当连接池被销毁时,池中的所有连接将被关闭,释放数据库资源。
常见的连接池框架
- HikariCP:一个高性能的JDBC连接池,广泛应用于Java应用中。
- C3P0:另一个JDBC连接池,支持自动测试、自动回收等功能。
- DBCP(Apache Commons DBCP):由Apache Commons提供的连接池实现,功能较为基础。
- Tomcat JDBC Connection Pool:Tomcat服务器自带的连接池,优化过的DBCP版本。
优点
- 提高性能:通过复用连接避免了每次都建立和关闭数据库连接的开销。
- 降低数据库负载:通过控制连接数,避免了数据库过载。
- 提升响应速度:能够快速获取数据库连接,缩短请求响应时间。
缺点
- 资源限制:如果连接池的最大连接数设置不合理,可能导致资源浪费或连接耗尽。
- 复杂性:需要管理连接池的配置和监控连接的状态。
配置连接池的一些常见参数
- 最小连接数(minIdle):连接池中至少保持的空闲连接数。
- 最大连接数(maxTotal):连接池中允许的最大连接数。
- 最大等待时间(maxWaitMillis):当连接池没有空闲连接时,应用程序等待连接的最长时间。
- 连接最大空闲时间(maxIdleTime):连接在池中空闲的最长时间,超过这个时间连接会被关闭。
通过合理的配置和使用数据库连接池,可以显著提高数据库操作的效率和性能,尤其是在并发量较高的情况下。
Druid连接池(为监控而生的数据库连接池)
Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。

功能类别 |
功能 |
Druid |
HikariCP |
DBCP |
Tomcat-jdbc |
C3P0 |
性能 |
PSCache |
是 |
否 |
是 |
是 |
是 |
LRU |
是 |
否 |
是 |
是 |
是 |
|
SLB负载均衡支持 |
是 |
否 |
否 |
否 |
否 |
|
稳定性 |
ExceptionSorter |
是 |
否 |
否 |
否 |
否 |
扩展 |
扩展 |
Filter |
JdbcIntercepter |
|||
监控 |
监控方式 |
jmx/log/http |
jmx/metrics |
jmx |
jmx |
jmx |
支持SQL级监控 |
是 |
否 |
否 |
否 |
否 |
|
Spring/Web关联监控 |
是 |
否 |
否 |
否 |
否 |
|
诊断支持 |
LogFilter |
否 |
否 |
否 |
否 |
|
连接泄露诊断 |
logAbandoned |
否 |
否 |
否 |
否 |
|
安全 |
SQL防注入 |
是 |
无 |
无 |
无 |
无 |
支持配置加密 |
是 |
否 |
否 |
否 |
否 |
从上表可以看出,Druid连接池在性能、监控、诊断、安全、扩展性这些方面远远超出竞品。
1. 连接池的性能消耗占比
-
连接池本身的性能消耗在整个调用链路中通常占比不大。
这是因为连接池的主要作用是管理和复用数据库连接,而不是直接处理业务逻辑。连接池的性能开销主要集中在连接的创建、销毁和管理上,但这些操作相比实际的数据库查询和业务处理来说,消耗相对较小。
2. 连接池的性能关键点
-
连接是否以 LRU(Least Recently Used,最近最少使用)的方式重用
LRU 是一种缓存淘汰算法,用于决定哪些连接应该被优先复用。通过 LRU 策略,连接池可以确保最常用的连接被保留,而不常用的连接被淘汰,从而提高连接的复用率,减少创建新连接的开销。 -
是否支持 PSCache(PreparedStatement Cache)
PreparedStatement 是预编译的 SQL 语句,可以重复使用,避免每次执行 SQL 时重新编译的开销。PSCache 的作用是缓存这些 PreparedStatement 对象,从而提升 SQL 执行的性能。如果连接池支持 PSCache,可以显著减少数据库的 CPU 和内存消耗。
3. DruidDataSource 的性能表现
-
DruidDataSource 是一个高性能的数据库连接池实现。
-
在没有使用 Filter(过滤器)且没有打开
testOnBorrow
(从连接池获取连接时是否检测连接的有效性)的情况下,DruidDataSource 的性能表现非常好(“裸测也是极好”)。
这是因为:-
不开启
testOnBorrow
可以避免每次获取连接时的额外检测开销。 -
不使用 Filter 可以减少额外的拦截和处理逻辑,从而提升性能。
-
连接池的性能优化重点在于:
-
连接的复用策略(如 LRU)。
-
是否支持 PreparedStatement 缓存(PSCache)。
-
减少不必要的检测和拦截逻辑(如
testOnBorrow
和 Filter)。
单数据源
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-19-datasource-pool-druid-single</artifactId>
<dependencies>
<!-- 保证 Spring JDBC 的依赖健全 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 实现对 Druid 连接池的自动化配置 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency> <!-- 本示例,我们使用 MySQL -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- 实现对 Spring MVC 的自动化配置,因为我们需要看看 Druid 的监控功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 方便等会写单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
应用配置文件
spring:
# datasource 数据源配置内容,对应 DataSourceProperties 配置属性类
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root # 数据库账号
password: # 数据库密码
type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
# Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
druid:
min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
filter:
stat: # 配置 StatFilter ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilter
log-slow-sql: true # 开启慢查询记录
slow-sql-millis: 5000 # 慢 SQL 的标准,单位:毫秒
stat-view-servlet: # 配置 StatViewServlet ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
enabled: true # 是否开启 StatViewServlet
login-username: admin # 账号
login-password: 1234 # 密码
spring.datasource
配置项,设置 Spring 数据源的通用配置。其中,spring.datasource.type
配置项,需要主动设置使用 DruidDataSource 。
@SpringBootApplication
public class Application implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(Application.class);
@Autowired
private DataSource dataSource;
public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) {
logger.info("[run][获得数据源:{}]", dataSource.getClass());
}
}
多数据源
配置文件
spring:
# datasource 数据源配置内容
datasource:
# 订单数据源配置
orders:
url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
# Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
# 用户数据源配置
users:
url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
# Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
# Druid 自定已配置
druid:
# 过滤器配置
filter:
stat: # 配置 StatFilter ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilter
log-slow-sql: true # 开启慢查询记录
slow-sql-millis: 5000 # 慢 SQL 的标准,单位:毫秒
# StatViewServlet 配置
stat-view-servlet: # 配置 StatViewServlet ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
enabled: true # 是否开启 StatViewServlet
login-username: admin # 账号
login-password: 123456 # 密码
数据源配置类
// DataSourceConfig.java
@Configuration
public class DataSourceConfig {
/**
* 创建 orders 数据源
*/
@Primary
@Bean(name = "ordersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.orders") // 读取 spring.datasource.orders 配置到 HikariDataSource 对象
public DataSource ordersDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 创建 users 数据源
*/
@Bean(name = "usersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.users")
public DataSource usersDataSource() {
return DruidDataSourceBuilder.create().build();
}
}
Application
HikariCP
是一个高性能的 JDBC 连接池实现,被广泛认为是目前最快的连接池之一。以下是对 HikariCP 单数据源的详细介绍和配置说明:
单数据源
引入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lab-19-datasource-pool-hikaricp-single</artifactId>
<dependencies>
<!-- 实现对数据库连接池的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency> <!-- 本示例,我们使用 MySQL -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- 方便等会写单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 无需主动引入 HikariCP 的依赖。因为在 Spring Boot 2.X 中,
spring-boot-starter-jdbc
默认引入 com.zaxxer.HikariCP 依赖。
应用配置文件:
spring:
# datasource 数据源配置内容,对应 DataSourceProperties 配置属性类
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root # 数据库账号
password: # 数据库密码
# HikariCP 自定义配置,对应 HikariConfig 配置属性类
hikari:
minimum-idle: 10 # 池中维护的最小空闲连接数,默认为 10 个。
maximum-pool-size: 10 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。
- 在
spring.datasource
配置项下,我们可以添加数据源的通用配置。 - 在
spring.datasource.hikari
配置项下,我们可以添加 HikariCP 连接池。spring: datasource: url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver hikari: pool-name: HikariPool minimum-idle: 5 # 最小空闲连接数 maximum-pool-size: 20 # 最大连接数 idle-timeout: 30000 # 空闲连接超时时间(毫秒) max-lifetime: 1800000 # 连接的最大生命周期(毫秒) connection-timeout: 30000 # 连接超时时间(毫秒) connection-test-query: SELECT 1 # 连接测试查询 auto-commit: true # 是否自动提交事务
- DataSourceConfiguration.Hikari会自动化配置 HikariCP 连接池。
// Application.java
@SpringBootApplication
public class Application implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(Application.class);
@Autowired
private DataSource dataSource;
public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) {
try (Connection conn = dataSource.getConnection()) {
// 这里,可以做点什么
logger.info("[run][获得连接:{}]", conn);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
多数据源
配置文件
spring:
# datasource 数据源配置内容
datasource:
# 订单数据源配置
orders:
url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
# HikariCP 自定义配置,对应 HikariConfig 配置属性类
hikari:
minimum-idle: 20 # 池中维护的最小空闲连接数,默认为 10 个。
maximum-pool-size: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。
# 用户数据源配置
users:
url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
# HikariCP 自定义配置,对应 HikariConfig 配置属性类
hikari:
minimum-idle: 15 # 池中维护的最小空闲连接数,默认为 10 个。
maximum-pool-size: 15 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。
@Bean(name = "ordersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.orders")
public DataSource ordersDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "usersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.users")
public DataSource ordersDataSource() {
return DataSourceBuilder.create().build();
}
以上配置
如果每个数据源如果有 HikariCP 的 "hikari"
自定义配置项时,它的自定义配置项无法设置到 HikariDataSource Bean 中。因为,"spring.datasource.orders.hikari"
是 "spring.datasource.orders"
的第二层属性。而 HikariDataSource的配置属性在第一层,这就导致无法正确的设置。
正确的示例
// DataSourceConfig.java
@Configuration
public class DataSourceConfig {
/**
* 创建 orders 数据源的配置对象
*/
@Primary
@Bean(name = "ordersDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.orders") // 读取 spring.datasource.orders 配置到 DataSourceProperties 对象
public DataSourceProperties ordersDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 创建 orders 数据源
*/
@Bean(name = "ordersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.orders.hikari") // 读取 spring.datasource.orders 配置到 HikariDataSource 对象
public DataSource ordersDataSource() {
// <1.1> 获得 DataSourceProperties 对象
DataSourceProperties properties = this.ordersDataSourceProperties();
// <1.2> 创建 HikariDataSource 对象
return createHikariDataSource(properties);
}
/**
* 创建 users 数据源的配置对象
*/
@Bean(name = "usersDataSourceProperties")
@ConfigurationProperties(prefix = "spring.datasource.users") // 读取 spring.datasource.users 配置到 DataSourceProperties 对象
public DataSourceProperties usersDataSourceProperties() {
return new DataSourceProperties();
}
/**
* 创建 users 数据源
*/
@Bean(name = "usersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.users.hikari")
public DataSource usersDataSource() {
// 获得 DataSourceProperties 对象
DataSourceProperties properties = this.usersDataSourceProperties();
// 创建 HikariDataSource 对象
return createHikariDataSource(properties);
}
private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
// 创建 HikariDataSource 对象
HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
// 设置线程池名
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
#ordersDataSourceProperties()
方法,创建"orders"
数据源的 DataSourceProperties 配置对象。- @Primary注解,保证项目中有一个主的 DataSourceProperties Bean 。
new DataSourceProperties()
代码段,会创建一个 DataSourceProperties 数据源的配置对象。- 搭配上
@Bean(name = "ordersDataSourceProperties")
注解,会创建一个名字为"ordersDataSourceProperties"
的 DataSourceProperties Bean 。 @ConfigurationProperties(prefix = "spring.datasource.orders")
注解,会将"spring.datasource.orders"
配置项,逐个属性赋值给 DataSourceProperties Bean 。
#ordersDataSource()
方法,创建orders
数据源。- 调用
#ordersDataSourceProperties()
方法,获得orders
数据源的 DataSourceProperties 。 - 调用
#createHikariDataSource(DataSourceProperties properties)
方法,创建 HikariDataSource 对象。这样,"spring.datasource.orders"
配置项,逐个属性赋值给 HikariDataSource Bean 。 - 搭配上
@Bean(name = "ordersDataSource")
注解,会创建一个名字为"ordersDataSource"
的 HikariDataSource Bean 。 @ConfigurationProperties(prefix = "spring.datasource.orders.hikari")
注解,会将 HikariCP 的"spring.datasource.orders.hikari"
自定义配置项,逐个属性赋值给 HikariDataSource Bean 。
- 调用
创建 Application.java类,配置 @SpringBootApplication
注解即可。代码如下:
// Application.java
@SpringBootApplication
public class Application implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(Application.class);
@Resource(name = "ordersDataSource")
private DataSource ordersDataSource;
@Resource(name = "usersDataSource")
private DataSource usersDataSource;
public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) {
// orders 数据源
try (Connection conn = ordersDataSource.getConnection()) {
// 这里,可以做点什么
logger.info("[run][ordersDataSource 获得连接:{}]", conn);
} catch (SQLException e) {
throw new RuntimeException(e);
}
// users 数据源
try (Connection conn = usersDataSource.getConnection()) {
// 这里,可以做点什么
logger.info("[run][usersDataSource 获得连接:{}]", conn);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}