21.数据库连接池

1.不同连接池的参数配置

  • HikariCP
<!-- 配置HikariCP数据源(连接池核心Bean) -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
    <!-- 基础数据库配置 -->
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>

    <!-- 连接池核心参数(和之前讲的一一对应) -->
    <property name="maxPoolSize" value="20"/>          <!-- 最大连接数 -->
    <property name="minIdle" value="5"/>              <!-- 最小空闲连接 -->
    <property name="connectionTimeout" value="3000"/> <!-- 获取连接超时3秒 -->
    <property name="idleTimeout" value="600000"/>     <!-- 空闲10分钟回收 -->
    <property name="maxLifetime" value="1800000"/>    <!-- 连接最长存活30分钟 -->
    <property name="testWhileIdle" value="true"/>     <!-- 空闲时验证连接有效性 -->
</bean>

<!-- 配置JdbcTemplate(用连接池执行SQL) -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/> <!-- 关联上面的连接池 -->
</bean>
  • Druid
<!-- 配置Druid数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <!-- 基础数据库配置 -->
    <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>

    <!-- 连接池核心参数 -->
    <property name="maxActive" value="20"/>          <!-- 对应Hikari的maxPoolSize -->
    <property name="minIdle" value="5"/>             <!-- 最小空闲连接 -->
    <property name="maxWait" value="3000"/>          <!-- 获取连接超时3秒(对应connectionTimeout) -->
    <property name="minEvictableIdleTimeMillis" value="600000"/> <!-- 空闲超时 -->
    <property name="maxEvictableIdleTimeMillis" value="1800000"/> <!-- 最大生命周期 -->
    <property name="testWhileIdle" value="true"/>    <!-- 空闲时验证 -->
    <property name="validationQuery" value="SELECT 1"/> <!-- 验证连接的SQL(MySQL) -->
</bean>

<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>
  • dbcp2
<!-- 配置DBCP2数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <!-- 1. 基础数据库配置 -->
    <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>

    <!-- 2. 连接池核心参数(对应之前的Hikari/Druid参数) -->
    <property name="maxTotal" value="20"/>          <!-- 最大连接数(对应Hikari的maxPoolSize) -->
    <property name="minIdle" value="5"/>            <!-- 最小空闲连接 -->
    <property name="maxWaitMillis" value="3000"/>   <!-- 获取连接超时时间(3秒,对应connectionTimeout) -->
    <property name="minEvictableIdleTimeMillis" value="600000"/> <!-- 空闲连接超时回收(10分钟) -->
    <property name="maxConnLifetimeMillis" value="1800000"/>      <!-- 连接最大生命周期(30分钟) -->
    
    <!-- 3. 连接验证配置 -->
    <property name="testWhileIdle" value="true"/>   <!-- 空闲时验证连接有效性 -->
    <property name="validationQuery" value="SELECT 1"/> <!-- 验证连接的SQL(MySQL) -->
    <property name="validationQueryTimeout" value="1"/> <!-- 验证超时(1秒) -->
</bean>

<!-- 配置JdbcTemplate(关联DBCP2连接池) -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

2.特别重要的一个参数----最大连接数、最小空闲数

  • 传统的 DBCP 设计成动态的(minIdle < maxTotal),是为了省内存。
    没人用的时候,我只维护 5 个连接,省点 RAM。人多了,我再临时 new Connection() 涨到 20 个。

  • 现代的最佳实践变了(HikariCP 的理念):
    现在的服务器内存都很大,不差这几个连接的内存。所以 HikariCP 建议: minIdle = maxTotal
    也就是说:一上来就铺满 20 张桌子,永远不撤。
    好处: 来了客人直接坐,不需要临时去搬桌子(不需要临时建立 TCP 连接),响应最快,性能最稳。

  • 核心性能悖论:那为啥连接设很大会出现性能反而很低呢?

    操作系统(CPU 调度器)的做法: 为了公平,它不能让某个人一直占着cpu,于是它强制实行“轮班制”。
    假设cpu只有4核,但是你允许的连接数是1000,故而高并发的话,会导致1000个线程同时工作,导致cpu高频切换,cpu切换的代价也是很高的,频繁的切换导致了用于切换线程的开销(保存和恢复现场)远远高于线程正正干活的开销,故而导致性能远远下降。

    • 黄金经验公式(针对机械硬盘):\(连接数 = (CPU核心数 \times 2) + 1\)

    • 如果是 SSD 硬盘: 因为读写极快,CPU 等待时间变短,公式甚至可以接近 CPU核心数

      为什么?按照常理,设备越快,应该能处理越多的连接才对,为什么 SSD 越快,我们需要的并发连接数反而越少(越接近 CPU 核数) 呢?

      有一个经典的线程数计算公式:$$最佳线程数 = CPU核数 \times \left( 1 + \frac{等待时间 (I/O)}{计算时间 (CPU)} \right)$$

      公式里的 \(\frac{等待时间}{计算时间}\) 趋近于 0,最佳线程数 趋近于CPU核数。

    总结
    越慢的 I/O (HDD): CPU 休息时间越多 -> 需要越多备胎(连接)来填补空缺 -> 连接数要大。
    越快的 I/O (SSD/内存): CPU 休息时间越少 -> 原来的线程一个人就能把 CPU 跑满 -> 不需要备胎 -> 连接数越少(接近核数)。

3.结尾---连接池返回给我们的connetion对象
本质是代理对象,但是在dbcp连接池中,并没有使用JDK原生代理,而是重新实现了 java.sql.Connection 接口,长得和普通连接一模一样(这个实现类被称为代理类,持有connetion原始对象的引用),许多功能是直接使用原始connetion对象的,但是对于 conn.close() 此时并不是关闭物理连接(这里的close是被修改过逻辑的,不可能让你直接就去把连接给关闭了),只是将当前的connetion对象引用归还给连接池。

posted @ 2025-12-13 22:25  那就改变世界吧  阅读(1)  评论(0)    收藏  举报