事务基础小结
事务的四大特性分别是:原子性、一致性、隔离性、持久性
原子性:不可分割,事务由多个原子组成,这些原子要么全成功,要么全失败。
一致性:事务在执行前与执行后数据保持一致。
隔离性:事务与事务之间不可以互相影响。
持久性:事务一旦commit,代表数据真正修改了,不可以再改变。
事务的隔离级别
1. 脏读 一个事务读取到了另一个事务未提交数据
2. 不可重复读 两次读取数据不一致 (读提交数据)
3. 虚读(幻读) 两次读取数据不一致
1.脏读 :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
2. 不可重复读 :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
3. 幻读 : 是一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
在开发中,要对事务隔离级别产生的问题解决,需要设置事务隔离级别。
数据库中的事务隔离级别有四种,这四种分别解决了以上三个问题. 以MYSQL数据库来分析四种隔离级别
第一种隔离级别:Read uncommitted(读未提交)
如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据
解决了更新丢失,但还是可能会出现脏读
第二种隔离级别:Read committed(读提交)
如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
解决了更新丢失和脏读问题
第三种隔离级别:Repeatable read(可重复读取)
可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。
解决了更新丢失、脏读、不可重复读、但是还会出现幻读
第四种隔离级别:Serializable(可序化)
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读
解决了更新丢失、脏读、不可重复读、幻读(虚读)
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低,像Serializeble这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况来,在MYSQL数据库中默认的隔离级别是Repeatable read(可重复读)。
在MYSQL数据库中,支持上面四种隔离级别,默认的为Repeatable read(可重复读);而在Oracle数据库中,只支持Serializeble(串行化)级别和Read committed(读已提交)这两种级别,其中默认的为Read committed级别
在MYSQL数据库中查看当前事务的隔离级别
SELECT @@tx_isolation;
在MYSQL数据库中设置事务的隔离级别:
记住:设置数据库的隔离级别一定要是在开启事务之前:
如果是使用JDBC对数据库的事务设置隔离级别的话,也应该是在调用Connecton对象的setAutoCommit(false)方法之前,调用Connection对象的setTransactionIsolation(level)即可设置当前连接的隔离级别,至于参数level,可以使用Connection对象的字段:
在JDBC中设置隔离级别的部分代码:
隔离级别的设置只对当前连接有效,对于使用MYSQL命令窗口而言,一个窗口就相当于一个连接,当前窗口设置的隔离级别只对当前窗口中的事务有效,对于JDBC操作数据库来说,一个Connection对象相当与一个连接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他连接Connection对象无关
事务传播行为
事务传播行为用于解决两个被事务管理的方法互相调用问题。实际开发中将事务在service控制,如下方法调用存在传播行为:
针对上边的问题:
如果serviceB也会产生一个代理对象,同时也会进行事务管理增强是,执行serviceA和serviceB分别开启事务,上边的serviceA中funA方法内容不处于一个事务中了!!!
解决方案:
设置serviceA和serviceB传播行为为REQURIED.
**测试**
1、两种方法都不加事务
1 @Override 2 public void InsertUsers(Users users) 3 @Override 4 public void InsertCuser(Cuser cuser)
这个是意料之中,不加事务注解当然不会开启事务
2、InsertUser 加上required,InsertCuser 不加事务
1 @Transactional(propagation= Propagation.REQUIRED) 2 @Override 3 public void InsertUsers(Users users) 4 5 @Override 6 public void InsertCuser(Cuser cuser)
![]()
InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;想象一下,调用关系 A-->B-->C--->D-->.... 若只有A上加了required事务注解,那么BCD...都会纳入A的事务中
3、InsertUser不加事务,InsertCuser 加上 required
1 @Override 2 public void InsertUsers(Users users) 3 4 @Transactional(propagation= Propagation.REQUIRED) 5 @Override 6 public void InsertCuser(Cuser cuser)
InsertUser没有使用事务,InsertCuser 使用自己的事务
4、InsertUser 和 InsertCuser 都加上 required
1 @Transactional(propagation= Propagation.REQUIRED) 2 @Override 3 public void InsertUsers(Users users) 4 5 @Transactional(propagation= Propagation.REQUIRED) 6 @Override 7 public void InsertCuser(Cuser cuser)
![]()
InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;
5、InsertUser 使用 required,InsertCuser 使用 supports
InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;
6、InsertUser 使用 supports ,InsertCuser 使用 required
InsertUser 没有创建事务,InsertCuser 使用自己的事务
7、InsertUser 使用required,InsertCuser 使用 requires_new
InsertUser和 InsertCuser 各自创建了事务,互不影响
8、InsertUser 使用requires_new,InsertCuser 使用required
InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;
9、InsertUser 使用 required,InsertCuser 使用 mandatory
InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;
10、InsertUser 使用 mandatory ,InsertCuser 使用 requires
报异常
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
11、InsertUser 使用 requires,InsertCuser 使用 not_supported
InsertUser事务先被挂起,等到InsertCuser 非事务执行完毕后再继续执行
12、InsertUser 使用not_supported ,InsertCuser 使用 requires
InsertUser 不会创建事务,InsertCuser 使用自己的事务
13、InsertUser 使用 requires,InsertCuser 使用 never
报异常
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
14、InsertUser 使用 never,InsertCuser 使用 requires
InsertUser 不使用事务,InsertCuser 使用自己的事务
15、InsertUser 使用 requires,InsertCuser 使用 nested
InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务
16、InsertUser 使用 nested,InsertCuser 使用 requires
InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务
1.1.1 大总结
实验 |
InsertUser |
InsertCuser |
结果 |
1 |
不加 |
不加 |
两者都以无事务状态执行 |
2 |
required |
不加 |
InsertUser 将它的事务传播给了InsertCuser |
3 |
不加 |
required |
InsertUser 无事务,InsertCuser 创建了事务 |
4 |
required |
required |
InsertUser 将事务传播给了InsertCuser |
5 |
requires |
supports |
InsertUser 把事务传播给了InsertCuser |
6 |
supports |
requires |
InsertUser无事务,InsertCuser 创建了事务 |
7 |
requires |
mandatory |
InsertUser将事务传播给了InsertCuser |
8 |
mandatory |
requires |
InsertUser报异常IllegalTransactionStateException |
9 |
requires_new |
required |
InsertUser 把事务传播给了InsertCuser |
10 |
required |
requires_new |
两者使用各自的事务,互不影响 |
11 |
requires |
not_supported |
InsertUser事务先被挂起,等到InsertCuser 非事务执行完毕后再继续执行 |
12 |
not_supported |
requires |
InsertUser 不会创建事务,InsertCuser 使用自己的事务 |
13 |
requires |
nerver |
InsertCuser 报异常IllegalTransactionStateException |
14 |
nerver |
requires |
InsertUser 无事务,InsertCuser 创建了事务 |
15 |
requires |
nested |
InsertUser 把事务传播给了InsertCuser |
16 |
nested |
requires |
InsertUser 把事务传播给了InsertCuser |
spring提供了传播行为:
注意:
required :可以将insert、update、delete这些service设置为required。
support : 可以将findList查询方法设置为support
对于查询事务,在事务定义 将isReadOnly设置true 性能更高。
PROPAGATION_REQUIRED及其他6种事务传播行为种类,有需要的朋友可以参考下。
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:
表1事务传播行为类型
事务传播行为类型 |
说明 |
PROPAGATION_REQUIRED |
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS |
支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY |
使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW |
新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED |
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER |
以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED |
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。