[MySQL]事务的四个隔离级别详解

前言

MySQL标准定义了4种隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。今天想把这块整理出来,尽量用最简洁的语言描述出来,供新人参考。

注:本文可以与上篇文章一同参考
Spring的七个事务传播行为


事务的基本要素(ACID)

  1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

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

   3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

   4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

事务的并发问题

  1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

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

MySQL事务隔离级别

四种事务隔离级别按等级从低到高分别为:

  • Read Uncommitted(读取未提交内容);
  • Read Committed(读取提交内容);
  • Repeatable Read(可重读);
  • Serializable(可串行化)。

修改事务隔离级别

set [ global | session ] transaction isolation level 
Read uncommitted | Read committed | Repeatable | Serializable;
例:
set global transaction isolation level read-uncommitted;

注意:

1、如果选择global,意思是此语句将应用于之后的所有session,而当前已经存在的session不受影响。
2、如果选择session,意思是此语句将应用于当前session内之后的所有事务。
3、如果什么都不写,意思是此语句将应用于当前session内的下一个还未开始的事务。

以下是查询当前事务隔离级别

select @@global.tx_isolation;
select @@session.tx_isolation;

准备工作

创建user表

mysql> select * from user;
+----+------+------+-------+
| id | name | pass | birth |
+----+------+------+-------+
| 54 | test | 1    | NULL  |
+----+------+------+-------+

以下四种事务隔离级别都用这个user表进行演示说明。


1、Read Uncommitted(读取未提交内容)

  在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。通俗地讲就是,在一个事务中可以读取到另一个事务中新增或修改但未提交的数据。因为另一个事务可能回滚,所以在第一个事务中读取到的数据很可能是无效的脏数据,造成脏读现象。读取未提交的数据,也被称为脏读。

例如:

修改事务隔离级别

mysql> set session transaction isolation level read uncommitted;

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+

分别开始两个事务A、B,做如下操作:

事务A事务B
mysql> delete from user;
Query OK, 1 row affected

mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
Empty set
mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
Empty set
mysql> insert user (name,pass) value (“test”,”1”);
Query OK, 1 row affected

mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 55 | test | 1 | NULL |
+—-+——+——+——-+
mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 55 | test | 1 | NULL |
+—-+——+——+——-+
1 row in set
mysql> rollback;
Query OK, 0 rows affected

mysql> select * from user;
Empty set
mysql> select * from user;
Empty set

经过上面的实例可以看出,事务B插入了一条数据但是没有提交,此时事务A可以查询到那条未提交的数据,但一旦事务B回滚后,事务A就查询不到那条数据。事务A在事务B提交之前查询到的那条数据是无效的,即脏数据,因为没法确保事务B最后是否会成功提交或者成功提交之前是否还会对该数据进行修改,依据脏数据所做的操作可能是不正确的,极有可能造成脏读现象。

2、Read Committed(读取提交内容)

  这是大多数数据库系统的默认隔离级别(但不是mysql默认的),它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。

例如:

修改事务隔离级别

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-COMMITTED         |
+------------------------+

分别开始两个事务A、B,做如下操作:

事务A事务B
mysql> delete from user;
Query OK, 0 rows affected

mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
Empty set
mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
Empty set
mysql> insert user (name,pass) value (“test”,”1”);
Query OK, 1 row affected

mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 59 | test | 1 | NULL |
+—-+——+——+——-+
mysql> select * from user;
Empty set
mysql> commit;
Query OK, 0 rows affected
mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 59 | test | 1 | NULL |
+—-+——+——+——-+

从实例可以看到,事务A在事务B插入数据但未提交时,查询不到数据,当直到事务B提交事务之后,事务A才可以查询到新增的数据。

经过上面的实例可以得出结论,已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,即事务A在两次查询的数据不一致,因为在两次查询之间事务B更新了一条数据。

3、Repeatable Read(可重读)

  这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。通俗来讲,可重复读在一个事务里读取数据,怎么读都不会变,除非提交了该事务,再次进行读取。

例如:

修改事务隔离级别

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ        |
+------------------------+

分别开始两个事务A、B,做如下操作:

事务A事务B
mysql> delete from user;
Query OK, 4 rows affected

mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
Empty set
mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
Empty set
mysql> insert user (name,pass) value (“test”,”1”);
Query OK, 1 row affected

mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 64 | test | 1 | NULL |
+—-+——+——+——-+
mysql> select * from user;
Empty set
mysql> commit;
Query OK, 0 rows affected
mysql> select * from user;
Empty set
mysql> commit;
Query OK, 0 rows affected

mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 64 | test | 1 | NULL |
+—-+——+——+——-+

从实例可以看到,事务A在事务B插入数据但未提交时,查询不到数据,当直到事务B提交事务之后,事务A才可以查询到新增的数据。

经过上面的实例可以得出结论,已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,即事务A在两次查询的数据不一致,因为在两次查询之间事务B更新了一条数据。

4、Serializable(可串行化)

  这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。通俗地讲就是,假如两个事务都操作到同一数据行,那么这个数据行就会被锁定,只允许先读取/操作到数据行的事务优先操作,只有当事务提交了,数据行才会解锁,后一个事务才能成功操作这个数据行,否则只能一直等待。就像java中的锁,写数据必须等待另一个事务结束。也正因为这样,可能导致大量的超时现象和锁竞争。

例如:

修改事务隔离级别

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| SERIALIZABLE           |
+------------------------+

分别开始两个事务A、B,做如下操作:

事务A事务B
mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 64 | test | 1 | NULL |
+—-+——+——+——-+
mysql> start transaction;
Query OK, 0 rows affected

mysql> select * from user;
+—-+——+——+——-+
| id | name | pass | birth |
+—-+——+——+——-+
| 64 | test | 1 | NULL |
+—-+——+——+——-+
mysql> delete from user;
1205 - Lock wait timeout exceeded; try restarting transaction
mysql> commit;
Query OK, 0 rows affected

从以上两个实例可以看到,事务B对user表的新增操作需要等事务A提交之后才可以操作成功,若事务A一直不提交,在等待一定时间之后事务B就会超时报错。从实例中可以看出serializable完全锁定字段,若后一个事务来查询同一份数据就必须等待,直到前一个事务完成并解除锁定为止,是完整的隔离级别,会锁定对应的数据表,因而会有效率的问题。

PS:实践发现,事务A查询user数据但未提交时,事务B增删改时需要等待,但查询数据不需要等待;事务A增删改数据但未提交时,事务B增删查改都需要等待(当然前提是有操作到同一行数据)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值