`
boy00fly
  • 浏览: 198023 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

一些老帖引发的思考之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:对于上述内容中有误的地方,欢迎拍砖!

分享到:
评论
1 楼 leecyz 2012-04-12  
分析比较不错,目前没发现问题,我被此事烦了很久。

相关推荐

    Spring 管理事务(传播特性、隔离级别、readonly).rar

    本知识点将深入探讨Spring中的事务管理,主要包括事务的传播特性、隔离级别以及readonly属性。 1. 事务的传播特性: 在Spring中,事务的传播特性定义了在一个事务方法被另一个事务方法调用时,应该如何处理事务...

    C#基础:基于const与readonly的深入研究

    在C#编程语言中,`const` 和 `readonly` 关键字都用于声明常量,但它们之间存在一些显著的区别。常量是不可更改的值,一旦被赋值后就不能再次修改。 首先,`const` 关键字用于声明编译时常量。这意味着,任何用 `...

    readonly总结

    本篇文章将深入探讨 `readonly` 的含义、用法以及在不同编程语言中的具体实现。 首先,我们来看看 `readonly` 在 C# 中的应用。在 C# 中,`readonly` 关键字用于声明一个只读字段,这意味着一旦在构造函数中初始化...

    QTP处理readonly控件

    ### QTP处理readonly控件 在自动化测试领域中,QTP(Quick Test Professional)是一款非常受欢迎的工具,它主要用于功能测试、回归测试等自动化测试任务。对于某些特定类型的控件,如只读(readonly)控件,QTP提供...

    c#中const与readonly区别

    在C#编程语言中,`const`和`readonly`关键字都是用来声明不可变变量的,但它们之间存在一些重要的区别,这些区别主要体现在初始化、作用域、存储位置以及使用场景等方面。 ### 1. 初始化 - **const**:`const`...

    事务传播特性&事务隔离级别

    事务传播特性&事务隔离级别 事务传播特性是指在 Java 中,事务的传播行为,即在多个...事务传播特性和事务隔离级别是 Java 中事务管理的两个重要方面,需要深入了解和正确使用,以确保事务的正确执行和数据的一致性。

    C#编程中 readonly与const的区别

    在C#编程语言中,`readonly`和`const`这两个关键字都用于定义只读字段,但它们之间存在一些重要的区别。理解这些区别有助于更好地设计和实现软件系统,尤其是在需要创建不可变数据或常量时。 #### 二、`const`...

    浅谈SpringBoot之事务处理机制

    浅谈SpringBoot之事务处理机制 SpringBoot之事务处理机制是Spring框架中的一种机制,用于管理事务。事务是指一系列的数据库操作,例如插入、更新、删除等,事务处理机制的目的就是为了确保这些操作的原子性、一致性...

    spring框架的学习--事务

    此注解可以包含各种事务属性,如isolation(隔离级别)、propagation(传播行为)、timeout(超时时间)和readOnly(只读事务)。 在Spring中,事务传播行为有七种,包括REQUIRED、SUPPORTS、MANDATORY、REQUIRES_...

    spring声明事务的配置

    Spring声明式事务管理是Spring框架的核心特性之一,它允许开发者在不侵入业务代码的情况下,对应用程序的事务进行管理。这种方式极大地提高了代码的可维护性和灵活性。以下是对Spring声明式事务配置的详细说明: 1....

    实例详解Spring JDBC事务管理.doc

    事务的readOnly是指事务是否是只读的。Spring的事务管理机制提供了readOnly机制,开发人员可以根据需要设置合适的readOnly值。 事务异常处理 事务的异常处理是指事务在执行过程中出现错误时的处理机制。Spring的...

    spring_事务管理(实例代码)

    例如,`@Transactional(readOnly = true)`表示该方法仅用于查询,不修改数据,Spring会为此优化事务设置。 三、事务的ACID属性 事务的四大特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation...

    C#中增加SQLite事务操作支持与使用方法

    C# 中增加 SQLite 事务操作支持与使用方法 在 C# 中使用 SQLite 数据库时,事务操作是非常重要的一部分。事务操作可以确保数据库的数据一致性和完整性,避免数据不一致的情况。下面将详细介绍 C# 中增加 SQLite ...

    Spring事务原理、Spring事务配置的五种方式

    Spring事务原理和配置 Spring事务原理是指Spring框架中的一种机制,用于管理事务,并提供了多种配置方式。事务是指一系列的操作,作为一个整体执行,如果其中某个操作失败,整个事务将回滚。Spring事务原理围绕着两...

    使用Spring的声明式事务----Annotation注解方式

    在Spring框架中,声明式事务管理是其核心特性之一,它允许开发者无需手动处理事务的开始、提交、回滚等操作,而是通过配置或者注解的方式进行声明,从而简化了事务管理的复杂性。本篇文章将深入探讨如何使用Spring的...

    Spring使用TransactionProxyFactoryBean声明式事务配置实例

    在Spring框架中,声明式事务管理是通过元数据(如XML配置或注解)来定义事务边界,使得开发者无需在业务代码中显式调用事务管理API,即可实现事务的控制。TransactionProxyFactoryBean是Spring提供的一种工具,用于...

    深入探讨C#中的const、readonly关键字

    在C#编程语言中,`const`和`readonly`关键字都是用来声明不可变的变量,但它们之间存在着一些重要的区别。本文将深入探讨这两个关键字,帮助开发者理解和掌握它们的使用场景以及背后的原理。 首先,`const`关键字...

    SSM中进行注解式和XML配置式事务管理代码

    在Java企业级开发中,Spring、Struts和MyBatis(简称SSM)是一个常见的框架组合,...通过ssmJarTemplateWithTransaction这个示例项目,你可以深入学习和实践这两种事务管理方法,从而更好地理解和运用在实际开发中。

    HTML中Select不用Disabled实现ReadOnly的效果

    方法如下: 代码如下:<select onbeforeactivate=”return false” onfocus=”this.blur()” onmouseover=”this.setCapture()” onmouseout=”this.releaseCapture()”> <option>1 <...

Global site tag (gtag.js) - Google Analytics