本文概览:事务分为扁平事务、保存点扁平事务、事务的嵌套。事务隔离中脏读、不可重复读、幻读三个读数据问题 定义及其解决方法
1 Mysql事务
1.1 事务种类
1.1.1 单事务
单事务,不涉及到事务的调用,即就是一个独立的事务。
1、提交回滚策略
单事务是最简单的一种,通过开启事务Begin和事务终止Commit/Rollback来控制一个事务。事务内的所有操作要么都更新数据库成功,要么都更新数据库失败。
BEGIN
Operation 1
Operation 2
.....
COMMIT/Rollback
1
2
3
4
5
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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提交数据的存在。幻读举例如下:
2、为了解决上面的问题,引入了隔离级别
(1)读未提交 Read Uncommited,RU
没有做任何隔离
(2) 读提交(Read commited,RC)
解决脏读。
(3)可重复读(Repeatable Read, RR)
解决脏读、不可重复读问题
(4)串行化(Serializable),即串行的执行事务,事务没有并发性了。
解决脏读、不可重复读、幻读 的问题。可以当成对表加上了一个表锁。
3、查看事务级别
查看当前会话的事务级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1
2
3
4
5
6
mysql>select@@tx_isolation;
+-----------------+
|@@tx_isolation|
+-----------------+
|REPEATABLE-READ|
+-----------------+
查看mysql事务级别
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1
2
3
4
5
6
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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
4
5
6
7
8
9
10
11
12
13
父事务{
子事务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也会回滚。