Java数据库连接池研究
一、背景
连接池简介:
连接池初始化时创建一定数量的连接,然后从连接池中重用连接,而不是每次创建一个新的。
数据库连接是一种关键的、有限的、昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正是针对这个问题提出来的。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
什么是连接?
连接,是编程语言与数据库交互的一种方式。
如下创建连接的代码片段,将展示其内部创建原理:
String connUrl = "jdbc:mysql://your.database.domain/yourDBname"; Class.forName("com.mysql.jdbc.Driver"); Connection con = DriverManager.getConnection (connUrl);
当创建了一个Connection对象,它在内部的执行步骤:
- “DriverManager”检查并注册驱动程序,
- “com.mysql.jdbc.Driver”就是注册的驱动程序,它会在驱动程序类中调用“connect(url…)”方法。
- com.mysql.jdbc.Driver的connect方法根据请求的“connUrl”,创建一个“Socket连接”,连接到IP为“your.database.domain”,默认端口3306的数据库。
- 创建的Socket连接将被用来查询指定的数据库,并最终让程序返回得到一个结果。
连接昂贵的体现?
如果创建Socket连接花费的时间比实际的执行查询的操作所花费的时间还要更长。
因为连接资源数如果是1,它需要每次创建一个Socket连接来访问DB。也就是说,如果在Web服务器上部署,每一个用户访问一次就连接到释放,如果用户数数以千计,那么这个过程也是数以千计的过程,时间和性能上消耗是非常巨大的。
因此,这就是昂贵的体现,将使用连接池把这些连接保存起来已供有需要的时候不用重新连接造成性能上的损失,从而提升性能。
二、影响因素
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数制约。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。要考虑到下列几个因素:
1、最小连接数
是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。
2、最大连接数
是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
3、最小连接数与最大连接数差距
最小连接数与最大连接数相差太大,那么最先的连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,它将被放到连接池中等待重复使用或是空闲超时后被释放。
4、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像Java等等,使用synchronized(java)关键字即可确保线程是同步的。
5、事务处理
事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
当2个线程公用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是仍然不能确定那个数据库操作是对应那个事务的,这是由于有2个线程都在进行事务操作而引起的。为此可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
6、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给它;如果没有就抛出一个异常给用户,List中连接是否可以被分配由一个线程来专门管理。
三、原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
下面将展示实际代码例子:
使用的连接池供应者,它的内部有一个连接池管理器,当它被初始化:
1、它创建连接池的默认大小,比如指定创建5个连接对象,并把它存放在“可用”状态的任何集合或数组中。
... String connUrl = "jdbc:mysql://your.database.domain/yourDBname"; String driver = "com.mysql.jdbc.Driver"; private Map<java.sql.Connection, String> connectionPool = null; private void initPool() { try { connectionPool = new HashMap<java.sql.Connection, String>(); Class.forName(driver); java.sql.Connection con = DriverManager.getConnection(dbUrl); for (int poolInd = poolSize; poolInd < 0; poolInd++) { connectionPool.put(con, "AVAILABLE"); } } ...
2、当调用connectionProvider.getConnection(),然后它会从集合中获取一个连接,当然状态也会更改为“不可用”。
... public java.sql.Connection getConnection() throws ClassNotFoundException, SQLException { boolean isConnectionAvailable = true; for (Entry<java.sql.Connection, String> entry : connectionPool.entrySet()) { synchronized (entry) { if (entry.getValue()=="AVAILABLE") { entry.setValue("NOTAVAILABLE"); return (java.sql.Connection) entry.getKey(); } isConnectionAvailable = false; } } if (!isConnectionAvailable) { Class.forName(driver); java.sql.Connection con = DriverManager.getConnection(connUrl); connectionPool.put(con, "NOTAVAILABLE"); return con; } return null; } ...
3、当关闭得到的连接,ConnectionProvider是不会真正关闭连接。相反,只是将状态更改为“AVAILABLE”。
... public void closeConnection(java.sql.Connection connection) throws ClassNotFoundException, SQLException { for (Entry<java.sql.Connection, String> entry : connectionPool.entrySet()) { synchronized (entry) { if (entry.getKey().equals(connection)) { //Getting Back the conncetion to Pool entry.setValue("AVAILABLE"); } } } } ...
基本上连接池的实际工作原理就是这样,但也有可能使用不同的方式。
四、Java连接池选择
在Java中开源的数据库连接池有以下几种 :
1、C3P0:是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
2、Proxool:是一个Java SQL Driver驱动程序,提供了对选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中,完全可配置,快速、成熟、健壮。可以透明地为现存的JDBC驱动程序增加连接池功能。
3、Jakarta DBCP:DBCP是一个依赖Jakartacommons-pool对象池机制的数据库连接池。DBCP可以直接的在应用程序中使用。也许是年代的问题,也许这就是Tomat DBCP连接池,Tomcat默认使用的就是这个连接池。
4、DDConnectionBroker:是一个简单、轻量级的数据库连接池。
5、DBPool:是一个高效、易配置的数据库连接池。它除了支持连接池应有的功能之外,还包括了一个对象池,使用户能够开发一个满足自己需求的数据库连接池。
6、XAPool:是一个XA数据库连接池。它实现了javax.sql.XADataSource并提供了连接池工具。
7、Primrose:是一个Java开发的数据库连接池。当前支持的容器包括Tomcat4&5、Resin3与JBoss3。它同样也有一个独立的版本,可以在应用程序中使用而不必运行在容器中。Primrose通过一个WEB接口来控制SQL处理的追踪、配置,以及动态池管理。在重负荷的情况下可进行连接请求队列处理。
8、SmartPool:是一个连接池组件,它模仿应用服务器对象池的特性。SmartPool能够解决一些临界问题如连接泄漏(connection leaks)、连接阻塞、打开的JDBC对象(如Statements、PreparedStatements)等。SmartPool的特性包括:
- 支持多个pool
- 自动关闭相关联的JDBC对象
- 在所设定time-outs之后察觉连接泄漏
- 追踪连接使用情况
- 强制启用最近最少用到的连接
- 把SmartPool“包装”成现存的一个pool
9、MiniConnectionPoolManager:是一个轻量级JDBC数据库连接池。它只需要Java1.5(或更高)并且没有依赖第三方包。
10、BoneCP:是一个快速、开源的数据库连接池。帮用户管理数据连接,让应用程序能更快速地访问数据库。比C3P0/DBCP连接池速度快25倍。
11、Druid:Druid不仅是一个数据库连接池,还包含一个ProxyDriver、一系列内置的JDBC组件库、一个SQL Parser。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。
Druid针对Oracle和MySql做了特别优化,比如:
- Oracle的PS Cache内存占用优化
- MySql的ping检测优化
Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。
简单SQL语句用时10微秒以内,复杂SQL用时30微秒。
通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter,就是通过Druid的SQL Parser分析语义实现的。
还有一个重要特定,这是阿里巴巴发布的。
五、dotnet连接池
1、ado.net默认已经开启线程池,无需人工设置,但是现在市面上也有线程池的实现,不过非常少,也可以人工设置这个线程池的连接数等等。
参考:
http://blog.csdn.net/u012152619/article/details/46287419(详细介绍,附带配置)
http://www.oschina.net/question/157182_72094?from=20121007(详细介绍)
http://blog.csdn.net/tuke_tuke/article/details/51532510(连接池实现)
http://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0(百度百科详细介绍)
http://duzc2.iteye.com/blog/1536205(性能对比测试报告)