目录
本文概览:事务分为扁平事务、保存点扁平事务、事务的嵌套。事务隔离中脏读、不可重复读、幻读三个读数据问题 定义及其解决方法
1 Mysql事务
1.1 事务种类
1.1.1 单事务
单事务,不涉及到事务的调用,即就是一个独立的事务。
1、提交回滚策略
单事务是最简单的一种,通过开启事务Begin和事务终止Commit/Rollback来控制一个事务。事务内的所有操作要么都更新数据库成功,要么都更新数据库失败。
1 2 3 4 5 |
BEGIN Operation 1 Operation 2 ..... COMMIT/Rollback |
2、主要缺点
单事务是不能提交或回滚事务的某些操作。可以通过带保存点实现
1.1.2 带有保存点的单事务
可以在事务中设置一个保存点,然后回滚时,可以只回滚到此保存点,其他的操作都可以进行提交。如果存在多个保存点p1、p2等,那么回滚时可以指定保存点。比如回滚到p1或者只回滚到p2。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
BEGIN Operation 0 设置保存点p1 Operation 1 回滚到保存点1 设置保存点p2 Operation 2 回滚到保存点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、查看事务级别
查看当前会话的事务级别
1 2 3 4 5 6 |
mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ |
查看mysql事务级别
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对数据库操作中数据是否发生了变化?.
发生了变化,但是其他事务是否可以看到是根据事务隔离性来确定的。
2 JDBC事务的操作
事务是针对一个连接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来执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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(); } } |
2.2 带有保存点事务
代码如下
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 |
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(); } } } |
3 Spring事务
当存在多个事务,进行嵌套调用时,根据提交和回滚策略可以划分为不同事务类型,这里主要介绍扁平事务、嵌套事务、独立事务三种类型嵌套调用事务,对于mysql并没有支持这三种嵌套调用相关的事务,但是在spring中通过@Transactional的传播属性来实现这三种事务。举例来说明这三种事务,如下是一个事务的嵌套,在父事务中又调用了三个子事务。
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也会回滚。