SQL 事务 2

1. 前言

上一小节中,我们介绍了事务的概念和基本使用,探讨了事务的四大特性,本小节我们将更加深入的学习事务。

在实际的生产环境中,偶尔会遇到大量并发访问的情况;大量的并发会导致数据的竞争,从而引起一系列的并发问题。

本小节,我们将一起学习 SQL 的 4 种事务隔离机制,以及与之对应的 3 种并发异常。

本小节测试数据如下,请先在数据库中执行,本小节的所有操作若无特殊说明默认在 MySQL 中执行。

DROP TABLE IF EXISTS imooc_user;
CREATE TABLE imooc_user
(
  id int PRIMARY KEY,
  username varchar(20),
  age int
);
INSERT INTO imooc_user(id,username,age)
VALUES (1,'peter',18),(2,'pedro',24),(3,'jerry',22);

2. 并发异常

SQL 标准共定义了 3 种并发异常,这三种异常分别是脏读(Dirty Read)、不可重复读(Nnrepeatable Read)和幻读(Phantom Read)。

这 3 种异常比较抽象,我们直接以一个小例子来讲解。

2.1 脏读

某一天,小王正在访问数据库,开启了一个事务,且向 imooc_user 表中插入名为 tom 的用户,如下:

BEGIN;
INSERT INTO imooc_user(id,username,age) VALUES (4,'tom',27);

此时,小王还未提交事务,但小李却在此时访问了数据库,并且查询了 imooc_user 表,如下:

SELECT * FROM imooc_user;

小李看到了如下结果:

+----+----------+-----+
| id | username | age |
+----+----------+-----+
| 4  | tom      | 27  |
+----+----------+-----+

小王明明还没有提交事务,但小李却已经看到了小王操作的结果。试想一下,如果此时小王回滚了事务,那么对于小李来说,他就看到了错误的数据,我们称之为脏读

流程图如下:
5e73097f096c6b6206800772.jpg

2.2 不可重复读

第二天,小王又在访问数据库了,他查看了 imooc_user 表中 id 为 1 的用户,如下:

SELECT * FROM imooc_user WHERE id = 1;

小王看到的结果是这样的:

+----+----------+-----+
| id | username | age |
+----+----------+-----+
| 1  | peter    | 18  |
+----+----------+-----+

此时,小李也来访问数据库,他开启了一个事务,并且修改了 id 为 1 的用户,如下:

BEGIN;
UPDATE imooc_user SET age = 100 WHERE id = 1;

这个时候,小王又查看了一次 id 为 1 的用户。

SELECT * FROM imooc_user WHERE id = 1;

但是他看到的结果却是这样的:

+----+----------+-----+
| id | username | age |
+----+----------+-----+
| 1  | peter    | 100 |
+----+----------+-----+

小王发现自己两次查询的数据不一样,可是小李的事务还未提交。像小王这样,两次查询结果不同的情况,我们称之为不可重复读

流程图如下:
5e7309d40953b22e06520884.jpg

2.3 幻读

第三天,小王开始了一个事务,并向 imooc_user 表中插入了一个名为 mary 的用户,如下:

BEGIN;
INSERT INTO imooc_user(id,username,age) VALUES (5,'mary',17);

此时,搞事的小李也来访问数据库,小李查询了一下 imooc_user 表,如下:

SELECT * FROM imooc_user;

小李看到了如下结果:

+----+----------+-----+
| id | username | age |
+----+----------+-----+
| 1  | peter    | 100 |
| 2  | pedro    | 24  |
| 3  | jerry    | 22  |
| 4  | tom      | 27  |
| 5  | mary     | 17  |
+----+----------+-----+

小李此时已经看到了新增的用户 mary 了,但是小王后悔了,他不想创建 mary 了,于是小王回滚了事务:

ROLLBACK;

但小李却看到了 mary,他一度以为自己出现了幻觉,我们把这种情况称之为幻读

流程图如下:
5e7309fd0937877c07081114.jpg

我们总结一下这三种异常的特点:

  1. 脏读:读到了其它事务还未提交的数据;
  2. 不可重复读:两次读到了同一数据的不同结果;
  3. 幻读:读到了其它事务新增但未提交的数据,而且新增数据并未成功。

3. 隔离级别

介绍了常见的 3 种并发异常后,我们再来介绍 4 种隔离机制。

SQL 事务的四种隔离机制主要是为了解决上述的三种并发异常,它们之间的关系如下表所示:

隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED ) 允许 允许 允许
读已提交(READ COMMITTED) 禁止 允许 允许
可重复读(REPEATABLE READ) 禁止 禁止 允许
可串行化(SERIALIZABLE) 禁止 禁止 禁止

上面的隔离级别由上往下,级别依次会提高,但消耗的性能也会依次提高。我们总结一下四种隔离级别:

  1. 读未提交:允许读未提交数据,可能会发生脏读、不可重复读和幻读异常;
  2. 读已提交:只能读已经提交的数据,避免了脏读,但可能会出现不可重复读和幻读;
  3. 可重复读:即能保证在一个事务中多次读取,数据一致,但可能会出现幻读;
  4. 可串行化:最高的隔离级别,串行的执行事务,可以避免 3 种异常,但性能耗损最高。

提示: SQL Server 和 Oracle 的默认隔离级别是读已提交,而 MySQL 的默认隔离级别是可重复读

鱼和熊掌不可而得兼!因此 SQL 提供了 4 种事务隔离级别,在数据吞吐能力和数据安全中,你需要作出相应的选择。

通过如下语句你可以设置事务隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL [level];

其中 level 表示隔离级别,如:READ UNCOMMITTED。

4. 小结

  • 事务隔离级别以及并发异常是数据库面试中的重点,请熟练掌握并总结。
  • 在实际开发中,事务的使用十分频繁,当然你不会手写这些 SQL 语句,会有相应的框架来帮你处理,但你仍然需要弄懂它们的原理,当出现问题时,你才可以迅速解决。