Java中事务的特性有四种,原子性、一致性、隔离性、持久性mysql
原子性:若是执行一条sql,底层是默认执行事务的,叫作隐形事务,当执行多条sql语句的时候,多条sql语句不能够进行分割,必须所有一次性执行完成,就是要么所有完成,要么失败;sql
一致性:事务完成后,数据状态保持一致性,能量守恒定律,哈哈哈;数据库
隔离性:多个事务并发访问数据库,事务对数据的修改,须要与其余事务进行隔离;安全
持久性:数据修改完成,数据持久化存储;并发
使用JDBC,须要先关闭主动提交事务,而后执行多条SQL语句,而后提交,若是报错进行回滚事务,在最后启动自动提交,由于执行1条sql语句的时候,也会开启事务,叫作隐形事务,默认进行提交,若是关闭了不启动,之后执行一条SQL也须要显示显示事务了;性能
Connection conn = openConnection(); try { // 关闭自动提交: conn.setAutoCommit(false); // 执行多条SQL语句: insert(); update(); delete(); // 提交事务: conn.commit(); } catch (SQLException e) { // 回滚事务: conn.rollback(); } finally { conn.setAutoCommit(true); conn.close(); }
Mysql隔离级别分为4种:Read Uncommitted(读取未提交的)、Read Committed(读取提交的)、Repeatable Red(可重复读)、Serializaable(串行化)spa
Isolation Level | 脏读(Dirty Read) | 不可重复读(Non Repeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
Read Uncommitted | Yes | Yes | Yes |
Read Committed | - | Yes | Yes |
Repeatable Read | - | - | Yes |
Serializable | - | - | - |
Read Uncommitted是隔离级别最低的一种事务级别。在这种隔离级别下,一个事务会读到另外一个事务更新后但未提交的数据,若是另外一个事务回滚,那么当前事务读到的数据就是脏数据,这就是脏读(Dirty Read)。code
mysql> select * from students; +----+-------+ | id | name | +----+-------+ | 1 | Alice | +----+-------+ 1 row in set (0.00 sec)
而后,分别开启两个MySQL客户端链接,按顺序依次执行事务A和事务B:事务
当事务A执行完第3步时,它更新了id=1
的记录,但并未提交,而事务B在第4步读取到的数据就是未提交的数据。ci
随后,事务A在第5步进行了回滚,事务B再次读取id=1
的记录,发现和上一次读取到的数据不一致,这就是脏读。
可见,在Read Uncommitted隔离级别下,一个事务可能读取到另外一个事务更新但未提交的数据,这个数据有多是脏数据。
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; |
2 | BEGIN; | BEGIN; |
3 | UPDATE students SET name = 'Bob' WHERE id = 1; | |
4 | SELECT * FROM students WHERE id = 1; | |
5 | ROLLBACK; | |
6 | SELECT * FROM students WHERE id = 1; | |
7 | COMMIT; |
在Read Committed隔离级别下,一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。
不可重复读是指,在一个事务内,屡次读同一数据,在这个事务尚未结束时,若是另外一个事务刚好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
咱们仍然先准备好students
表的数据:
mysql> select * from students; +----+-------+ | id | name | +----+-------+ | 1 | Alice | +----+-------+ 1 row in set (0.00 sec)
而后,分别开启两个MySQL客户端链接,按顺序依次执行事务A和事务B:
当事务B第一次执行第3步的查询时,获得的结果是Alice
,随后,因为事务A在第4步更新了这条记录并提交,因此,事务B在第6步再次执行一样的查询时,获得的结果就变成了Bob
,所以,在Read Committed隔离级别下,事务不可重复读同一条记录,由于极可能读到的结果不一致。
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; |
2 | BEGIN; | BEGIN; |
3 | SELECT * FROM students WHERE id = 1; | |
4 | UPDATE students SET name = 'Bob' WHERE id = 1; | |
5 | COMMIT; | |
6 | SELECT * FROM students WHERE id = 1; | |
7 | COMMIT; |
在Repeatable Read隔离级别下,一个事务可能会遇到幻读(Phantom Read)的问题。
幻读是指,在一个事务中,第一次查询某条记录,发现没有,可是,当试图更新这条不存在的记录时,居然能成功,而且,再次读取同一条记录,它就神奇地出现了。
咱们仍然先准备好students
表的数据:
mysql> select * from students; +----+-------+ | id | name | +----+-------+ | 1 | Alice | +----+-------+ 1 row in set (0.00 sec)
而后,分别开启两个MySQL客户端链接,按顺序依次执行事务A和事务B:
事务B在第3步第一次读取id=99
的记录时,读到的记录为空,说明不存在id=99
的记录。随后,事务A在第4步插入了一条id=99
的记录并提交。事务B在第6步再次读取id=99
的记录时,读到的记录仍然为空,可是,事务B在第7步试图更新这条不存在的记录时,居然成功了,而且,事务B在第8步再次读取id=99
的记录时,记录出现了。
可见,幻读就是没有读到的记录,觉得不存在,但实际上是能够更新成功的,而且,更新成功后,再次读取,就出现了。
时刻 | 事务A | 事务B |
---|---|---|
1 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
2 | BEGIN; | BEGIN; |
3 | SELECT * FROM students WHERE id = 99; | |
4 | INSERT INTO students (id, name) VALUES (99, 'Bob'); | |
5 | COMMIT; | |
6 | SELECT * FROM students WHERE id = 99; | |
7 | UPDATE students SET name = 'Alice' WHERE id = 99; | |
8 | SELECT * FROM students WHERE id = 99; | |
9 | COMMIT; |
Serializable是最严格的隔离级别。在Serializable隔离级别下,全部事务按照次序依次执行,所以,脏读、不可重复读、幻读都不会出现。
虽然Serializable隔离级别下的事务具备最高的安全性,可是,因为事务是串行执行,因此效率会大大降低,应用程序的性能会急剧下降。若是没有特别重要的情景,通常都不会使用Serializable隔离级别。
默认隔离级别
若是没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,若是使用InnoDB,默认的隔离级别是Repeatable Read。
启动事务
在启动类上添加注解 @EnableTransactionManagement ,
在执行事务的方法上面使用 @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)设置隔离界别与事务传播。默认就是REQUIRED;
事务传播
Spring的声明式事务为事务传播定义了几个级别,默认传播级别就是REQUIRED,它的意思是,若是当前没有事务,就建立一个新事务,若是当前有事务,就加入到当前事务中执行。
SUPPORTS
:表示若是有事务,就加入到当前事务,若是没有,那也不开启事务执行。这种传播级别可用于查询方法,由于SELECT语句既能够在事务内执行,也能够不须要事务;
MANDATORY
:表示必需要存在当前事务并加入执行,不然将抛出异常。这种传播级别可用于核心更新逻辑,好比用户余额变动,它老是被其余事务方法调用,不能直接由非事务方法调用;
REQUIRES_NEW
:表示无论当前有没有事务,都必须开启一个新的事务执行。若是当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;
NOT_SUPPORTED
:表示不支持事务,若是当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;
NEVER
:和NOT_SUPPORTED
相比,它不但不支持事务,并且在监测到当前有事务时,会抛出异常拒绝执行;
NESTED
:表示若是当前有事务,则开启一个嵌套级别事务,若是当前没有事务,则开启一个新事务。