# 数据库连接池
### 什么是连接池
数据库连接池负责<font color='red'>分配、管理和释放数据库连接</font>,它允许应用程序<font color='red'>重复使用</font>一个现有的数据库连接,<font color='orange'>而不是再重新建立一个</font>
### 为什么要用连接池
一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,<font color='cornflowerblue'>这样造成系统的性能低下</font>。
![img](池.assets/1606768-20190616155729169-544894466.png)
数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池,由应用程序动态地对池中的连接进行申请、使用和释放。
<font color='red'>连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。</font>通过使用连接池,将<font color='orange'>大大提高程序运行效率</font>,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
### 数据库连接池接口
Java 为数据库连接池提供了公共接口:<font color='red'>javax.sql.DataSource</font>,各大厂商可以让自己的连接池实现该接口,而应用程序可以方便的切换不同的连接池。
### 数据库连接池的优点
- <font color='red'>资源重用</font>
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
- <font color='red'>更快的系统响应速度</font>
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
- <font color='red'>新的资源分配手段</font>
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。
- <font color='red'>统一的连接管理,避免数据库连接泄漏</font>
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。
### 数据库连接池的工作原理
分位3步:
- 连接池的建立
- 连接池中连接的使用管理
- 连接池的关闭
<font color='red'>连接池的建立。</font>一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。<font color='cornflowerblue'>连接池中的连接不能随意创建和关闭</font>,这样避免了连接随意建立和关闭造成的系统开销。
<font color='red'>连接池的管理。</font>连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
```oop
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
```
<font color='red'>连接池的关闭。</font>当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,以便连接可以返回池中重复利用。我们可以通过<font color='orange'>Connection</font>对象的<font color='orange'>Close</font>或<font color='orange'>Dispose</font>方法
## 常用的连接池
| 数据库连接池 | 最新版本 | 发布时间 |
| ------------ | ------------ | -------- |
| c3p0 | c3p0-0.9.5.2 | 2015 |
| dbcp | 2.2.0 | 2017 |
| **druid** | 0.11.0 | 2017 |
| **HikariCP** | 2.7.6 | 2018 |
#### C3P0
<font color='red'>C3P0</font>在很长一段时间内,它一直是Java领域内数据库连接池的代名词,当年盛极一时的<font color='red'>Hibernate</font>都将其作为内置的数据库连接池,可以业内对它的稳定性还是认可的。<font color='orange'>C3P0功能简单易用,稳定性好这是它的优点,但是性能上的缺点却让它彻底被打入冷宫</font>。<font color='red'>C3P0的性能很差</font>,差到即便是同时代的产品相比它也是垫底的
#### DBCP
属于Apache顶级项目Commons中的核心子项目(最早在Jakarta Commons里就有),在Apache的生态圈中的影响里十分广泛,比如最为大家所熟知的Tomcat就在内部集成了DBCP,实现JPA规范的<font color='orange'>OpenJPA</font>,也是默认集成DBCP的。但DBCP并不是独立实现连接池功能的,它内部依赖于Commons中的另一个子项目Pool,连接池最核心的“池”,就是由Pool组件提供的,因为核心功能依赖于Pool,所以DBCP本身只能做小版本的更新,真正大版本的更迭则完全依托于pool。
#### 性能无敌的<font color='red'>HikariCP</font>
- 字节码精简:优化代码,直到编译后的字节码最少,这样,CPU缓存可以加载更多的程序代码;
- 优化代理和拦截器:减少代码,例如<font color='orange'>HikariCP</font>的Statement proxy只有100行代码;
- 自定义数组类型(<font color='orange'>FastStatementList</font>)代替<font color='orange'>ArrayList</font>:避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描;
- 自定义集合类型(<font color='orange'>ConcurrentBag</font>):提高并发读写的效率;
- 其他缺陷的优化,比如对于耗时超过一个CPU时间片的方法调用的研究(但没说具体怎么优化)。
java代码
```Java
@Test
//Hikari
public void test3() throws Exception{
Properties properties = new Properties();
properties.load(PoolTest.class.getClassLoader().getResourceAsStream("hikari.properties"));
HikariConfig hikariConfig = new HikariConfig(properties);
DataSource dataSource = new HikariDataSource(hikariConfig);
System.out.println(dataSource.getConnection());
}
```
properties配置文件
```properties
jdbcUrl=jdbc:mysql://127.0.0.1:3306/job?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=root
password=root
driverClassName=com.mysql.cj.jdbc.Driver
```
#### 功能全面的<font color='red'>Druid</font>(德鲁伊)
Druid 相对于其他数据库连接池的优点
- 强大的监控特性,通过Druid提供的监控功能,可以清楚知道连接池和SQL的工作情况。
- 监控SQL的执行时间、<font color='orange'>ResultSet</font>持有时间、返回行数、更新行数、错误次数、错误堆栈信息;
- SQL执行的耗时区间分布。什么是耗时区间分布呢?比如说,某个SQL执行了1000次,其中0\~1毫秒区间50次,1\~10毫秒800次,10\~100毫秒100次,100\~1000毫秒30次,1~10秒15次,10秒以上5次。通过耗时区间分布,能够非常清楚知道SQL的执行耗时情况;
- 监控连接池的物理连接创建和销毁次数、逻辑连接的申请和关闭次数、非空等待次数、<font color='orange'>PSCache</font>命中率等。
- 方便扩展。Druid提供了Filter-Chain模式的扩展API,可以自己编写Filter拦截JDBC中的任何方法,可以在上面做任何事情,比如说性能监控、SQL审计、用户名密码加密、日志等等。
<font color='red'>Druid</font>集合了开源和商业数据库连接池的优秀特性,并结合<font color='red'>阿里巴巴</font>大规模苛刻生产环境的使用经验进行优化。
| 配置 | 缺省值 | 说明 |
| ----------------------------- | ------------------ | ------------------------------------------------------------ |
| name | | 配置这个属性的意义在于,如果存在多个数据源,监控的时候 可以通过名字来区分开来。如果没有配置,将会生成一个名字, 格式是:"DataSource-" + System.identityHashCode(this) |
| jdbcUrl | | 连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto |
| username | | 连接数据库的用户名 |
| password | | 连接数据库的密码。如果你不希望密码直接写在配置文件中, 可以使用ConfigFilter。详细看这里: [https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter](https://github.com/alibaba/druid/wiki/使用ConfigFilter) |
| driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName |
| initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
| maxActive | 8 | 最大连接池数量 |
| maxIdle | 8 | 已经不再使用,配置了也没效果 |
| minIdle | | 最小连接池数量 |
| maxWait | | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后, 缺省启用公平锁,并发效率会有所下降, 如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 |
| poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。 PSCache对支持游标的数据库性能提升巨大,比如说oracle。 在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。 作者在5.5版本中使用PSCache,通过监控界面发现PSCache有缓存命中率记录, 该应该是支持PSCache。 |
| maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时, poolPreparedStatements自动触发修改为true。 在Druid中,不会存在Oracle下PSCache占用内存过多的问题, 可以把这个数值配置大一些,比如说100 |
| validationQuery | | 用来检测连接是否有效的sql,要求是一个查询语句。 如果validationQuery为null,testOnBorrow、testOnReturn、 testWhileIdle都不会其作用。 |
| testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
| testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
| testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。 申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis, 执行validationQuery检测连接是否有效。 |
| timeBetweenEvictionRunsMillis | | 有两个含义: 1) Destroy线程会检测连接的间隔时间 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
| numTestsPerEvictionRun | | 不再使用,一个DruidDataSource只支持一个EvictionRun |
| minEvictableIdleTimeMillis | | |
| connectionInitSqls | | 物理连接初始化的时候执行的sql |
| exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
| filters | | 属性类型是字符串,通过别名的方式配置扩展插件, 常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall |
| proxyFilters | | 类型是List<com.alibaba.druid.filter.Filter>, 如果同时配置了filters和proxyFilters, 是组合关系,并非替换关系 |
Druid java代码
```Java
public void test1() throws Exception{
Properties properties = new Properties();
properties.load(PoolTest.class.getClassLoader().getResourceAsStream("jdbc.properties"));
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.configFromPropety(properties);
Connection connection = druidDataSource.getConnection();
System.out.println(connection);
}
```
Druid properties配置文件
```properties
druid.url=jdbc:mysql://127.0.0.1:3306/job?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
druid.username=root
druid.password=root
druid.driverClassName=com.mysql.cj.jdbc.Driver
```