2.4 主流数据库连接池对比
英国政治家、历史学家、画家、演说家、作家、记者、前首相温斯顿·丘吉尔曾经说过:“批评也许并不会令人满意,但却是必要的。它和人体疼痛的功能类似,它可以唤起人们对于事物不健康状态的关注。”近几年,手机评测比较热门,王自如和罗永浩当年关于手机评测就进行过一场辩论。眼花缭乱的数据库连接池当然也需要一个评测。那么该如何评测呢?评价原则是:性能固然是好的,但是却不能以放弃可靠性为代价。
2.4.1 性能对比
在本书的主角HikariCP的官网上,通过JMH Benchmarks对主流数据库连接池分别进行了Connection和Statement级的性能对比,如图2-13所示。可以看到,常见的数据库连接池有c3p0、DPCP2、Tomcat、Vibur、Hikari等,HikariCP的性能独占鳌头。
图2-13 Connection和Statement级的性能对比
2.4.2 代码复杂度
虽然代码行数并不直接表明复杂性,但冗长和可理解性之间存在着无可争辩的相关性。一个被业内广泛接受的观点是这样的,每千行代码的Bug数量在不同的项目和语言中表现得相当一致。除了测试套件外,目视检查代码中的逻辑错误和竞争条件是最可靠的方法之一。HikariCP从未经历过记录的死锁(deadlock)和活锁(livelock)的情况。
我们按照截至2014年3月15日已发布的主流数据库连接池版本,不包括测试和comment,从文件数和代码量上,我们可以得到对比表格,如表2-2所示。
表2-2 主流数据库连接池文件数和代码量对比
上述数据来源于Brett Wooldridge 2014年的文章。写到这里,不访和读者分享一个小故事。笔者曾经在和朋友闲聊过程中分享这组数据,笔者的朋友王振飞先生在2018年4月26日的时候也使用当时最新的TAG对druid和HikariCP的数据做了一次比较。得到的结论如下:
1)只统计Java文件和xml文件,Druid总行数:430289, HikariCP总行数:18372。
2)只统计Java代码,Druid总行数:428749, HikariCP总行数:17556。
3) 再过滤一下test目录,Druid总行数:215232, HikariCP总行数:7960。
4)且不说性能,光一个DruidDataSource就编写了3000行。
一个在某购物网站用户增长组的好友也曾经告诉我一个我不知道的故事,他知道HikariCP的原因是HikariCP给BoneCP捐了几美金,BoneCP的作者给他发了邮件。当他在“有赞”的时候,用HikariCP替换Durid,之后,RT出现断崖式下滑(1.2毫秒~1.5毫秒)并且持续稳定、毛刺少。他还给我分享了当时的性能测试报告。目前,据我所知,“杭州有赞”“51信用卡”“海康威视”3家公司都采用HikariCP作为数据库连接池,很多中小型互联网企业也选择HikariCP作为数据库连接池。
2.4.3 功能对比
在Druid的官方文档上,也给出了一份几种数据库连接池功能对比表,如表2-3所示。
表2-3 几种数据库连接池功能对比
在这份文档中,Druid强调了如下几个参数:
1)LRU。LRU是一个性能关键指标,特别是Oracle,其中每个Connection对应数据库端的一个进程,如果数据库连接池遵从LRU,有助于数据库服务器优化,这是重要的指标。在测试中,Druid、DBCP、Proxool是遵守LRU的。BoneCP、c3p0则不是。BoneCP在mock环境下性能可能好,但在真实环境中则不见得好。
2)PSCache。PSCache是数据库连接池的关键指标。在Oracle中,类似SELECT NAME FROM USER WHERE ID = ?这样的SQL,启用PSCache和不启用PSCache的性能可能会相差一个数量级。Proxool是不支持PSCache的数据库连接池,如果你使用Oracle、SQL Server、DB2、Sybase这样支持游标的数据库,那就完全不用考虑Proxool。
3)PSCache-Oracle-Optimized。在Oracle 10系列的Driver中,如果开启PSCache,会占用大量的内存,必须做特别的处理,启用内部的EnterImplicitCache等方法优化才能够减少内存的占用。这个功能只有DruidDataSource有。如果你使用的是Oracle JDBC,则应该毫不犹豫地采用DruidDataSource。
4)ExceptionSorter。ExceptionSorter是一个很重要的容错特性,如果一个连接产生了一个不可恢复的错误,必须立刻将其从连接池中去掉,否则会连续产生大量错误。这个特性,目前只有JBossDataSource和Druid实现。Druid的实现参考自JBossDataSource,经过长期生产反馈进行了补充。
结合HikariCP和Druid的官方文档,并收集整理了大量数据库连接资料,我从线程同步、PSCache、LRU、ExceptionSorted、监控、扩展性、SQL拦截及解析、代码复杂度、特点、连接池管理、JNDI、Tomcat数据源、更新维护等角度对主流数据库连接池做了一份更全面的选型对比,以方便读者在实际学习工作过程中作为工具使用,如表2-4所示。
表2-4 主流数据库连接池全面功能对比
2.4.4 数据库中断
在HikariCP的官方文档上,记载了关于对比主流数据库连接池数据库中断的测试情况,这是针对各种数据库连接池都提供的超时功能的测试对比。这个测试实验模拟了这样一个场景,将数据库连接池执行getConnection()在5秒的调用后超时,应用程序应在指定时间内获得连接,或获得异常。该实验设置了一个测试,可以针对远程MySQL服务器配置4个池(HikariCP、c3p0、DBCP2、Vibur)。每隔2秒getConnection()调用一次,执行查询,然后关闭连接(返回池)。
每个连接池都如表2-5所示进行了配置,来验证check-out时的连接。
表2-5 每个数据库连接池的Validation Enablement配置
每种数据库连接池超时配置如表2-6所示。
表2-6 每种数据库连接池超时配置
然后开始运行测试内容,所有的数据库连接池都没有问题,但是MySQL服务器网络电缆突然故意断开,则出现了如下现象。
❑HikariCP:等待5秒,若连接未恢复,抛出SQLExceptions异常,后续getConnection()同样处理。
❑c3p0:完全无反应且无提示,也不会在CheckoutTimeout配置的时长超时后有任何通知给调用者。然后等待2分钟后终于醒来了,返回一个error。
❑Tomcat:返回一个connection,若调用者如果利用这个无效的connection执行SQL语句结果可想而知。大约55秒之后终于醒来,此时getConnection()终于返回一个error,但未像参数配置的那样等待5秒钟,而是立即返回error。
❑BoneCP:与Tomcat类似,也是约55秒后醒来,有正常反应,且最终等5秒钟后返回error。
可见,HikariCP的处理方式是最合理的。根据这个测试结果,对于各个CP处理数据库中断的情况,评分如表2-7所示。
表2-7 测试结果主观评分
注意
这是使用JDBC 4.1驱动程序获得的结果,结果可能因新旧驱动程序差异而有所不同。除了数据库中断以外,HikariCP在其他方面还有很多值得称赞的地方:
❑连接的测试在getConnection时同步进行,并有独特的优化。
❑在自己的事务中封装内部池的查询,含testQuery一级initSQLQuery。
❑当连接Connections归还给连接池的时候,执行rollback操作。
❑在Connection.close的时候,跟踪并关闭废弃语句Statements。
❑在将Connection连接归还到客户端之前清除SQL警告。
❑默认参数支持自动提交、事务隔离级别、catalog和只读状态。
❑对于一些诸如“SQLException对象是否存在断开连接错误”等陷阱进行检查。