数据库事务管理简介

事务管理简介

事务(Transaction ) 通俗的讲就是一件事件,要么做完,要么不做,不要做一半留一半。也就是说,事务必须是一个不可分割的整体。

1. 事务特性

1.1 原子性(Atomicity)

事务必须是一个不可分割的整体,要么全部执行,要么不执行。

1.2. 一致性(Consistency)

事务开始前和结束后,数据库的完整性约束没有被破坏 。比如 A 向 B 转账,不可能 A 扣了钱,B 却没收到。

1.3. 隔离性(Isolation)

同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任务干扰。

1.4. 持久性(Durability)

事务完成后,事务对数据库的所有更改将被保存到数据库中。

2. 事务隔离级别

数据库定义了4个隔离的级别:

  • READ_UNCOMMITTED 读未提交
  • READ_COMMITTED 读提交
  • REPEATABLE_READ 重复读
  • SERIALIZABLE 序列化

从上往下,级别越来越高,并发性越来越差,一致性越来越高。

3. 事务面临的问题

数据库在高并发下会产生下列问题:

  • Dirty Read 脏读
  • Unrepeatable Read 不可重复读
  • Phantom Read 幻读

3.1 脏读

脏读可以理解为读到 “垃圾数据”了。比如有2个事务同时对数据库进行操作,会发生竞争,如下表所示:

时间 事务A(存款) 事务B(取款)
T1 开始事务
T2 开始事务
T3 查询余额(1000元)
T4 取出1000(余额0元)
T5 查询余额(0元)
T6 撤销事务(余额恢复为1000元)
T7 存入500元(余额500元)
T8 提交事务

余额应该为1500元,但是T5时间点,事务A查询余额为0元,这个数据就是脏数据,它是事务B造成的。所以脏数据是非常要不得的。

3.2 不可重复读

时间 事务A(存款) 事务B(取款)
T1 开始事务
T2 开始事务
T3 查询余额(1000元)
T4 查询余额(1000元)
T5 取出1000元(余额0元)
T6 提交事务
T7 查询余额(0元)

从上述表格中看到,事务A除了查询操作2次以外,其他什么事情都没有做,结果钱就从1000变为0了,这就是重复读。

3.3 幻读

时间 事务A(统计) 事务B(存款)
T1 开始事务
T2 开始事务
T3 统计总存款(1000元)
T4 存入100元
T5 提交事务
T6 统计总存款(10100元)

事务A每次统计的结果都是不一样的,就好像产生了幻觉一样。

小结:

不可重复读和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需要锁住满足条件的行,解决幻读需要锁表。

不同的隔离级别解决并发问题:

事务隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED 允许 允许 允许
READ_COMMITTED 禁止 允许 允许
REPEATABLE_READ 禁止 禁止 允许
SERIALIZABLE 禁止 禁止 禁止

MySQL 默认的隔离级别是:REPEATABLE_READ(重复读)

4. Spring 的事务传播行为

前提条件:

  • 方法A有事务,方法B也有事务
  • 方法A有事务,方法B没有事务
  • 方法A没有事务,方法B有事务
  • 方法A没有事务,方法B也没有事务

如上图所示:FooService 的业务方法的传播行为被我们指定为 Required,表示如果当前存在事务的话,则加入当前事务。

  • FoobarService 在调用 FooService 业务方法的时候已经启动了一个事务,所以,FooService 的业务方法会直接加入 FooBarService启动的事务1中。

  • BarService的业务方法的传播行为被指定为RequiredNew,表示无论当前是否存在事务,都需要为其重新启动一个事务,所以它使用的是自己启动的事务2.

Spring 提供了7种事务传播行为:

  1. PROPAGATION_REQUIRED(默认)

    如果没有,就新建一个事务;如果有,就加入当前事务。总之,要至少保证在一个事务中运行。

  2. PROPAGATION_REQUIRES_NEW

    如果没有,就新建一个事务;如果有,就将当前事务挂起。意思就是创建一个事务,和原来的事务没有关系了。场景:如果某个业务对象所做的事情不想影响到外层事务,REQUIRES_NEW是合适的选择。

  3. PROPAGATION_NESTED

    如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。所嵌套的子事务和主事务之间是有关联的(当主事务提交或回滚,子事务也会提交或回滚)。

  4. PROPAGATION_SUPPORTS

    如果没有,就以非事务方式执行;如果有,就使用当前事务。

    对于一些查询方法来说,SUPPORTS 通常是比较合适的传播行为。如果当前方法直接执行,那么不需要事务的支持;如果当前方法被其他方法调用,而其他方法启动了一个事务,使用 SUPPORTS 可以保证当前方法能够加入当前事务,并且洞察当前事务对数据资源所做的更新。

  5. PROPAGATION_NOT_SUPPORTS

    如果没有,就以非事务方式执行;如果有,就将当前事务挂起(但也要看对应的事务管理器实现类是否支持事务的挂起)。

  6. PROPAGATION_NEVER

    如果没有,就以非事务方式执行;如果有,就抛出异常。

  7. PROPAGATION_MANDATORY

    如果没有,就抛出异常;如果有,就使用当前事务。

Spring 提供了一些小的附加功能:

  • 事务超时(Transaction Timeout) 为了解决事务时间太长,消耗太多资源的问题,所以故意给事务设置一个最大时长,如果超时了,就回滚事务。
  • 只读事务(Readonly Transaction) 为了忽略哪些不需要事务的方法,比如读取数据,就样可以有效地提高一些性能。

5. 总结