mysql事务-----四种隔离级别

什么是事务

事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。

事务的结束有两种,当事务中的所以步骤全部成功执行时,事务提交。如果其中一个步骤失败,将发生回滚操作,撤消撤消之前到事务开始时的所以操作。

事务四种隔离级别

MySQL 有四种隔离级别,我们来看一下这四种隔离级别的基本定义:

  1. Read uncommitted(读未提交,简称:RU): 在该隔离级别,所有事务都可以看到其它未提交的事务的执行结果。可能会出现脏读。
  2. Read Committed(读已提交,简称: RC):一个事务只能看见已经提交事务所做的改变。因为同一事务的其它实例在该实例处理期间可能会有新的 commit,所以可能出现幻读。
  3. Repeatable Read(可重复读,简称:RR):这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。消除了脏读、不可重复读,默认也不会出现幻读。
  4. Serializable(串行):这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。

在MySQL中,实现了这四种隔离级别,分别有可能产生问题如下所示:

 重现四种事务隔离级别

准备一张表

CREATE TABLE `t21` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_c` (`a`)
) ENGINE=InnoDB CHARSET=utf8mb4;
insert into t21(a,b) values (1,1),(2,2);

下面我们开始进行事务隔离级别实验:

Read uncommitted 

# 第一个客户端
mysql> set session transaction isolation level read uncommitted; #修改成RU级别
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;   #查看当前的隔离级别
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from t21;  #查看表中数据
+----+---+---+
| id | a | b |
+----+---+---+
|  1 | 1 | 1 |
|  2 | 2 | 2 |
+----+---+---+
2 rows in set (0.00 sec)

# 第二个客户端
mysql> set session transaction isolation level read uncommitted; #修改成RU级别
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;  #开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> update t21 set a=11 where id=1 ;  #修改数据
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
mysql> select * from t21;
+----+----+---+
| id | a  | b |
+----+----+---+
|  1 | 11 | 1 |
|  2 |  2 | 2 |
+----+----+---+
2 rows in set (0.00 sec)

# 再回到第一个客户端
mysql> select * from t21;
+----+----+---+
| id | a  | b |
+----+----+---+
|  1 | 11 | 1 |
|  2 |  2 | 2 |
+----+----+---+
2 rows in set (0.00 sec)

 

从上面例子中可以看到第二个客户端已经进行修改但是没有进行事务提交,但是第一个就能查询出来修改的数据,出现脏读的现象

Read Committed

# 第一个客户端
mysql> set session transaction isolation level read committed;  #修改级别读已提交
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;  # 查询级别
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> start transaction;  # 开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t21;  # 查询表
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
+----+---+----+
4 rows in set (0.00 sec)

# 第二个客户端
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
+----+---+----+
4 rows in set (0.00 sec)

mysql> insert t21(a,b) values(5,5);  #新增一条数据
Query OK, 1 row affected (0.00 sec)

mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
+----+---+----+
5 rows in set (0.00 sec)

# 回到第一个客户端
# 查询发现数据没有多
mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
+----+---+----+
4 rows in set (0.00 sec)

# 回到第二个客户端
# 提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

# 再回到第一个客户端
# 发现数据多了一条
mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
+----+---+----+
5 rows in set (0.00 sec)


# 第二个客户端 写入了新数据未提交的情况下,第一个客户端 无法查看到新记录,
# 等到 第二个客户端 提交之后,第一个客户端 才能看到第二个客户端 写入的数据。

我们可以得出结论,已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,即第一个客户端在两次查询的数据不一致,因为在两次查询之间第二个客户端更新了一条数据。已提交读只允许读取已提交的记录,但不要求可重复读。

repeatable read

# 第一个客户端
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
+----+---+----+
5 rows in set (0.00 sec)

# 第二个客户端
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert t21(a,b) values(7,7);
Query OK, 1 row affected (0.00 sec)

# 来到第一个客户端
# 发现数据没有改变
mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
+----+---+----+
5 rows in set (0.00 sec)

# 来到第二个客户端
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

# 来到第一个客户端

mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
|  7 | 7 |  7 |
+----+---+----+
6 rows in set (0.00 sec)

# 由以上的实验可以得出结论,可重复读隔离级别只允许读取已提交记录,
# 而且在一个事务两次读取一个记录期间,其他事务不会更新该记录。但该事务不要求与其他事务可串行化。
# 当一个事务可以找到由一个已提交事务更新的记录,但是可能产生幻读问题

Serializable

# 来到第一个客户端
mysql> set session transaction isolation level Serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
|  7 | 7 |  7 |
+----+---+----+
6 rows in set (0.00 sec)

# 来到第二个客户端

mysql> set session transaction isolation level Serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert t21(a,b) values(5,5);
# 一直阻塞在这里

# 第一个客户端
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
|  7 | 7 |  7 |
+----+---+----+
6 rows in set (0.00 sec)

# 第二个客户端
mysql> insert t21(a,b) values(5,5);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t21;
+----+---+----+
| id | a | b  |
+----+---+----+
|  1 | 1 |  1 |
|  2 | 2 |  2 |
|  3 | 3 | 33 |
|  4 | 4 |  4 |
|  6 | 5 |  5 |
|  7 | 7 |  7 |
|  8 | 5 |  5 |
+----+---+----+
7 rows in set (0.00 sec)

# serializable完全锁定字段,若一个事务来查询同一份数据就必须等待,
# 直到前一个事务完成并解除锁定为止。是完整的隔离级别,会锁定对应的数据表格,因而会有效率的问题。

通过生活中的例子认识事务隔离级别

Read uncommitted 的例子

拿零售业务场景来讲,在事务隔离级别 RU 下:比如顾客 A 在超市买单时,当收银员扫完顾客 A 的支付码后,因为网络原因,一直等待着(也就是整个支付过程的事务还没结束);这时收银员去后台数据查询,看到 A 的钱已经进入超市账户了,然后让顾客 A 离开。过了一会,整个支付过程回滚了,才发现 A 实际是支付失败。这样超市岂不是很亏。这就是 RU 隔离级别可能导致脏读的情况。

Read Committed 的例子

在 RC 隔离级别下:比如顾客 A 在超市购买了 90 元的东西,当收银系统查询到顾客 A 还剩 100 元,足够扣款,此时 A 的老婆在家网购,花掉了 A 账户里的这 100 块,这时收银系统在扣除 A 账户 90 元这一步操作时,就会出现报错的情况。这时顾客 A 肯定郁闷,不是明明钱够么?这就是 RC 隔离级别下的幻读现象。

Repeatable Read 的例子

还是拿上面的例子,顾客 A 在超市购买了 90 元的东西,当收银系统查询到顾客 A 还剩 100 元,足够扣款,此时 A 的老婆在家网购,能查询到 A 的账户里还有 100 元,但是想要用 A 账户里的 100 块,却发现并不能使用这 100 元。这样,A 最后的扣款步骤也能正常完成,最终顺利完成了整个付款过程。这就是可重复读的现象。

Serializable 的例子

顾客 A 在超市购买了 90 元的东西,当收银系统查询到顾客 A 还剩 100 元,足够扣款,此时 A 的老婆在家网购,想查询 A 账户里还有多少钱,却发现无法查看到,必须要等到 A 整个付款完成,其老婆才能去查询余额。这就是串行导致的。

如何选择合适的事务隔离级别

对于 RU 隔离级别,会导致脏读,从性能上看,也不会比其它隔离级别好太多,因此生产环境不建议使用。

对于 RC 隔离级别,相比 RU 隔离级别,不会出现脏读;但是会出现幻读,一个事务中的两次执行同样的查询,可能得到不一样的结果。

对于 RR 隔离级别,相比 RC 隔离级别,不会出现幻读(这个在第 17 节详细讲了,RR 隔离级别通过间隙锁解决了幻读),但是相对于 RC,锁的范围可能更大了。

对于 Serializable 隔离级别,因为它强制事务串行执行,会在读取的每一行数据上都加锁,因此可能会导致大量的超时和锁争用的问题。生成环境很少使用。

因此总的来说,建议在 RC 和 RR 两个隔离级别中选一种,如果能接受幻读,需要并发高点,就可以配置成 RC,如果不能接受幻读的情况,就设置成 RR 隔离级别。

愚人的蠢事算不得稀奇,聪明人的蠢事才叫人笑痛肚皮;因为他用全副的本领,证明他自己愚笨。 -----莎士比亚

猜你喜欢

转载自blog.csdn.net/lin_keys/article/details/108894668