一些老帖引发的思考之readonly事务深入挖掘

写这篇文章的缘由,在于读过论坛中的一些帖子引发的疑问,先来看一下帖子的内容。

 

robbin的一个帖子

只读查询是否需要启动事务管理,兼论只读事务

http://www.iteye.com/topic/8850

 

有人真正测试过spring事务中readonly吗

http://www.iteye.com/topic/319768


1. 首先最根本的,我们要看一下数据库中对于readonly事务的定义!

 

第一个帖子中已经给出了结论:

Oracle默认情况下保证了SQL语句级别的读一致性,即在该条SQL语句执行期间,它只会看到执行前点的数据状态,而不会看到执行期间数据被其他SQL改变的状态。 

而Oracle的只读查询(read-only transaction)则保证了事务级别的读一致性,即在该事务范围内执行的多条SQL都只会看到执行前点的数据状态,
而不会看到事务期间的任何被其他SQL改变的状态。 

 

 这个是Oracle对于只读事务的描述,要保证事务级别的读一致性有两种方式,第一个帖子中也给出了结论:

一是用SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
二是用SET TRANSCATION READ ONLY

 

我们来做一下测试(采用第二种方式):

编写一个oracle的存储过程:

 

create or replace procedure p_test as
ware_code varchar2(14);
begin
  set transaction read only;
  select t.ware_code into ware_code from tp_area t where t.area_code = '1';
  dbms_output.put_line(ware_code);
  dbms_lock.sleep(10);--暂停10秒
  select t.ware_code into ware_code from tp_area t where t.area_code = '1';
  dbms_output.put_line(ware_code);
end p_test;

 

 area_code是tp_area表的主键。在暂停的10秒钟内,我们去更改area_code为1的这条记录中ware_code的值,并提交。

经过测试发现,两次的输出结果一致,这就证明了正确性。

ps:对于oracle中只读事务的描述,推荐参考《Oracle 9i&10g编程艺术:深入数据库体系结构》这本书!

 

但是我们要说明,并不是所有的数据库都支持readonly事务。

 

2. 接下来我们讨论jdbc中对于readonly事务的描述

 

jdk1.6中java.sql.Connection接口中定义的方法

setReadOnly(boolean readOnly) throws SQLException的描述:将此连接设置为只读模式,作为驱动程序启用数据库优化的提示。

例如:此连接设置为只读模式后,通知Oracle后,Oracle做自己的优化;通知DB2后,DB2做自己的优化等等,但是并不一定对于数据库而言这就是readonly事务,此readonly并非彼readonly!

 

讲到这里,很多人可能并不信,难道setReadOnly为true之后数据库并不是以readonly事务执行的?

 

我们以实际的测试结果说话:

测试环境一:

数据库:Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
oracle jdbc驱动版本:Oracle JDBC Driver version - 9.0.2.0.0 此驱动已经是很老的了,但是相信还是有很多项目还在用就是ojdbc14.jar。
jdk版本:jdk 1.6.0_20

 

    测试代码:

Class.forName("oracle.jdbc.OracleDriver");                                                                
Connection conn = DriverManager.getConnection(url, username, password);                                   
conn.setAutoCommit(false);                                                                                
conn.setReadOnly(true);//启动只读模式                                                                                   
PreparedStatement ps = conn.prepareStatement("select s.ware_code from tp_area s where s.area_code = '1'");
ResultSet rs = ps.executeQuery();                                                                         
rs.next();                                                                                                
System.out.println(rs.getString(1));                                                                      
                                                                                                          
Thread.sleep(10000);//暂停10秒                                                                            
                                                                                                          
ps = conn.prepareStatement("select s.ware_code from tp_area s where s.area_code = '1'");                  
rs = ps.executeQuery();                                                                                   
rs.next();                                                                                                
System.out.println(rs.getString(1));                                                                      
                                                                                                          
ps.close();                                                                                               
conn.commit();                                                                                            

 

    在暂定的10秒钟内,我通过pl/sql手动去修改了这条记录中ware_code的值,并提交,那这两次的输出结果一样吗?

    测试的结果是:这两次的输出结果是一致的!

    咦,这不是对的吗?oracle启动了只读事务,根据事务级都一致性的原则,两次读出来的应该是同一时间点的数据,应该一致啊!

    好,别着急,我们再来看一个测试。

 

测试环境二:

数据库:Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production                     
oracle jdbc驱动版本:Oracle JDBC Driver version - 11.2.0.1.0 这种从官方网站上面下载的最新的驱动ojdbc5.jar
jdk版本:jdk 1.6.0_20                                                                                    

 

   测试代码(与上面的测试代码一致):

Class.forName("oracle.jdbc.OracleDriver");                                                                
Connection conn = DriverManager.getConnection(url, username, password);                                   
conn.setAutoCommit(false);                                                                                
conn.setReadOnly(true);                                                                                   
PreparedStatement ps = conn.prepareStatement("select s.ware_code from tp_area s where s.area_code = '1'");
ResultSet rs = ps.executeQuery();                                                                         
rs.next();                                                                                                
System.out.println(rs.getString(1));                                                                                                                                                          
Thread.sleep(10000);//暂停10秒                                                                                                                                                                 
ps = conn.prepareStatement("select s.ware_code from tp_area s where s.area_code = '1'");                  
rs = ps.executeQuery();                                                                                   
rs.next();                                                                                                
System.out.println(rs.getString(1));                                                                      
                                                                                    
ps.close();                                                                                               
conn.commit();                                                                                                                              

   在暂定的10秒钟内,我同样还是通过pl/sql手动去修改了这条记录中ware_code的值,那这两次的输出结果一样吗?

 

   测试的结果是:这两次的输出结果是不一致的!

 

   --------------------------------------------------------------------分割线--------------------------------------------------------------------------------

 

   是不是不太敢相信自己的眼睛,我开始的时候也是很纳闷,为啥会这样!!??

   我们先来看一下 Oracle JDBC Driver version - 9.0.2.0.0 版本的的api中的说明:

oracle.jdbc.driver.OracleConnection
public void setReadOnly(boolean value) throws java.sql.SQLException
Sets the Connection as read only if the value is true and the enables the Connection for writing (updat/delete/insert) with the value as false.
 

   但是最直接有效的方式还是看源码!

   好的,我们来看一下Oracle JDBC Driver version - 9.0.2.0.0 ojdbc14.jar这个版本的驱动源码:

//jdbc.oracle.driver.OracleConnection 的代码片段
public void setReadOnly(boolean flag)           
    throws SQLException                         
{                                               
    PreparedStatement preparedstatement = null; 
    try                                         
    {                                           
        String s = null;                        
        if (flag)                               
            s = "SET TRANSACTION READ ONLY";    
        else                                    
            s = "SET TRANSACTION READ WRITE";   
        preparedstatement = prepareStatement(s);
        preparedstatement.execute();            
    }                                           
    finally                                     
    {                                           
        if (preparedstatement != null)          
            preparedstatement.close();          
    }                                           
    m_readOnly = flag;                          
}                                               

 从源码中我们可以很明显的看出来当setReadOnly为true时,究竟做了什么事-->"SET TRANSACTION READ ONLY",

 所以在此版本的jdbc实现中只要你setReadOnly为true,则对于数据库而言就是以只读事务来执行。

 

 那Oracle JDBC Driver version - 11.2.0.1.0这个版本的驱动究竟怎么回事呢?

 首先从这个版本的api中,我们已经找不到oracle.jdbc.driver.OracleConnection这个类了,

我们来看oracle.jdbc.OracleConnectionWrapper implements oracle.jdbc.OracleConnection

 虽然api中存在public void setReadOnly(boolean readOnly) throws java.sql.SQLException这个方法,但是没有任何描述,很奇怪。

 

 我们来看一下源码的片段:

 

protected oracle.jdbc.OracleConnection connection;
.....
public void setReadOnly(boolean flag)
        throws SQLException
{
        connection.setReadOnly(flag);
}

//OracleConnection接口声明:public interface OracleConnection extends Connection

 

你能够看出什么来吗? 是啊,根本就没做什么是嘛,对的,什么事情都没做!

ps:貌似此版本的驱动对于readonly属性就是抛弃了,没起作用!(没有再做深入研究,不知这样讲是否正确)

 

甚至说我如下的代码都可以执行通过:

 

Class.forName("oracle.jdbc.OracleDriver");                                            
Connection conn = DriverManager.getConnection(url, username, password);               
conn.setAutoCommit(false);                                                            
conn.setReadOnly(true);                                                               
                                                                                      
PreparedStatement ps = conn.prepareStatement("update tp_area t set t.ware_code ='t'");
ps.executeUpdate();                                                                   
ps.close();                                                                           
conn.commit();                                                                        

 

 设置只读后,我还能执行更新操作,并且运行测试都是Ok的!

 

但是如果我想要实现oracle的readonly事务该怎么办呢?

你可以从Oracle JDBC Driver version - 9.0.2.0.0 ojdbc14.jar源码中得到思路,代码如下:

Class.forName("oracle.jdbc.OracleDriver");                                              
Connection conn = DriverManager.getConnection(url, username, password);                 
conn.setAutoCommit(false);                                                              
conn.setReadOnly(true);                                                                 
//新增的两行                                                                            
PreparedStatement ps = conn.prepareStatement("set transaction read only");              
ps.execute();                                                                           
                                                                                        
ps = conn.prepareStatement("select s.ware_code from tp_area s where s.area_code = '1'");
ResultSet rs = ps.executeQuery();                                                       
rs.next();                                                                              
System.out.println(rs.getString(1));                                                    
                                                                                        
Thread.sleep(10000);//暂停10秒                                                          
                                                                                        
ps = conn.prepareStatement("select s.ware_code from tp_area s where s.area_code = '1'");
rs = ps.executeQuery();                                                                 
rs.next();                                                                              
System.out.println(rs.getString(1));                                                    
                                                                                        
ps.close();                                                                             
conn.commit();                                                                          

对的,就是设置手动显示地 "set transaction read only"即可!ps:不知道还有没有其他的方式?

 

经过上述分析后是不是豁然开朗的许多!

总结:1. 首先jdbc的规范中已经说明了readonly只是将此连接设置为只读模式,作为驱动程序启用数据库优化的提示,并不一定以只读事务执行!

 2.  对于oracle的jdbc驱动而言,不同版本的驱动会得出不同的结论!

但是我不清楚的是为什么oracle在后续驱动中,不支持了readonly事务了呢?处于性能的考虑?还是别的其他原因,有待求证!

 

3. SSH架构中Spring的readonly事务

底层的驱动都可能不支持readonly事务,你说Spring的readonly事务能管用吗?答案是很显然的,当然不一定支持。

第二个帖子中的theone说的是对的。

“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,
那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。

 

ps:对于上述内容中有误的地方,欢迎拍砖!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值