本文概览:事务分为扁平事务、保存点扁平事务、事务的嵌套。事务隔离中脏读、不可重复读、幻读三个读数据问题 定义及其解决方法

1 Mysql事务

1.1 事务种类

1.1.1 单事务

单事务,不涉及到事务的调用,即就是一个独立的事务。

1、提交回滚策略

单事务是最简单的一种,通过开启事务Begin和事务终止Commit/Rollback来控制一个事务。事务内的所有操作要么都更新数据库成功,要么都更新数据库失败。

BEGIN

Operation 1

Operation 2

.....

COMMIT/Rollback


BEGIN

Operation1

Operation2

.....

COMMIT/Rollback

2、主要缺点

单事务是不能提交或回滚事务的某些操作。可以通过带保存点实现

1.1.2 带有保存点的单事务

可以在事务中设置一个保存点,然后回滚时,可以只回滚到此保存点,其他的操作都可以进行提交。如果存在多个保存点p1、p2等,那么回滚时可以指定保存点。比如回滚到p1或者只回滚到p2。

BEGIN

Operation 0

设置保存点p1

Operation 1

回滚到保存点1

设置保存点p2

Operation 2

回滚到保存点2

.....

COMMIT

BEGIN

Operation0

设置保存点p1

Operation1

回滚到保存点1

设置保存点p2

Operation2

回滚到保存点2

.....

COMMIT

1.2 事务相关的sql

1. 提交和回滚

BEGIN或START TRANSACTION;显示地开启一个事务;

COMMIT或COMMIT WORK,事务提交;

ROLLBACK或ROLLBACK WORK,事务回滚;

2. 保存点

SAVEPOINT identifier;SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;

RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;

ROLLBACK TO identifier;把事务回滚到指定保存点;

3. 设置隔离级别

SET TRANSACTION:用来设置事务的隔离级别。InnoDB存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。

1.3 事务隔离级别

在数据库操作中,为了有效保证并发事务读取数据的正确性,引入了事务隔离级别。

1、不同事务之前会互相影响,会存在三个问题:

(1)脏读,A事务读取B事务未提交数据。造成问题就是,B事务回滚,那么A事务读取数据是不合法的。

(2)不可重复读。A事务读取 B事务提交的update /delet/inset数据。A事务读取一个数据,B事务读取并修改这个数据,然后提交,A事务第二次读取就是最新的数据。

(3)幻读。在保证可重复读基础上,即A事务读不会读取到B事务的已提交数据时,但是A事务在做insert或delete 数据时会感知事务B提交数据的存在。幻读举例如下:

mysql 嵌套查询返回多个字段 mysql嵌套事务的提交与回滚_事务回滚

2、为了解决上面的问题,引入了隔离级别

(1)读未提交 Read Uncommited,RU

没有做任何隔离

(2) 读提交(Read commited,RC)

解决脏读。

(3)可重复读(Repeatable Read, RR)

解决脏读、不可重复读问题

(4)串行化(Serializable),即串行的执行事务,事务没有并发性了。

解决脏读、不可重复读、幻读 的问题。可以当成对表加上了一个表锁。

3、查看事务级别

查看当前会话的事务级别

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+


mysql>select@@tx_isolation;
+-----------------+
|@@tx_isolation|
+-----------------+
|REPEATABLE-READ|
+-----------------+

查看mysql事务级别

mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+


mysql>select@@global.tx_isolation;
+-----------------------+
|@@global.tx_isolation|
+-----------------------+
|REPEATABLE-READ|
+-----------------------+

1.4 事务相关问题

1.4.1 一个事务在没有提交时,执行完sql是否已经更新到数据库

假设一个事务对数据库进行更新操作sql1,那么在执行完sql1还没有进行事务提交时,此时sql1对数据库操作中数据是否发生了变化?.

发生了变化,但是其他事务是否可以看到是根据事务隔离性来确定的。

2JDBC事务的操作

事务是针对一个连接Connection,所以JDBC通过Connection来实现事务。JDBC封装了一个Conncection接口,由这个类来完成sql执行(通过Statement/PreparedStatement来执行sql)、事务提交(connection#commit())、事务回滚(conection#rollback())。

2.1 扁平事务

1、怎么使用事务

将自动提交设置为false,即conn.setAutoCommit(false),然后手动conn.commit()、或者conn.rollback()。

2、谁在执行事务

如下代码,可以通过操作Connection来执行事务提交(conncetion.commit())和回滚(connection.rollback())。所以 一个事务内的所有sql,都是由同一个Connection来执行。

public void save(User user) throws SQLException{

Connection conn=jdbcDao.getConnection();

//1. 第一步 为了使用连接的事务,需要设置不自动的提交.

在使用事务时,自动提交应该被禁用,因为只有这样事务才不会自动提交

conn.setAutoCommit(false);
try {
PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)");
ps.setString(1,user.getName());
ps.setInt(2,user.getAge());
ps.execute();
//2. 第二步 事务提交
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//3.第二步 事务回滚
conn.rollback();
}finally{
conn.close();
}
}


publicvoidsave(Useruser)throwsSQLException{

Connectionconn=jdbcDao.getConnection();

//1. 第一步 为了使用连接的事务,需要设置不自动的提交.

在使用事务时,自动提交应该被禁用,因为只有这样事务才不会自动提交

conn.setAutoCommit(false);
try{
PreparedStatementps=conn.prepareStatement("insert into user(name,age) value(?,?)");
ps.setString(1,user.getName());
ps.setInt(2,user.getAge());
ps.execute();
//2. 第二步 事务提交
conn.commit();
}catch(Exceptione){
e.printStackTrace();
//3.第二步 事务回滚
conn.rollback();
}finally{
conn.close();
}
}

2.2 带有保存点事务

代码如下

public static void main(String[] args) {
Connection con = null;
Savepoint savepoint = null;
try {
con = DBConnection.getConnection();
// 1.关闭自动提交
con.setAutoCommit(false);
// 2. 执行sql更新或者插入操作
.........
// 3.设置保存点
savepoint = con.setSavepoint("EmployeeSavePoint");
// 4. 执行sql更新或者插入操作
.........
// 5.提交
con.commit();
}
catch (SQLException e) {
e.printStackTrace();
try {
if (savepoint == null) {
con.rollback();
} else {
//6.回滚到保存点
con.rollback(savepoint);
// 7.还需要执行commit
// Q:对于回滚到保存点的操作,为什么需要执行这个commit?
// A:因为在保存点之前的sql语句是需要提交的,所以这里增加了commit操作
con.commit();
}
}
catch (SQLException e1) {
System.out.println("SQLException in rollback" + e.getMessage());
}
}
finally {
try {
if (con != null)
con.close();
}
catch (SQLException e) {
e.printStackTrace();
}
}
}


publicstaticvoidmain(String[]args){
Connectioncon=null;
Savepointsavepoint=null;
try{
con=DBConnection.getConnection();
// 1.关闭自动提交
con.setAutoCommit(false);
// 2. 执行sql更新或者插入操作
.........
// 3.设置保存点
savepoint=con.setSavepoint("EmployeeSavePoint");
// 4. 执行sql更新或者插入操作
.........
// 5.提交
con.commit();
}
catch(SQLExceptione){
e.printStackTrace();
try{
if(savepoint==null){
con.rollback();
}else{
//6.回滚到保存点
con.rollback(savepoint);
// 7.还需要执行commit
// Q:对于回滚到保存点的操作,为什么需要执行这个commit?
//  A:因为在保存点之前的sql语句是需要提交的,所以这里增加了commit操作
con.commit();
}
}
catch(SQLExceptione1){
System.out.println("SQLException in rollback"+e.getMessage());
}
}
finally{
try{
if(con!=null)
con.close();
}
catch(SQLExceptione){
e.printStackTrace();
}
}
}

3Spring事务

当存在多个事务,进行嵌套调用时,根据提交和回滚策略可以划分为不同事务类型,这里主要介绍扁平事务、嵌套事务、独立事务三种类型嵌套调用事务,对于mysql并没有支持这三种嵌套调用相关的事务,但是在spring中通过@Transactional的传播属性来实现这三种事务。举例来说明这三种事务,如下是一个事务的嵌套,在父事务中又调用了三个子事务。

父事务{

子事务1{

...

}

子事务2{

...

}

子事务3{

...

}

}


父事务{

子事务1{

...

}

子事务2{

...

}

子事务3{

...

}

}

1、扁平类型

对于扁平类型事务,这三个子事务以及父事务,要么都提交,要么都失败。

2、嵌套事务

嵌套事务作用:对于一个事务中存在很多个子事务,为了保证一个子事务失败,不会造成所有的事务都回滚。

嵌套事务的特点:

内层事务之间是独立的,所以其中一个内层事务失败,不影响其他内层事务和外层事务提交。只是回滚这个内层事务即可

外层事务回滚,所有子事务都需要回滚,即使子事务已经提交了。

只有外层事务提交成功后,子事务的commit操作才会生效。

对于上面例子,假设现在子事务1成功执行,子事务2失败,子事务3成功。子事务2失败了不影响后面的事务3的提交,最终结果就是根据父事务执行结果分为两种情况:

情况1 父事务commit成功,那么此时子事务1和3的操作更新到数据库,因为事务2回滚了,所以子事务2不起作用

情况2 父事务回滚了,那么子事务1、2、3的commit操作全部无效

3、独立事务

子事务和子事务之间,子事务和外层事务之间,都是独立的互不影响提交,即在嵌套事务的基础上,进行了增强,如下场景:

子事务2提交了,父事务回滚了,此时事务2还是会提交,但是在嵌套事务中,此时事务2也会回滚。