事务的4种隔离级别

数据库中事务的四大特性(ACID)。

  1. 原子性(Atomicity)
    原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。

  2. 一致性(Consistency)
    一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

  3. 隔离性(Isolation)
    隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
    即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
    关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
    例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

SQL规范定制了四种隔离级别。

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

  • READ COMMITTED
    (1)这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)
    (2)它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变
    (3)这种隔离级别出现的问题是——不可重复读(Nonrepeatable Read):事务交叉时读到别的事务已提交的数据。

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

既然事务A不会读到事务B提交的数据。
那么问题来了,如果A、B两个事务同时更新同一个数据,会出问题么?
mysql让后一个事务等待前一个事务提交后在前一个事务更新的基础上再处理。

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

下面通过流程图看下不同隔离级别下会出现的情况:

隔离级别 脏读 不可重复读 幻读
Read Uncommitted
Read Committed ×
Repeatable Read × ×
Serializable × × ×
  1. 脏读(Dirty Read)
    事务B读到事务A修改过的数据,这个数据还没有提交,后续A可能会回滚事务,导致数据一致性遭到破坏。即数据“变脏”。

    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
    ┌─────────────┐                   ┌─────────────┐
    user A user B
    └─────────────┘ └─────────────┘
    | ┌─────────┐ | ┌─────────┐
    | ──┤money=10 | ──┤money=10
    | └─────────┘ | └─────────┘
    | |
    | ┌───────────────────┐ |
    | ──┤start transaction; │ |
    | └───────────────────┘ | ┌───────────────────┐
    | | ──┤start transaction; │
    | | └───────────────────┘
    | ┌──────────────────┐ |
    | ──┤update money=100 │ |
    | └──────────────────┘ |
    | | ┌──────────────────────────┐
    | |──┤select money from account;│
    | | │money=100; │
    | | └──────────────────────────┘
    | ┌───────────┐ |
    | ──┤rollback; │ |
    | └───────────┘ |
    | | ┌──────────────────────────┐
    | |──┤select money from account;│
    | | │money=10; │
    | | └──────────────────────────┘
    | | ┌───────────┐
    | |──┤rollback; │
    | | └───────────┘
  2. 不可重复读(Nonrepeatable Read)
    事务B读到事务A已提交的数据。同一个事务内读到不同的结果。

    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
    ┌─────────────┐                   ┌─────────────┐
    user A user B
    └─────────────┘ └─────────────┘
    | ┌─────────┐ | ┌─────────┐
    | ──┤money=10 | ──┤money=10
    | └─────────┘ | └─────────┘
    | |
    | ┌───────────────────┐ |
    | ──┤start transaction; │ |
    | └───────────────────┘ | ┌───────────────────┐
    | | ──┤start transaction; │
    | | └───────────────────┘
    | ┌──────────────────┐ |
    | ──┤update money=100 │ |
    | └──────────────────┘ |
    | | ┌──────────────────────────┐
    | |──┤select money from account;│
    | | │money=10; │
    | | └──────────────────────────┘
    | ┌───────────┐ |
    | ──┤commit; │ |
    | └───────────┘ |
    | | ┌──────────────────────────┐
    | |──┤select money from account;│
    | | │money=100; │
    | | └──────────────────────────┘
    | | ┌───────────┐
    | |──┤rollback; │
    | | └───────────┘
  3. 幻读
    事务B读到事务A增加或删除的记录。

    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
    ┌─────────────┐                                       ┌─────────────┐
    user A user B
    └─────────────┘ └─────────────┘
    | ┌───────────┐ | ┌───────────┐
    | ──┤count(*)=1 | ──┤count(*)=1
    | └───────────┘ | └───────────┘
    | |
    | ┌───────────────────┐ |
    | ──┤start transaction; │ |
    | └───────────────────┘ | ┌───────────────────┐
    | | ──┤start transaction; │
    | | └───────────────────┘
    | ┌───────────────────────────┐ |
    | ──┤select count(*) from user; │ |
    | │ 1 │ |
    | └───────────────────────────┘ |
    | | ┌───────────────────────────┐
    | | ──┤select count(*) from user; │
    | | │ 1 │
    | | └───────────────────────────┘
    | ┌───────────────────────────────────────┐ |
    | ──┤insert into user (name) values ('tom') │ |
    | └───────────────────────────────────────┘ |
    | ┌───────────────────────────┐ |
    | ──┤select count(*) from user; │ |
    | │ 2 │ |
    | └───────────────────────────┘ |
    | ┌───────────┐ |
    | ──┤commit; │ |
    | └───────────┘ |
    | | ┌──────────────────────────┐
    | |──┤select count(*) from user;│
    | | │ 2 │
    | | └──────────────────────────┘
    | | ┌───────────┐
    | |──┤rollback; │
    | | └───────────┘

JDK里面也定义了事务隔离级别:

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
package java.sql;
public interface Connection extends Wrapper, AutoCloseable {
int TRANSACTION_NONE = 0;
/**
* 表示可能发生脏读,不可重复读和幻读的常量。 这个级别允许一个事务改变的行被另一个事务读取,然后该行的任何改变被提交(“脏读”)。
* 如果任何更改回滚,则第二个事务将检索到无效行。
*/
int TRANSACTION_READ_UNCOMMITTED = 1;
/**
* 表示可以防止脏读的常量。 不可重复读取和幻像读取可能发生。
* 该级别仅禁止事务读取其中未提交更改的行。
*/
int TRANSACTION_READ_COMMITTED = 2;
/**
* 一个常量,指示防止脏读和不可重复读取; 幻像读取可能会发生。
* 该级别禁止事务读取其中有未提交更改的行,并且还禁止一个事务读取一行,第二个事务更改该行并且第一个事务重新读取行,
* 第二次获取不同值的情况 一个“不可重复读”)。
*/
int TRANSACTION_REPEATABLE_READ = 4;
/**
* 一个常量表示可以防止脏读,不可重复读取和幻读。
* 该级别包括TRANSACTION_REPEATABLE_READ中的禁止,并进一步禁止一个事务读取满足WHERE条件的所有行的情况,第二个事务插入满足该WHERE条件的行,
* 并且第一个事务重新读取相同条件的情况,检索额外的“幻影”排在第二次阅读。
*/
int TRANSACTION_SERIALIZABLE = 8;

...
}

设置隔离级别要在开启事务之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
Connection conn = null;
Statement state = null;
ResultSet ret = null
try{
//获取jdbc连接
conn = JDBCUtils.getConnection()
//设置隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
//开启事务
conn.setAutoCommit(false);
//执行业务逻辑
exeBusiness();
}

参考:
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
https://blog.csdn.net/qq_33290787/article/details/51924963
https://www.cnblogs.com/snsdzjlz320/p/5761387.html


事务的4种隔离级别
https://www.wekri.com/spring/transaction-isolation-level/
Author
Echo
Posted on
May 23, 2018
Licensed under