对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了。频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。
所以,为了连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。一个最小化的数据库连接池实现:
资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。
在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。
外部使用者可通过getConnection 方法获取连接,使用完毕后再通过releaseConnection 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。
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之后关闭)
在标准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。
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状态
根据原理机制,Snap-ConnectionPool(一种简单快速的连接池工具,可在www.snapbug.net下载)按照部分的JDBC规范,实现了连接池所具备的对数据库资源有效管理功能。
在JDBC规范中,应用通过驱动接口(Driver Interface)直接方法数据库的资源。为了有效、合理地管理资源,在应用与JDBC Driver之间,增加了连接池: Snap-ConnectionPool。并且通过面向对象的机制,使连接池的大部分操作是透明的。参见下图,Snap-ConnectionPool的体系:
图中所示,通过实现JDBC的部分资源对象接口( Connection, Statement, ResultSet ),在 Snap-ConnectionPool内部分别产生三种逻辑资源对象: PooledConnection, PooledStatement和 PooledResultSet。它们也是连接池主要的管理操作对象,并且继承了JDBC中相应的从属关系。
ConnectionPool是Snap-ConnectionPool的连接池对象。在Snap-ConnectionPool内部,可以指定多个不同的连接池(ConnectionPool)为应用服务。ConnectionManager管理所有的连接池,每个连接池以不同的名称区别。通过配置文件适应不同的数据库种类。如下图所示:
通过ConnectionManager,可以同时管理多个不同的连接池,提供通一的管理界面。在应用系统中通过 ConnectionManager和相关的配置文件,可以将凌乱散落在各自应用程序中的数据库配置信息(包括:数据库名、用户、密码等信息),集中在一个文件中。便于系统的维护工作。
对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,阿里巴巴的开源框架。
常见问题 · alibaba/druid Wiki https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
各种数据库连接池对比 · 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
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.
(Least recently used,最近最少使用)缓存淘汰算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。
程序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。
由此,就有了JNDI。
配置 |
缺省值 |
说明 |
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 |
如果同时配置了filters和proxyFilters, |
||
是组合关系,并非替换关系 |
表1.1 配置属性
druid/druid-spring-boot-starter at master · alibaba/druid https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
A: 游标数量是有上限的,oracle数据库中open_cursors默认值为300,所以一个connection中的statement是有上限的。
Java-超出打开游标的最大数 - xiaoli - CSDN博客 https://blog.csdn.net/wodestudy/article/details/23887279
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