数据库连接池有什么用?

我们开发连接数据库的时候,经常是使用连接池,比如常见的c3p0,那么为什么要使用连接池呢,第一反应就是可以节约创建销毁socket的成本,那节约多少呢?除了这些还有什么其他的好处呢,下面自己动手做个连接实验。

客户端

一台普通Mac笔记本
JDBC-[mysql/mysql-connector-java “6.0.4”]
C3P0-[com.mchange/c3p0 “0.9.2.1”]
JDK 1.8

服务端

Centos 6.4刀片机
Mysql5.7

测试clojure脚本如下(不想看代码 可以直接忽略往下看)


(ns dbtest.core
	(:require [dbtest.dbrepo :as r]
	  )
	(:import [java.util UUID]))

(def text "XXX本来有很长的东西")

(defn lauch
	[limit is-pool]
	(dotimes [n limit]
		(if is-pool
			(try (r/insert-book-withpool (.toString (UUID/randomUUID)) (str "use-pool" n text) ))
			(try (r/insert-book (.toString (UUID/randomUUID)) (str n text) )))))

(defn go-test
	([nthread]
		(go-test nthread 10000 false))
	([nthread limit is-pool]
		(dotimes [n nthread]
			(future (lauch limit is-pool)))))

(ns dbtest.dbrepo
	(:use [dbtest.dbconf])
	(:require [clojure.java.jdbc :as j]
	  ))


(declare insert-stat)

(defn insert-book
  [name text]
  (let [s-time (System/currentTimeMillis) 
  		result (j/insert! cur-db-spec :bookinfo {:name name :text text})]
  	(insert-stat (-> result first :generated_key) s-time (- (System/currentTimeMillis) s-time))))


(defn insert-stat
	[biz-id start-time cost-time]
	(j/insert! cur-db-spec :bookstat 
		{:biz_id biz-id :start_time start-time :cost_time cost-time}))


(defn insert-book-withpool
  [name text]
  (let [s-time (System/currentTimeMillis) 
  		result (j/insert! (db-connection) :bookinfo {:name name :text text})]
  	(insert-stat (-> result first :generated_key) s-time (- (System/currentTimeMillis) s-time))))

测试逻辑如下:
首先有个目标表bookinfo,里面有两个字段,一个是32位长的字符串字段name(有唯一键约束),一个是1000位长的大字段text。
每次只做一个动作就是插入一条记录内容基本一致,但是又区分.每次插入前后记下时间戳,并计算得出此次数据插入所花时间(无事务),存入bookstat统计表中。

几个重要参数:nThread->几个线程,limit->插入多少次, is-pool->是否使用连接池
测试用例为:5,10,20,50个线程,每个线程插入200次,使用或者不使用连接池

最后得出的结果

取所有结果的平均数
不使用线程的用时(ms) 78.9(5线程) 106.8(10线程) 164.3(20线程) 310.5(50线程)
使用了线程的用时(ms) 51.9(5线程) 56.9(10线程) 67.3(20线程) 148.7(50线程)

取所有结果top100(最耗时的100)的平均数
不使用线程的用时(ms) 126.1(5线程) 228.1(10线程) 506.4(20线程) 3863.4(50线程)
使用了线程的用时(ms) 102.4(5线程) 176.6(10线程) 314.6(20线程) 903.4(50线程)

可以看出整体平均值上,使用了连接池省去了创建销毁的成本,但时间相对而言也就快了1倍,整体的吞吐量并没有我们想的那么糟糕,可是从下面top100的数据就可以看出另外一个端倪,那就是连接池的随着并发量的上升,插入更加稳定,可以想象如果一条数据插入操作的RT是4秒左右(峰值可能还要糟糕),那么整个操作响应、体验将变得很差,甚至直接调用超时了。

最后通过测试总结一下自己的感悟:任何系统的连接操作都是有上限的,当到达危险水位的时候(并发度很高、RT很大),那么对这个系统一定要做好连接的管理,如果没有就像上面不使用线程池的结果一样,甚至会引发系统瘫痪假死,导致业务全线不可用。虽然我们很多时候不用关心连接管理,都有相应连接模块处理,但是了解连接管理是一个系统的闸门这个思想,或许会在某天帮助你完成一个更健壮的架构

数据库隔离级别

细细梳理一下DB的隔离级别。

1.Read Uncomitted(未提交读)

在此级别中,事务中的级别即便是没有提交,对其他事务也都是可见的。也就是说事务可以读取到未提交的数据,这也是俗称的“脏读”。这个是比较危险的,而且性能上不比其他严格级别的好多少。

2.Read Committed(提交读)

顾名思义,该事务下,一个事务只有当提交之后,其所做的修改才能被另外的事务看见。这里又会产生另外一个问题,事务前后查询的数据是不一样的,(即“不可重复读”),大多数数据库默认是这个级别。因为在一个事务中前后两次查询同一数据,并且对其结果有一致性要求的需求极少,所以一般情况下是安全的。

3.Repeatalbe Read(可重复读)

该级别保证了同一事务中多次读取同一记录的结果是一致的。但是理论上,该级别无法解决另外一个”幻读“问题。当一个事务查询ID 0~100的记录,此时另外一个事务插入ID为99的记录,那边在插入事务前后的查询会不一致。(此问题和不可重复读类似,前者是因为新增或删除,后者是因为修改)

4.Serializable(可串行化)

最高的隔离级别。所有事务串行执行,会对操作的每一行数据都加上锁,会导致性能耗费在超时和锁争抢上。只有在非常需要确保数据的一致性并且可以接受没有并发的情况下,才应该选择此级别。

Mysql-查看事务级别

<pre><code>SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;</pre></code>

默认的行为(不带session和global)是为下一个(未开始)事务设置隔离级别。如果使用Global关键字,语句在全局对从那点开始创建的所有新连接(除了不存在的连接)设置默认事务级别。需要Super权限来做这个。使用Session 关键字为将来在当前连接上执行的事务设置默认事务级别。 任何客户端都能自由改变会话隔离级别(甚至在事务的中间),或者为下一个事务设置隔离级别。