[总结]数据库连接池

一、背景

对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了。频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。

 

所以,为了连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。

 

二、优势

1. 资源重用

由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。

2. 更快的系统响应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。

3. 新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。

4. 统一的连接管理,避免数据库连接泄漏

在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。一个最小化的数据库连接池实现:

 

三、实现原理

1. 设计模式

资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。

 

2. 基本原理

 

在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。

外部使用者可通过getConnection 方法获取连接,使用完毕后再通过releaseConnection 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。

 

四、具体实现举例——JDBC

4.1.JDBC Driver

JDBC是一个规范,遵循JDBC接口规范,各个数据库厂家各自实现自己的驱动程序(Driver),如下图所示:

应用在获取数据库连接时,需要以URL的方式指定是那种类型的Driver,在获得特定的连接后,可按照固定的接口操作不同类型的数据库,如: 分别获取Statement、执行SQL获得ResultSet等,如下面的例子 :

import java.sql.*;
…
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
Connection dbConn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:oracle","username","password");
Statement st = dbConn.createStatement();
ResultSet rs = st.executeQuery("select * from demo_table");
…some data source operation in here
rs.close();
st.close();
dbConn.close();

在完成数据操作后,还一定要关闭所有涉及到的数据库资源。这虽然对应用程序的逻辑没有任何影响,但是关键的操作。上面是个简单的例子,如果搀和众多的if-else、exception,资源的管理也难免百密一疏。如同C中的内存泄漏问题,Java系统也同样会面临崩溃的恶运。所以数据库资源的管理依赖于应用系统本身,是不安全、不稳定的一种隐患。

 

不使用连接池流程

下面以访问MySQL为例,执行一个SQL命令,如果不使用连接池,需要经过哪些流程。

 

 

不使用数据库连接池的步骤:

 

TCP建立连接的三次握手

MySQL认证的三次握手

真正的SQL执行

MySQL的关闭

TCP的四次握手关闭

可以看到,为了执行一条SQL,却多了非常多我们不关心的网络交互。

 

优点:

实现简单

缺点:

网络IO较多

数据库的负载较高

响应时间较长及QPS较低

应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁

在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)

 

 

4.2. JDBC连接池

在标准JDBC对应用的接口中,并没有提供资源的管理方法。所以,缺省的资源管理由应用自己负责。虽然在JDBC规范中,多次提及资源的关闭/回收及其他的合理运用。但最稳妥的方式,还是为应用提供有效的管理手段。所以,JDBC为第三方应用服务器(Application Server)提供了一个由数据库厂家实现的管理标准接口:连接缓冲(connection pooling)。引入了连接池( Connection Pool )的概念 ,也就是以缓冲池的机制管理数据库的资源。

JDBC最常用的资源有三类:

— Connection: 数据库连接。

— Statement: 会话声明。

— ResultSet: 结果集游标。

分别存在以下的关系 :

这是一种“爷—父—子”的关系,对Connection的管理,就是对数据库资源的管理。举个例子: 如果想确定某个数据库连接(Connection)是否超时,则需要确定其(所有的)子Statement是否超时,同样,需要确定所有相关的 ResultSet是否超时;在关闭Connection前,需要关闭所有相关的Statement和ResultSet。

因此,连接池(Connection Pool)所起到的作用,不仅仅简单地管理Connection,还涉及到 Statement和ResultSet。

 

4.3 连接池(ConnectionPool)与资源管理

ConnectionPool以缓冲池的机制,在一定数量上限范围内,控制管理Connection,Statement和ResultSet。任何数据库的资源是有限的,如果被耗尽,则无法获得更多的数据服务。

在大多数情况下,资源的耗尽不是由于应用的正常负载过高,而是程序原因。

在实际工作中,数据资源往往是瓶颈资源,不同的应用都会访问同一数据源。其中某个应用耗尽了数据库资源后,意味其他的应用也无法正常运行。因此,ConnectionPool的第一个任务是限制:每个应用或系统可以拥有的最大资源。也就是确定连接池的大小(PoolSize)。

ConnectionPool的第二个任务:在连接池的大小(PoolSize)范围内,最大限度地使用资源,缩短数据库访问的使用周期。许多数据库中,连接(Connection)并不是资源的最小单元,控制Statement资源比Connection更重要。以Oracle为例:

每申请一个连接(Connection)会在物理网络(如 TCP/IP网络)上建立一个用于通讯的连接,在此连接上还可以申请一定数量的Statement。同一连接可提供的活跃Statement数量可以达到几百。在节约网络资源的同时,缩短了每次会话周期(物理连接的建立是个费时的操作)。但在一般的应用中,多数按照2.1范例操作,这样有10个程序调用,则会产生10次物理连接,每个Statement单独占用一个物理连接,这是极大的资源浪费。 ConnectionPool可以解决这个问题,让几十、几百个Statement只占用同一个物理连接, 发挥数据库原有的优点。

通过ConnectionPool对资源的有效管理,应用可以获得的Statement总数到达 :

(并发物理连接数)×(每个连接可提供的Statement数量)

例如某种数据库可同时建立的物理连接数为 200个,每个连接可同时提供250个Statement,那么ConnectionPool最终为应用提供的并发Statement总数为: 200 × 250 = 50,000个。这是个并发数字,很少有系统会突破这个量级。所以在本节的开始,指出资源的耗尽与应用程序直接管理有关。

对资源的优化管理,很大程度上依靠数据库自身的JDBC Driver是否具备。有些数据库的JDBC Driver并不支持Connection与Statement之间的逻辑连接功能,如SQLServer,我们只能等待她自身的更新版本了。

对资源的申请、释放、回收、共享和同步,这些管理是复杂精密的。所以,ConnectionPool另一个功能就是,封装这些操作,为应用提供简单的,甚至是不改变应用风格的调用接口。

使用连接池流程

使用数据库连接池的步骤:

 

第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。

 

优点:

 

较少了网络开销

系统的性能会有一个实质的提升

没了麻烦的TIME_WAIT状态

 

 

五、简单JDBC连接池的实现——Snap-ConnectionPool

根据原理机制,Snap-ConnectionPool(一种简单快速的连接池工具,可在www.snapbug.net下载)按照部分的JDBC规范,实现了连接池所具备的对数据库资源有效管理功能。

5.1 体系描述

在JDBC规范中,应用通过驱动接口(Driver Interface)直接方法数据库的资源。为了有效、合理地管理资源,在应用与JDBC Driver之间,增加了连接池: Snap-ConnectionPool。并且通过面向对象的机制,使连接池的大部分操作是透明的。参见下图,Snap-ConnectionPool的体系:

图中所示,通过实现JDBC的部分资源对象接口( Connection, Statement, ResultSet ),在 Snap-ConnectionPool内部分别产生三种逻辑资源对象: PooledConnection, PooledStatement和 PooledResultSet。它们也是连接池主要的管理操作对象,并且继承了JDBC中相应的从属关系。

5.2 连接池集中管理ConnectionManager

ConnectionPool是Snap-ConnectionPool的连接池对象。在Snap-ConnectionPool内部,可以指定多个不同的连接池(ConnectionPool)为应用服务。ConnectionManager管理所有的连接池,每个连接池以不同的名称区别。通过配置文件适应不同的数据库种类。如下图所示:

  通过ConnectionManager,可以同时管理多个不同的连接池,提供通一的管理界面。在应用系统中通过 ConnectionManager和相关的配置文件,可以将凌乱散落在各自应用程序中的数据库配置信息(包括:数据库名、用户、密码等信息),集中在一个文件中。便于系统的维护工作。

5.3 连接池使用范例

  对4.1的标准JDBC的使用范例,改为使用连接池,结果如下:

import java.sql.*;
import net.snapbug.util.dbtool.*;
…
..ConnectionPool dbConn = ConnectionManager.getConnectionPool("testOracle" );
Statement st = dbConn.createStatement();
ResultSet rs = st.executeQuery(
“select * from demo_table” );
…
some data source operation
in herers.close();st.close();

  在例子中,Snap-ConnectionPool封装了应用对Connection的管理。只要改变JDBC获取Connection的方法,为获取连接池(ConnectionPool)(粗体部分),其他的数据操作都可以不做修改。按照这样的方式,Snap- ConnectionPool可帮助应用有效地管理数据库资源。如果应用忽视了最后资源的释放: rs.close() 和 st.close(),连接池会通过超时(time-out)机制,自动回收。

 

六、连接池驱动——Druid

Druid,阿里巴巴的开源框架。

常见问题 · alibaba/druid Wiki https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

 

 

6.1主流连接池各项功能对比如下:

各种数据库连接池对比 · alibaba/druid Wiki https://github.com/alibaba/druid/wiki/%E5%90%84%E7%A7%8D%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0%E5%AF%B9%E6%AF%94

6.2 主要指标解释

1.PSCache:

PreparedStatementCache。

sql里用?占位,等待被替换。例如,select * from table1 where user_name = ? and age > ?。解析后的PS缓存,sql语句,被发送到DB server端,经一系列处理(语法解析、语义解析、结构优化),转化为一个树型结构(sql, string -> tree)。底层会缓存(LRU替换),重复利用。比如新的查询到来:

如果存在,要看能否直接使用。能就用,不能就会临时创建出一个ps给我用(这个新的ps不会被插到pscache中,因为cache里已经有啦)

如果不存在,那么就创建一个ps对象,并把它放到pscache中,根据LRU规则调整下pscache.

 

2.LRU——缓存淘汰算法

(Least recently used,最近最少使用)缓存淘汰算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

3.JNDI

(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。

程序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。

由此,就有了JNDI。

 

6.3 Druid主要配置

 

配置

缺省值

说明

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

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, 

是组合关系,并非替换关系

表1.1 配置属性

 

6.4 如何在Spring Boot中集成Druid连接池和监控

druid/druid-spring-boot-starter at master · alibaba/druid https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

 

七、Questions&Answers

7.1.一个Connection的最大statement数量如何确定?

A: 游标数量是有上限的,oracle数据库中open_cursors默认值为300,所以一个connection中的statement是有上限的。

Java-超出打开游标的最大数 - xiaoli - CSDN博客 https://blog.csdn.net/wodestudy/article/details/23887279

7.2.一个statement是一个线程吗?

A: connection里,每个方法都是synchronized,都执行了同步。一个connection应该就是一个对象,一个statement也是一个对象,执行的现场跟对象是无关的,而是看有几个线程调用几个对象的方法,操作的数据是不是有冲突。

 

 

参考文档:

主流Java数据库连接池分析(C3P0,DBCP,TomcatPool,BoneCP,Druid) http://baijiahao.baidu.com/s?id=1599699339020031595&wfr=spider&for=pc

数据库连接池学习笔记(一):原理介绍+常用连接池介绍 - CrankZ的博客 - CSDN博客 https://blog.csdn.net/crankz/article/details/82874158

数据库连接池的实现及原理 - 王YMsir - 博客园 https://www.cnblogs.com/wym789/p/6374440.html

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值