参考http://shift-alt-ctrl.iteye.com/blog/1967020
关于JDBC中关于Connection的两个疑问:
1.Connection实例是线程安全的吗?
即一个connection实例,在多线程环境中是否可以确保数据操作是安全的?
Java代码
- private static Connection connection;
上述代码,设计会不会有问题? 一个Connection实例,即对应底层一个TCP链接,有些开发者可能考虑到"性能",就将代码写成上述样式,最终一个application中所有的DB操作,使用一个connection.确实减少了与DB的TCP链接个数,但是这真的安全吗??真的提升了性能了吗??
1) 似乎一个初学者都不会这些写,因为没有sample是这么展示的.但是往往会在每个方法调用时都新建connection,然后close();
2) 当一个开发者考虑到性能的时候,可能会写出上面的代码..
Connection不是线程安全的,它在多线程环境中使用时,会导致数据操作的错乱,特别是有事务的情况.connection.commit()方法就是提交事务,你可以想象,在多线程环境中,线程A开启了事务,然后线程B却意外的commit,这该是个多么纠结的情况.
如果在没有事务的情况下,且仅仅是简单的SQL Select操作,似乎在不会出现数据错乱,一切看起来比较安全.但是这并非意味着提升了性能,JDBC的各种实现中,connection的各种操作都进行了同步:
Java代码
- ##PreparedStatement
- public int executeUpdate(){
- synchronized(connection){
- //do
- }
- }
同时只有当DB操作完成之后,方法调用才会返回. 尽管connection实例的个数唯一,但是在并发环境中,connection实例上锁竞争是必然的,反而没有提升性能(并发量).通常情况下,一个Connection实例只会在一个线程中使用,使用完之后close().
TCP链接的创建开支是昂贵的,当然DB server所能承载的TCP并发连接数也是有限制的.因此每次调用都创建一个Connection,这是不现实的;所以才有了数据库连接池的出现.
数据库连接池中保持了一定数量的connection实例,当需要DB操作的时候"borrow"一个出来,使用结束之后"return"到连接池中,多线程环境中,连接池中的connection实例交替性的被多个线程使用.
2. 在使用dataSourcePool的情况下,一个线程中所有的DB操作使用的是同一个connection吗??
比如线程A,依次调用了2个方法,每个方法都进行了一次select操作,那么这两个select操作是使用同一个connection吗?
第一感觉就是: dataSourcePool本身是否使用了threadLocal来保存线程与connection实例的引用关系;如果使用了threadLocal,那么一个线程多次从pool中获取是同一个connection,直到线程消亡或者调用向pool归还资源..
如果在spring环境中(或者其他ORM矿建中),这个问题需要分2种情况:事务与非事务.
在非事务场景下,一切都很简单,每一次调用,都是从pool中取出一个connection实例,调用完毕之后归还资源,因此多次调用,应该是不同的connection实例.
Java代码
- public List<Object> select(String sql){
- Connection connection = pool.getConnection();
- //do
- pool.release(connection);//归还资源
- }
在使用事务的场景下,情况就有所不同,开启一个新事务的同时,就会冲pool中获取一个connection实例,并将transaction和connection互为绑定,即此transaction中只会使用此connection,此connection此时只会在一个transaction中使用;因此,在此事务中,无论操作了多少次DB,事实上只会是一个connection实例,直到事务提交或者回滚,当事务提交或者回滚时,将会解除transaction与connection的绑定关系,同时将connection归还到pool中.
Java代码
- //开启事务
- public TransactionHolder transaction(){
- Connection connection = pool.getConnection();
- connection.setAutoCommit(false);
- return new TransactionHolder(connection);
- }
- //执行sql
- public boolean insert(String sql,TransactionHolder holder){
- Connection connection = holder.getConnection();
- //do
- try{
- //doInsert
- return true;
- }catch(Exception e){
- holder.setRollback(true);
- }
- return false;
- }
- //提交事务
- public void commit(TransactionHolder holder){
- Connection connection = holder.getConnection();
- connection.commit();
- holder.unbind();//解除绑定
- pool.release(connection);//归还资源
- }
在有事务的情况上,伪代码可能就像上述例子.对于跨DB分布式事务,可能更加的复杂.
另外,不考虑JDBC规范,关系型数据库的Java客户端是否可以基于Netty实现?正如Redis高级Java客户端lettuce。
3. MySQL为什么使用线程都模型而不是epoll之类的技术?
Mysql 的整个任务是把数据组织成树表,在磁盘和内存之间进行转换,并且进行大量的查找和排序,所有这些操作花费的时间都很长。假设每次操作平均占用 30 ms,而通过网络返回只需要 1 ms,则总花费 31 ms。时间主要花费在 cpu 和内存占用,而不是网络等待上。所以,非阻塞 io 没有优势。而应用服务器,主要是接入客户端,对客户端认证,然后通过远程访问数据库,之后就是等待期,应用服务器大部分时间花费在等待数据库结果以及各种远程服务比如某种密钥计算或者认证。那么 redis 为什么用非阻塞?主要是 redis 的数据目标是存储到内存,而且没有大密度查找排序这种计算,所以 redis 的计算很快,也就不存在 io 问题
···
mysql的瓶颈一般是在磁盘IO,使用epoll后只是能处理的连接请求变多了,这些请求在磁盘IO已到瓶颈的时候被mysql处理时,只会使得IO更繁忙。