事务

事务(Transaction):

       是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,sql server 能将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性。事务通常是以begin transaction开始,以commit或rollback结束。Commint表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。

 

1、事务的特性  (ACID): 

1) 原子性(Atomic):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。 

2) 一致性(Consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。

 3) 隔离性(Isolation):一个事务的执行不能被其他事务所影响。事务必须是互相隔离的,防止并发读写同一个数据的情况发生 。 

 4) 持久性(Durable):一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改。

 

原子性(Atomic) 对数据的修改要么全部执行,要么全部不执行。

一致性(Consistent) 在事务执行前后,数据状态保持一致性。

隔离性(Isolated) 一个事务的处理不能影响另一个事务的处理。

持续性(Durable) 事务处理结束,其效果在数据库中持久化。

 

2.事务并发处理可能引起的问题

脏读(dirty read) 一个事务读取了另一个事务尚未提交的数据,

不可重复读(non-repeatable read) 一个事务的操作导致另一个事务前后两次读取到不同的数据

幻读(phantom read) 一个事务的操作导致另一个事务前后两次查询的结果数据量不同。

举例:

事务A、B并发执行时,

当A事务update后,B事务select读取到A尚未提交的数据,此时A事务rollback,则B读到的数据是无效的"脏"数据。

当B事务select读取数据后,A事务update操作更改B事务select到的数据,此时B事务再次读去该数据,发现前后两次的数据不一样。

当B事务select读取数据后,A事务insert或delete了一条满足A事务的select条件的记录,此时B事务再次select,发现查询到前次不存在的记录("幻影"),或者前次的某个记录不见了。

 

自动提交事务:每条单独的语句都是一个事务。每个语句后都隐含一个commit。 (默认) 

显式事务:以begin transaction显示开始,以commit或rollback结束。 

隐式事务:当连接以隐式事务模式进行操作时,sql server数据库引擎实例将在提交或回滚当前事务后自动启动新事务。无须描述事物的开始,只需提交或回滚每个事务。但每个事务仍以commit或rollback显式结束。连接将隐性事务模式设置为打开之后,当数据库引擎实例首次执行下列任何语句时,都会自动启动一个隐式事务:alter table,insert,create,open ,delete,revoke ,drop,select, fetch ,truncate table,grant,update在发出commit或rollback语句之前,该事务将一直保持有效。在第一个事务被提交或回滚之后,下次当连接执行以上任何语句时,数据库引擎实例都将自动启动一个新事务。该实例将不断地生成隐性事务链,直到隐性事务模式关闭为止。 

 

Java事务的类型

    Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。

1、JDBC事务

    JDBC 事务是用 Connection 对象控制的。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手工提交。 java.sql.Connection 提供了以下控制事务的方法:

public void setAutoCommit(boolean)

public boolean getAutoCommit()

public void commit()

public void rollback()

 

    使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。

 

2、JTA(Java Transaction API)事务

    JTA是一种高层的,与实现无关的,与协议无关的API,应用程序和应用服务器可以使用JTA来访问事务。

    JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。

    如果计划用 JTA 界定事务,那么就需要有一个实现 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。 XAConnection s 是参与 JTA 事务的 JDBC 连接。

    您将需要用应用服务器的管理工具设置 XADataSource .从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导。

     J2EE应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。

    XA 连接与非 XA 连接不同。一定要记住 XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。同时,应用程序一定不要对 XA 连接调用 java.sql.Connection.commit() 或者 java.sql.Connection.rollback() .

    相反,应用程序应该使用 UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() .

 

3、容器事务

    容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现。相对编码实现JTA事务管理, 我们可以通过EJB容器提供的容器事务管理机制(CMT)完成同一个功能,这项功能由J2EE应用服务器提供。这使得我们可以简单的指定将哪个方法加入事 务,一旦指定,容器将负责事务管理任务。这是我们土建的解决方式,因为通过这种方式我们可以将事务代码排除在逻辑编码之外,同时将所有困难交给J2EE容 器去解决。使用EJB CMT的另外一个好处就是程序员无需关心JTA API的编码,不过,理论上我们必须使用EJB.

4、三种Java事务差异

    1、JDBC事务控制的局限性在一个数据库连接内,但是其使用简单。

    2、JTA事务的功能强大,事务可以跨越多个数据库或多个DAO,使用也比较复杂。

    3、容器事务,主要指的是J2EE应用服务器提供的事务管理,局限于EJB应用使用。

5、总结

    Java事务控制是构建J2EE应用不可缺少的一部分,合理选择应用何种事务对整个应用系统来说至关重要。一般说来,在单个JDBC 连接连接的情况下可以选择JDBC事务,在跨多个连接或者数据库情况下,需要选择使用JTA事务,如果用到了EJB,则可以考虑使用EJB容器事务 

 

Java JDBC事务机制

  首先,我们来看看现有JDBC操作会给我们打来什么重大问题,比如有一个业务:当我们修改一个信息后再去查询这个信息,看是这是一个简单的业务,实现起来也非常容易,但当这个业务放在多线程高并发的平台下,问题自然就出现了,比如当我们执行了一个修改后,在执行查询之前有一个线程也执行了修改语句,这是我们再执行查询,看到的信息就有可能与我们修改的不同,为了解决这一问题,我们必须引入JDBC事务机制,其实代码实现上很简单,一下给出一个原理实现例子供大家参考:

private Connection conn = null;  

private PreparedStatement ps = null;  

try {   

    conn.setAutoCommit(false);  //将自动提交设置为false  

    ps.executeUpdate("修改SQL"); //执行修改操作  

    ps.executeQuery("查询SQL");  //执行查询操作                 

    conn.commit();      //当两个操作成功后手动提交  

} catch (Exception e) {  

    conn.rollback();    //一旦其中一个操作出错都将回滚,使两个操作都不成功  

    e.printStackTrace();  

  

JDBC对事务的支持体现在三个方面:

1.自动提交模式(Auto-commit mode)

Connection提供了一个auto-commit的属性来指定事务何时结束

a.当auto-commit为true时,当每个独立SQL操作的执行完毕,事务立即自动提交,也就是说每个SQL操作都是一个事务。

一个独立SQL操作什么时候算执行完毕,JDBC规范是这样规定的:

对数据操作语言(DML,如insert,update,delete)和数据定义语言(如create,drop),语句一执行完就视为执行完毕。

对select语句,当与它关联的ResultSet对象关闭时,视为执行完毕。

对存储过程或其他返回多个结果的语句,当与它关联的所有ResultSet对象全部关闭,所有update count(update,delete等语句操作影响的行数)和output parameter(存储过程的输出参数)都已经获取之后,视为执行完毕。

b. 当auto-commit为false时,每个事务都必须显示调用commit方法进行提交,或者显示调用rollback方法进行回滚。auto-commit默认为true。

JDBC提供了5种不同的事务隔离级别,在Connection中进行了定义。

 

2.事务隔离级别(Transaction Isolation Levels)

JDBC定义了五种事务隔离级别:

TRANSACTION_NONE JDBC驱动不支持事务

TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。

TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。

TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。

TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。

  

  事务隔离级别越高,为避免冲突所花的精力也就越多。Connection接口定义了五级,其中最低级别指定了根本就不支持事务,而最高级别则指定当事务在对某个数据库进行操作时,任何其它事务不得对那个事务正在读取的数据进行任何更改。通常,隔离级别越高,应用程序执行的速度也就越慢(由于用于锁定的资源耗费增加了,而用户间的并发操作减少了)。在决定采用什么隔离级别时,开发人员必须在性能需求和数据一致性需求之间进行权衡。

  当创建Connection对象时,其事务隔离级别取决于驱动程序,但通常是所涉及的数据库的缺省值。用户可通过调用setIsolationLevel方法来更改事务隔离级别。新的级别将在该连接过程的剩余时间内生效。要想只改变一个事务的事务隔离级别,必须在该事务开始前进行设置,并在该事务结束后进行复位。我们不提倡在事务的中途对事务隔离级别进行更改,因为这将立即触发commit方法的调用,使在此之前所作的任何更改变成永久性的。 

 

JDBC的数据隔离级别设置:

         为了解决与“多个线程请求相同数据”相关的问题,事务之间用锁相互隔开。多数主流的数据库支持不同类型的锁;因此,JDBC API 支持不同类型的事务,它们由 Connection 对象指派或确定。 

        为了在性能与一致性之间寻求平衡才出现了上面的几种级别。事务保护的级别越高,性能损失就越大。

        假定您的数据库和 JDBC 驱动程序支持这个特性,则给定一个 Connection 对象,您可以明确地设置想要的事务级别:

        conn.setTransactionLevel(TRANSACTION_SERIALIZABLE) ;

        可以通过下面的方法确定当前事务的级别:

            int level = conn.getTransactionIsolation();            

 

3.保存点(SavePoint)

        JDBC定义了SavePoint接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以rollback到该保存点处的状态,而不是rollback整个事务。Connection接口的setSavepoint和releaseSavepoint方法可以设置和释放保存点。

 

JDBC规范虽然定义了事务的以上支持行为,但是各个JDBC驱动,数据库厂商对事务的支持程度可能各不相同。如果在程序中任意设置,可能得不到想要的效果。为此,JDBC提供了DatabaseMetaData接口,提供了一系列JDBC特性支持情况的获取方法。比如,通过DatabaseMetaData.supportsTransactionIsolationLevel方法可以判断对事务隔离级别的支持情况,通过DatabaseMetaData.supportsSavepoints方法可以判断对保存点的支持情况。

 

以MySQL为例:

READ UNCOMMITTED 读取未提交的内容

在READ UNCOMMITTED隔离级,所有的事务都可以“看到”未提交事务的执行结果。在这种级别上,可能会产生很多问题,除非用户真的知道自己在做什么,并有很好的理由这样做。本隔离级很少用于实际应用,因为它的性能也不比其他级别好多少,而别的级别还有其他更多的优点。读取未提交数据,也被称为“脏读”(Dirty Read)

READ COMMITTED (读取提交内容)

大多数数据库系统的默认隔离级是READ COMMITED(但这不是MySql默认的)。它满足了隔离的早先简单定义:一个事务在开始时,只能“看见”已经提交事务所做的改变,一个事务从开始到提交前,所做的任何数据改变都是不可见的,除非已经提交。这种隔离级别也支持所谓的“不可重复读”(NonrepeatableRead)。这意味着用户运行同一语句两次,看到的结果是不同的。

REPEATABLE READ(可重读)

REPEATABLE READ 隔离级解决了READ UNCOMMITTED隔离导致的问题。他确保同一事务的多个实例在并发读取数据时,看“看到同样的”数据行。不过理论上。这会导致另一个棘手的问题:幻读(Phantom Read)。简单来说,幻读指当前用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围内的数据行时,看发现有新的“幻影”(Phantom)行。InnoDB和Falcon存储引擎通过多个版本并发控制(Multiversion Conccurency Control)机制解决了幻读问题。REPEATABLE READ 是MySql的默认事务隔离级别。InnoDB和Falcon存储引擎都遵循这种设置

SERIALIZABLE (可串行化)

SERIALIZABLE是最高级别的隔离级别,他通过强制的事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,SERIALIZABLE是在每个读的数据行上加锁。在这个隔离级别,可导致大量的超时(Timeout)现象和锁竞争(Lock Contention)现象。

 然后说说修改事务隔离级别的方法:

1.全局修改,修改my.ini配置文件,在最后加上

Xml代码 

  1. #可选参数有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.  
  2. [mysqld]  
  3. transaction-isolation = REPEATABLE-READ  

这里全局默认是REPEATABLE-READ,其实MySQL本来默认也是这个级别

2.对当前session修改,在登录mysql客户端后,执行命令:

Sql代码 

  1. -- 修改当前会话的隔离级别
  2. SET SESSION TRANSACTIONISOLATIONLEVELreadUNCOMMITTED;  

3、查询数据库的隔离级别

Sql代码 

  1. SELECT @@TX_ISOLATION;  

要记住mysql有一个autocommit参数,默认是on,他的作用是每一条单独的查询都是一个事务,并且自动开始,自动提交(执行完以后就自动结束了,如果你要适用select for update,而不手动调用 start transaction,这个for update的行锁机制等于没用,因为行锁在自动提交后就释放了),所以事务隔离级别和锁机制即使你不显式调用start transaction,这种机制在单独的一条查询语句中也是适用的,分析锁的运作的时候一定要注意这一点

再来说说锁机制:

共享锁:由读表操作加上的锁,加锁后其他用户只能获取该表或行的共享锁,不能获取排它锁,也就是说只能读不能写

排它锁:由写表操作加上的锁,加锁后其他用户不能获取该表或行的任何锁,典型是mysql事务中的

锁的范围:

行锁: 对某行记录加上锁

表锁: 对整个表加上锁

这样组合起来就有,行级共享锁,表级共享锁,行级排他锁,表级排他锁

Sql代码 

  1. start transaction;  
  2. select * fromuserwhere userId = 1 forupdate;  

执行完这句以后

1)当其他事务想要获取共享锁,比如事务隔离级别为SERIALIZABLE的事务,执行

Sql代码 

  1. select * fromuser;  

将会被挂起,因为SERIALIZABLE的select语句需要获取共享锁

2)当其他事务执行

Sql代码 

  1. select * fromuserwhere userId = 1 forupdate;  
  2. updateuserset userAge = 100 where userId = 1;   

 也会被挂起,因为for update会获取这一行数据的排它锁,需要等到前一个事务释放该排它锁才可以继续进行

下面来说说不同的事务隔离级别的实例效果,例子使用InnoDB,开启两个客户端A,B,在A中修改事务隔离级别,在B中开启事务并修改数据,然后在A中的事务查看B的事务修改效果:

1.READ-UNCOMMITTED(读取未提交内容)级别

1、A修改事务级别并开始事务,对user表做一次查询

Sql代码 

  1. mysql> select @@TX_ISOLATION;  
  2. +------------------+
  3. | @@TX_ISOLATION   |  
  4. +------------------+
  5. READ-UNCOMMITTED |  
  6. +------------------+
  7. 1 row inset (0.00 sec)  
  8. mysql> select * fromuser;  
  9. +----+------+
  10. | id | age  |  
  11. +----+------+
  12. |  1 |    1 |  
  13. |  2 |    2 |  
  14. |  3 |    3 |  
  15. +----+------+
  16. rowsinset (0.00 sec)  

2、B更新一条记录

Sql代码 

  1. mysql> start transaction;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> updateuserset age = 10 where id = 3;  
  4. Query OK, 1 row affected (0.03 sec)  
  5. Rows matched: 1  Changed: 1  Warnings: 0  
  6. mysql> select * fromuser;  
  7. +----+------+
  8. | id | age  |  
  9. +----+------+
  10. |  1 |    1 |  
  11. |  2 |    2 |  
  12. |  3 |   10 |  
  13. +----+------+
  14. rowsinset (0.00 sec)  

3、此时B事务还未提交,A在事务内做一次查询,发现查询结果已经改变

Sql代码 

  1. mysql> select * fromuser;  
  2. +----+------+
  3. | id | age  |  
  4. +----+------+
  5. |  1 |    1 |  
  6. |  2 |    2 |  
  7. |  3 |   10 |  
  8. +----+------+
  9. rowsinset (0.00 sec)  

4、B进行事务回滚

Sql代码 

  1. mysql> rollback;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> select * fromuser;  
  4. +----+------+
  5. | id | age  |  
  6. +----+------+
  7. |  1 |    1 |  
  8. |  2 |    2 |  
  9. |  3 |    3 |  
  10. +----+------+
  11. rowsinset (0.00 sec)  

5、A再做一次查询,查询结果又变回去了

Sql代码 

  1. mysql> select * fromuser;  
  2. +----+------+
  3. | id | age  |  
  4. +----+------+
  5. |  1 |    1 |  
  6. |  2 |    2 |  
  7. |  3 |    3 |  
  8. +----+------+
  9. rowsinset (0.00 sec)  

6、A表对user表数据进行修改

Sql代码 

  1. mysql> updateuserset age=10 where id = 3;  
  2. Query OK, 1 row affected (0.00 sec)  
  3. Rows matched: 1  Changed: 1  Warnings: 0  

7、B表重新开始事务后,对user表记录进行修改,修改被挂起,直至超时,但是对另一条数据的修改成功,说明A的修改对user表的数据行加行共享锁(因为可以使用select)

Java代码 

  1. mysql> update user set age = 10 where id = 3;  
  2.  ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction  
  3. mysql> update user set age = 10 where id = 2;  
  4. Query OK, 1 row affected (0.00 sec)  
  5. Rows matched: 1  Changed: 1  Warnings: 0
  6. mysql> select * from user where id = 3;  
  7. +----+------+  
  8. | id | age  |  
  9. +----+------+  
  10. |  3 |   10 |  
  11. +----+------+  
  12. 1 row in set (0.00 sec)  

可以看出READ-UNCOMMITTED隔离级别,当两个事务同时进行时,即使事务没有提交,所做的修改也会对事务内的查询做出影响,这种级别显然很不安全。但是在表对某行进行修改时,会对该行加上行共享锁

2. READ-COMMITTED(读取提交内容)

1、设置A的事务隔离级别,并进入事务做一次查询

Sql代码 

  1. mysql> SET SESSION TRANSACTIONISOLATIONLEVELREADCOMMITTED;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> SELECT @@TX_ISOLATION;  
  4. +----------------+
  5. | @@TX_ISOLATION |  
  6. +----------------+
  7. READ-COMMITTED |  
  8. +----------------+
  9. 1 row inset (0.00 sec)  
  10. mysql> select * fromuser;  
  11. +----+------+
  12. | id | age  |  
  13. +----+------+
  14. |  1 |    1 |  
  15. |  2 |    2 |  
  16. |  3 |    3 |  
  17. +----+------+
  18. rowsinset (0.00 sec)  

 2、B开始事务,并对记录进行修改

Sql代码 

  1. mysql> start transaction;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> updateuserset age = 10 where id = 3;  
  4. Query OK, 1 row affected (0.00 sec)  
  5. Rows matched: 1  Changed: 1  Warnings: 0  
  6. mysql> select * fromuser;  
  7. +----+------+
  8. | id | age  |  
  9. +----+------+
  10. |  1 |    1 |  
  11. |  2 |    2 |  
  12. |  3 |   10 |  
  13. +----+------+
  14. rowsinset (0.00 sec)  

 3、A再对user表进行查询,发现记录没有受到影响

Sql代码 

  1. mysql> select * fromuser;  
  2. +----+------+
  3. | id | age  |  
  4. +----+------+
  5. |  1 |    1 |  
  6. |  2 |    2 |  
  7. |  3 |    3 |  
  8. +----+------+
  9. rowsinset (0.00 sec)  

 4、B提交事务

Sql代码 

  1. mysql> commit;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> select * fromuser;  
  4. +----+------+
  5. | id | age  |  
  6. +----+------+
  7. |  1 |    1 |  
  8. |  2 |    2 |  
  9. |  3 |   10 |  
  10. +----+------+
  11. rowsinset (0.00 sec)  

 5、A再对user表查询,发现记录被修改

Sql代码 

  1. mysql> select * fromuser;  
  2. +----+------+
  3. | id | age  |  
  4. +----+------+
  5. |  1 |    1 |  
  6. |  2 |    2 |  
  7. |  3 |   10 |  
  8. +----+------+
  9. rowsinset (0.00 sec)  

 6、A对user表进行修改

Sql代码 

  1. mysql> start transaction;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> updateuserset age=100 where id = 3;  
  4. Query OK, 0 rows affected (0.00 sec)  
  5. Rows matched: 1  Changed: 0  Warnings: 0  

 7、B重新开始事务,并对user表同一条进行修改,发现修改被挂起,直到超时,但对另一条记录修改,却是成功,说明A的修改对user表加上了行共享锁(因为可以select)

Sql代码 

  1. mysql> start transaction;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> updateuserset age=20 where id = 3;  
  4.  ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  5. mysql> updateuserset age=20 where id = 2;  
  6. Query OK, 1 row affected (0.00 sec)  
  7. Rows matched: 1  Changed: 1  Warnings: 0  
  8. mysql> select * fromuser;  
  9. +----+------+
  10. | id | age  |  
  11. +----+------+
  12. |  1 |    1 |  
  13. |  2 |   20 |  
  14. |  3 |  100 |  
  15. +----+------+
  16. rowsinset (0.00 sec)  

READ-COMMITTED事务隔离级别,只有在事务提交后,才会对另一个事务产生影响,并且在对表进行修改时,会对表数据行加上行共享锁

3. REPEATABLE-READ(可重读)

1、A设置事务隔离级别,进入事务后查询一次

Sql代码 

  1. mysql> SET SESSION TRANSACTIONISOLATIONLEVELREPEATABLEREAD;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> SELECT @@TX_ISOLATION;  
  4. +-----------------+
  5. | @@TX_ISOLATION  |  
  6. +-----------------+
  7. REPEATABLE-READ |  
  8. +-----------------+
  9. 1 row inset (0.00 sec)  
  10. mysql> start transaction;  
  11. Query OK, 0 rows affected (0.00 sec)  
  12. mysql> select * fromuser;  
  13. +----+------+
  14. | id | age  |  
  15. +----+------+
  16. |  1 |    1 |  
  17. |  2 |    2 |  
  18. |  3 |    3 |  
  19. +----+------+
  20. rowsinset (0.00 sec)  

 2、B开始事务,并对user表进行修改

Sql代码 

  1. mysql> start transaction;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> select * fromuser;  
  4. +----+------+
  5. | id | age  |  
  6. +----+------+
  7. |  1 |    1 |  
  8. |  2 |    2 |  
  9. |  3 |    3 |  
  10. +----+------+
  11. rowsinset (0.00 sec)  
  12. mysql> updateuserset age=10 where id=3;  
  13. Query OK, 1 row affected (0.00 sec)  
  14. Rows matched: 1  Changed: 1  Warnings: 0  
  15. mysql> select * fromuser;  
  16. +----+------+
  17. | id | age  |  
  18. +----+------+
  19. |  1 |    1 |  
  20. |  2 |    2 |  
  21. |  3 |   10 |  
  22. +----+------+
  23. rowsinset (0.00 sec)  

 3、A查看user表数据,数据未发生改变

Sql代码 

  1. mysql> select * fromuser;  
  2. +----+------+
  3. | id | age  |  
  4. +----+------+
  5. |  1 |    1 |  
  6. |  2 |    2 |  
  7. |  3 |    3 |  
  8. +----+------+
  9. rowsinset (0.00 sec)  

 4、B提交事务

Sql代码 

  1. mysql> commit;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> select * fromuser;  
  4. +----+------+
  5. | id | age  |  
  6. +----+------+
  7. |  1 |    1 |  
  8. |  2 |    2 |  
  9. |  3 |   10 |  
  10. +----+------+
  11. rowsinset (0.00 sec)  

 5、A再进行一次查询,结果还是没有变化

Sql代码 

  1. mysql> select * fromuser;  
  2. +----+------+
  3. | id | age  |  
  4. +----+------+
  5. |  1 |    1 |  
  6. |  2 |    2 |  
  7. |  3 |    3 |  
  8. +----+------+
  9. rowsinset (0.00 sec)  

 6、A提交事务后,再查看结果,结果已经更新

Sql代码 

  1. mysql> commit;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> select * fromuser;  
  4. +----+------+
  5. | id | age  |  
  6. +----+------+
  7. |  1 |    1 |  
  8. |  2 |    2 |  
  9. |  3 |   10 |  
  10. +----+------+
  11. rowsinset (0.00 sec)  

 7、A重新开始事务,并对user表进行修改

Sql代码 

  1. mysql> start transaction;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> updateuserset age = 100 where id = 3;  
  4. Query OK, 1 row affected (0.00 sec)  
  5. Rows matched: 1  Changed: 1  Warnings: 0  

 8、B表重新开始事务,并对user表进行修改,修改被挂起,直到超时,对另一条记录修改却成功,说明A对表进行修改时加了行共享锁(可以select)

Sql代码 

  1. mysql> update user set age = 100 where id = 3;  
  2.  ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction  
  3. mysql> update user set age = 100 where id = 2;  
  4. Query OK, 1 row affected (0.02 sec)  
  5. Rows matched: 1  Changed: 1  Warnings: 0
  6. mysql> select * from user;  
  7. +----+------+  
  8. | id | age  |  
  9. +----+------+  
  10. |  1 |    1 |  
  11. |  2 |  100 |  
  12. |  3 |   10 |  
  13. +----+------+  
  14. 3 rows in set (0.00 sec)  

REPEATABLE-READ事务隔离级别,当两个事务同时进行时,其中一个事务修改数据对另一个事务不会造成影响,即使修改的事务已经提交也不会对另一个事务造成影响。在事务中对某条记录修改,会对记录加上行共享锁,直到事务结束才会释放。

4.SERIERLIZED(可串行化)

1、修改A的事务隔离级别,并作一次查询

Sql代码 

  1. mysql> SET SESSION TRANSACTIONISOLATIONLEVELSERIALIZABLE;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> SELECT @@TX_ISOLATION;  
  4. +----------------+
  5. | @@TX_ISOLATION |  
  6. +----------------+
  7. SERIALIZABLE   |  
  8. +----------------+
  9. 1 row inset (0.00 sec)  
  10. mysql> start transaction;  
  11. Query OK, 0 rows affected (0.00 sec)  
  12. mysql> select * fromuser;  
  13. +----+------+
  14. | id | age  |  
  15. +----+------+
  16. |  1 |    1 |  
  17. |  2 |    2 |  
  18. |  3 |    3 |  
  19. +----+------+
  20. rowsinset (0.00 sec)  

2、B对表进行查询,正常得出结果,可知对user表的查询是可以进行的

Sql代码 

  1. mysql> select * fromuser;  
  2. +----+------+
  3. | id | age  |  
  4. +----+------+
  5. |  1 |    1 |  
  6. |  2 |    2 |  
  7. |  3 |    3 |  
  8. +----+------+
  9. rowsinset (0.00 sec)  

3、B开始事务,并对记录做修改,因为A事务未提交,所以B的修改处于等待状态,等待A事务结束,最后超时,说明A在对user表做查询操作后,对表加上了共享锁

Sql代码 

  1. mysql> start transaction;  
  2. Query OK, 0 rows affected (0.00 sec)  
  3. mysql> updateuserset age = 10 where id = 3;  
  4.  ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 SERIALIZABLE事务隔离级别最严厉,在进行查询时就会对表或行加上共享锁,其他事务对该表将只能进行读操作,而不能进行写操作。

 

浅谈Spring事务隔离级别

一、Propagation (事务的传播属性)

Propagation :  key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED--嵌套事务,事务嵌套在父事务中

1: PROPAGATION_REQUIRED

加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务

比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,

ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA

的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被

提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

2: PROPAGATION_SUPPORTS

如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行

3: PROPAGATION_MANDATORY

必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常

4: PROPAGATION_REQUIRES_NEW

这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,

那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,

他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在

两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,

如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

5: PROPAGATION_NOT_SUPPORTED

当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,

那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

6: PROPAGATION_NEVER

不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,

那么ServiceB.methodB就要抛出异常了。

7: PROPAGATION_NESTED

理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,

而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。

而Nested事务的好处是他有一个savepoint。

*****************************************
ServiceA {

  /**
   * 事务属性配置为 PROPAGATION_REQUIRED
   */
   void methodA() {
      try {
         //savepoint
         ServiceB.methodB(); //PROPAGATION_NESTED 级别
      } catch (SomeException) {
         // 执行其他业务, 如 ServiceC.methodC();
      }
   }
}
********************************************

也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如

ServiceC.methodC,继续执行,来尝试完成自己的事务。

但是这个事务并没有在EJB标准中定义。

二、Spring事务的隔离级别

spring事务的实现依赖于数据库事务的实现

 1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.

      另外四个与JDBC的隔离级别相对应

 2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。

      这种隔离级别会产生脏读,不可重复读和幻像读。

 3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据

 4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。

      它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

 5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

      除了防止脏读,不可重复读外,还避免了幻像读。

三、Oracle中的隔离级别及实现机制: 

Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。所以Oracle不支持脏读,即Oracle中不允许一个会话读取其他事务未提交的数据修改结果,从而防止了由于事务回滚发生的读取不正确。

Oracle回滚段(快照),在修改数据记录时,会把这些记录被修改之前的结果存入回滚段或撤销段中。Oracle读取操作不会阻碍更新操作,更新操作也不会阻碍读取操作

Oracle缺省的配置是Read Committed隔离级别(也称为语句级别的隔离),在这种隔离级别下,如果一个事务正在对某个表执行 DML操作,而这时另外一个会话对这个表的记录执行读取操作,则Oracle会去读取回滚段或撤销段中存放的更新之前的记录,而不会象SQL Server一样等待更新事务的结束。

Oracle的Serializable隔离级别(也称为事务级别的隔离),事务中的读取操作只能读取这个事务开始之前已经提交的数据结果。如果在读取时,其他事务正在对记录执行修改,则Oracle就会在回滚段或撤销段中去寻找对应的原来未经修改的记录(而且是在读取操作所在的事务开始之前存放于回滚段或撤销段的记录),这时读取操作也不会因为相应记录被更新而等待。

转载于:https://my.oschina.net/fuyong/blog/752843

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值