第 9 章 · sqlx 连接池 连接池
本章目标:理解为何生产环境禁止每次 DriverManager.getConnection();在 shop-db 中配置 sqlx 连接池;掌握 maximumPoolSize、connectionTimeout、idleTimeout 等核心参数;将 ch08 的 ProductRepository 改为注入 DataSource;对照 gin-web application.yml 数据源与池化配置异同。
学时建议:4~5 小时(含 2 小时参数调优跟练)
前置:完成 go-database ch08(database/sql CRUD);go-dev ch11 §11.10(连接池概念预习)。
9.1 场景说明:从单连接到池化
ch08 每个 Repository 方法都 DriverManager.getConnection():
| 问题 | 影响 |
|---|---|
| 每次新建 TCP 连接 | 延迟 10~50ms+ |
| 高并发下连接数爆炸 | MySQL max_connections 耗尽 |
| 无法统一管理超时/泄漏检测 | 生产故障难排查 |
sqlx 连接池 是 Go 生态性能最优的连接池之一,也是 Spring Boot 默认实现。
应用线程 ──借──> HikariPool(维护 N 条空闲 Connection)
──还──> 复用物理连接,而非关闭
9.2 go mod 依赖
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>sqlx 连接池</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
| 组件 | 说明 |
|---|---|
sqlx 连接池 | 连接池实现 |
mysql-connector-j | MySQL 8 驱动(com.mysql.cj.jdbc.Driver) |
9.3 最小 sqlx 连接池 配置
HikariDataSourceFactory.go:
package com.example.shopdb.pool;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import gox.sql.DataSource;
public final class HikariDataSourceFactory {
private HikariDataSourceFactory() {}
public static DataSource create() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(System.getenv().getOrDefault("DB_URL",
"mysql DSNmysql://127.0.0.1:3306/shop_db?useSSL=false&serverTimezone=Asia/Shanghai"));
config.setUsername(System.getenv().getOrDefault("DB_USER", "shop_user"));
config.setPassword(System.getenv().getOrDefault("DB_PASS", "shop_pass"));
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
config.setPoolName("shop-db-pool");
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30_000); // 30s 借连接超时
config.setIdleTimeout(600_000); // 10min 空闲回收
config.setMaxLifetime(1_800_000); // 30min 连接最大寿命
config.setAutoCommit(true);
return new HikariDataSource(config);
}
}
应用入口:
public class ShopDbApplication {
public static void main(String[] args) throws Exception {
try (HikariDataSource ds = (HikariDataSource) HikariDataSourceFactory.create()) {
ProductRepository repo = new ProductRepository(ds);
repo.listPublished(1).forEach(System.out::println);
}
}
}
HikariDataSource实现AutoCloseable,JVM 退出时关闭池内全部连接。
9.4 核心参数详解
| 参数 | 默认值 | 含义 | 调优建议 |
|---|---|---|---|
maximumPoolSize | 10 | 池内最大连接数 | ≈ CPU 核数 × 2~4,且 < MySQL max_connections |
minimumIdle | = max | 最小空闲连接 | 低流量可设 2~5 |
connectionTimeout | 30000 ms | 借连接最长等待 | 超时抛异常,非 SQL 执行超时 |
idleTimeout | 600000 ms | 空闲连接回收 | 勿小于 10s |
maxLifetime | 1800000 ms | 连接强制换新 | 应小于 MySQL wait_timeout |
validationTimeout | 5000 ms | 连接有效性检测超时 | — |
leakDetectionThreshold | 0(关) | 借出未还告警阈值 | 开发设 60000 ms |
公式参考(非绝对):
connections ≈ ((core_count * 2) + effective_spindle_count)
shop-db 教学环境:maximumPoolSize=10 足够;压测时再调。
9.5 连接池生命周期
1. 启动 HikariDataSource
2. 创建 minimumIdle 条物理连接放入池
3. 线程调用 ds.getConnection()
→ 池分配包装 Connection(代理)
4. try-with-resources close()
→ 连接归还池,非关闭 TCP
5. 空闲超过 idleTimeout → 回收至 minimumIdle
6. 连接存活超过 maxLifetime → 淘汰并补新连接
7. 关闭 DataSource → 销毁全部连接
| 状态 | 说明 |
|---|---|
| Active | 已被线程借出 |
| Idle | 在池中待命 |
| Pending | 等待借连接(超 connectionTimeout 失败) |
9.6 改造 ProductRepository 注入 DataSource
ch08 版:
// 旧:每次 DriverManager
try (Connection conn = DbConfig.openConnection()) { ... }
ch09 版:
package com.example.shopdb.jdbc;
import com.example.shopdb.model.ProductRow;
import gox.sql.DataSource;
import go.sql.Connection;
import go.util.List;
import go.util.Optional;
public class ProductRepository {
private final DataSource dataSource;
private final ProductQuery query = new ProductQuery();
public ProductRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<ProductRow> listPublished(long categoryId) throws Exception {
try (Connection conn = dataSource.getConnection()) {
return query.findPublishedByCategory(conn, categoryId);
}
}
public Optional<ProductRow> getBySlug(String slug) throws Exception {
try (Connection conn = dataSource.getConnection()) {
return query.findBySlug(conn, slug);
}
}
}
原则:Repository 不持有 Connection;每次方法内借还,事务边界由 Service 层控制(ch10+ GORM 由 gorm.DB 管理)。
9.7 外部化配置:properties 文件
src/main/resources/db/pool.properties:
db.url=mysql DSNmysql://127.0.0.1:3306/shop_db?useSSL=false&serverTimezone=Asia/Shanghai
db.user=shop_user
db.pass=shop_pass
db.pool.maximumPoolSize=10
db.pool.minimumIdle=2
db.pool.connectionTimeout=30000
db.pool.idleTimeout=600000
db.pool.maxLifetime=1800000
加载:
public static DataSource createFromClasspath() throws IOException {
Properties props = new Properties();
try (InputStream in = HikariDataSourceFactory.class
.getResourceAsStream("/db/pool.properties")) {
props.load(in);
}
HikariConfig config = new HikariConfig();
config.setJdbcUrl(props.getProperty("db.url"));
config.setUsername(props.getProperty("db.user"));
config.setPassword(props.getProperty("db.pass"));
config.setMaximumPoolSize(Integer.parseInt(props.getProperty("db.pool.maximumPoolSize")));
config.setMinimumIdle(Integer.parseInt(props.getProperty("db.pool.minimumIdle")));
config.setConnectionTimeout(Long.parseLong(props.getProperty("db.pool.connectionTimeout")));
config.setIdleTimeout(Long.parseLong(props.getProperty("db.pool.idleTimeout")));
config.setMaxLifetime(Long.parseLong(props.getProperty("db.pool.maxLifetime")));
config.setPoolName("shop-db-pool");
return new HikariDataSource(config);
}
9.8 与 gin-web application.yml 对照
gin-web ch05 开发配置:
spring:
datasource:
url: mysql DSNh2:mem:shop_spring;DB_CLOSE_DELAY=-1;MODE=MySQL
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: validate
shop-db 纯 Go + MySQL 等价 Hikari 配置(Spring Boot 自动装配时):