数据库事务4种隔离级别、7种传播行为和@Transactional注解实例讲解

前言

数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

1、原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。

2、一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。

3、隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。

4、持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。

事务的ACID特性是由关系数据库系统(DBMS)来实现的,DBMS采用日志来保证事务的原子性、一致性和持久性。日志记录了事务对数据库所作的更新,如果某个事务在执行过程中发生错误,就可以根据日志撤销事务对数据库已做的更新,使得数据库回滚到执行事务前的初始状态。

对于事务的隔离性,DBMS是采用锁机制来实现的。当多个事务同时更新数据库中相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。


提示:以下是本篇文章正文内容,下面案例可供参考

一、隔离级别

数据库事务的隔离级别有4个,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。

隔离级别 脏读 不可重复读 幻读 数据库默认级别

Read uncommitted

读未提交

可能 可能 可能  

Read committed

读已提交

不可能 可能 可能 PostgreSQL、ORACLE、SQL Server和DB2

Repeatable read

可重复读

不可能 不可能 可能 MySQL

Serializable

可串行化

不可能 不可能 不可能  
  1. Read uncommitted 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。(这种隔离级别会产生脏读,不可重复读和幻像读)。
  2. Read committed 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
  3. Repeatable read 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。(它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读))。
  4. Serializable 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行(除了防止脏读,不可重复读外,还避免了幻像读)。

二、Mysql隔离实例

1.第一级别:Read Uncommitted(读未提交)

  1. 所有事务都可以看到其他未提交事务的执行结果
  2. 本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少
  3. 该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据

Read Uncommitted读未提交 如下(示例):

#首先,修改隔离级别
set tx_isolation='READ-UNCOMMITTED';
select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+

#事务A:启动一个事务
start transaction;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务B:也启动一个事务(那么两个事务交叉了)
       在事务B中执行更新语句,且不提交
start transaction;
update tx set num=10 where id=1;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务A:那么这时候事务A能看到这个更新了的数据吗?
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |   --->可以看到!说明我们读到了事务B还没有提交的数据
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务B:事务B回滚,仍然未提交
rollback;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务A:在事务A里面看到的也是B没有提交的数据
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |      --->脏读意味着我在这个事务中(A中),事务B虽然没有提交,但它任何一条数据变化,我都可以看到!
|    2 |    2 |
|    3 |    3 |
+------+------+

 

2.第二级别:Read Committed(读已提交)

  1. 它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变
  2. 这种隔离级别出现的问题是——不可重复读(Nonrepeatable Read):不可重复读意味着我们在同一个事务中执行完全相同的select语句时可能看到不一样的结果。
    1.  |——> 导致这种情况的原因可能有
      1. 有一个交叉的事务有新的commit,导致了数据的改变;
      2. 一个数据库被多个实例操作时,同一事务的其他实例在该实例处理其间可能会有新的commit

Read Committed读已提交 如下(示例):

#首先修改隔离级别
set tx_isolation='read-committed';
select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+

#事务A:启动一个事务
start transaction;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务B:也启动一个事务(那么两个事务交叉了)
       在这事务中更新数据,且未提交
start transaction;
update tx set num=10 where id=1;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务A:这个时候我们在事务A中能看到数据的变化吗?
select * from tx; --------------->
+------+------+                |
| id   | num  |                |
+------+------+                |
|    1 |    1 |--->并不能看到! |
|    2 |    2 |                |
|    3 |    3 |                |
+------+------+                |——>相同的select语句,结果却不一样
                                  |
#事务B:如果提交了事务B呢?          |
commit;                           |
                                  |
#事务A:                            |
select * from tx; --------------->
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |--->因为事务B已经提交了,所以在A中我们看到了数据变化
|    2 |    2 |
|    3 |    3 |
+------+------+

 

3.第三级别:Repeatable Read(可重读)

  1. 它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行
  2. 此级别可能出现的问题——幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行
  3. InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题

Repeatable Read可重读 如下(示例):

#首先,更改隔离级别
set tx_isolation='repeatable-read';
select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

#事务A:启动一个事务
start transaction;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务B:开启一个新事务(那么这两个事务交叉了)
       在事务B中更新数据,并提交
start transaction;
update tx set num=10 where id=1;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+
commit;

#事务A:这时候即使事务B已经提交了,但A能不能看到数据变化?
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 | --->还是看不到的!(这个级别2不一样,也说明级别3解决了不可重复读问题)
|    2 |    2 |
|    3 |    3 |
+------+------+

#事务A:只有当事务A也提交了,它才能够看到数据变化
commit;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+

4.第四级别:Serializable(可串行化)

  1. 这是最高的隔离级别
  2. 它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。
  3. 在这个级别,可能导致大量的超时现象和锁竞争

Serializable可串行化 如下(示例):

#首先修改隔离界别
set tx_isolation='serializable';
select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+

#事务A:开启一个新事务
start transaction;

#事务B:在A没有commit之前,这个交叉事务是不能更改数据的
start transaction;
insert tx values('4','4');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
update tx set num=10 where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

三、7种传播行为

事务传播行为:如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为

1. TransactionDefinition.PROPAGATION_REQUIRED:

如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。

2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:

创建一个新的事务,如果当前存在事务,则把当前事务挂起。

3. TransactionDefinition.PROPAGATION_SUPPORTS:

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:

以非事务方式运行,如果当前存在事务,则把当前事务挂起。

5. TransactionDefinition.PROPAGATION_NEVER:

以非事务方式运行,如果当前存在事务,则抛出异常。

6. TransactionDefinition.PROPAGATION_MANDATORY:

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

7. TransactionDefinition.PROPAGATION_NESTED:

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行; 如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

四、@Transactional注解详解

1、 @Transactional注解参数说明

参数名称

类型

功能描述

value String 可选的限定描述符,指定使用的事务管理器

readOnly

boolean

读写或只读事务,默认读写

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)

rollbackFor

Class对象数组、必须继承自Throwable

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:

指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

rollbackForClassName

类名数组、必须继承自Throwable

该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:

指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException")

指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})

noRollbackFor

Class对象数组,必须继承自Throwable

该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:

指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

noRollbackForClassName

类名数组,必须继承自Throwable

不会导致事务回滚的异常类数组

该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:

指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException")

指定多个异常类名称:

@Transactional(noRollbackForClassName={"RuntimeException","Exception"})

propagation

enum:Propagation

该属性用于设置事务的传播行为,具体取值可参考表6-7。

例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

isolation

enum:Isolation

该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置

timeout

int (in seconds granularity)

该属性用于设置事务的超时秒数,默认值为-1表示永不超时


2、@Transactional 注意事项

  1. 不要再类级别上加事务,否则所有方法都有事务,要在方法上加事务,如果类中绝大多数方法都需要加事务,那请在不需要加事务的方法上面禁止事务
  2. 加事务的方法必须是public
  3. 只有外部直接调用的方法上加事务,才会有效果
  4. 用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

3、@Transactional参数详解

事物传播行为介绍: 

  • @Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
  • @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
  • @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
  • @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
  • @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
  • @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

事物超时设置:

  •   @Transactional(timeout=30) //默认是30秒

事务隔离级别:

  •   @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
  •   @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
  •   @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
  •   @Transactional(isolation = Isolation.SERIALIZABLE):串行化
  • 脏读 : 一个事务读取到另一事务未提交的更新数据
  • 不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
  • 幻读 : 一个事务读到另一个事务已提交的insert数据

数据库事务4种隔离级别、7种传播行为和@Transactional注解实例讲解_第1张图片

数据库事务4种隔离级别、7种传播行为和@Transactional注解实例讲解_第2张图片

你可能感兴趣的:(Springboot,mysql,spring,多线程,数据库)