五分钟后,你将真正理解MySQL事务隔离级别!

  • 2020 年 2 月 19 日
  • 筆記

点击上方“码农沉思录”,选择“设为星标”

优质文章,及时送达

什么是事务?

事务是一组原子性的SQL操作,所有操作必须全部成功完成,如果其中有任何一个操作因为崩溃或其他原因无法执行,那么所有的操作都不会被执行。也就是说,事务内的操作,要么全部执行成功,要么全部执行失败。

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

事务的特性

一个运行良好的事务处理系统,还需要具备四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)。这四个特性简称为ACID特性。

原子性(Atomicity)

一个事务必须被视为一个不可分割的最小逻辑工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。对于一个事务来说,不可能只执行其中的一部分操作,而不执行其中的另外一部分操作,这就是事务的原子性。

一致性(Consistency)

事务执行的结果必须是从一个一致性的状态转换到另外一个一致性的状态。当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果事务因为崩溃或其他原因尚未完成,被迫中断最终事务没有提交,那么事务中所做的修改也不会保存到数据库中。

隔离性(Isolation)

通常来说,一个事务的执行不能其它事务干扰。也就是说,一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。后面我们将要讲解隔离级别(Isolation Level)的时候,会发现为什么我们要说“通常来说”是隔离的。

持续性(Durability)

事务一旦提交,它对数据库中的数据的修改就应该是永久性的。此时即使系统崩溃,修改的数据也不会丢失。不过,实际上持久性也分很多不同的级别,有些持久性策略能够提供非常强的安全保障,而有些则未必。

事务隔离级别

在SQL标准中定义了四种隔离级别,每一种级别都定义了一个事务所做的修改,在另外一个事务内和事务间,哪些是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

未提交读(Read Uncommitted)

未提交读级别中,事务中的修改即使没有提交,对其他事务也是可见的。读取到了事务没有提交的数据,就被成为脏读(Dirty Read)。事务没有提交的数据是很“脏”的,被读取到会引起很多问题。从性能角度上看,未提交读级别不会比其他级别好很多,但缺乏其他级别的好处,所以在实际应用中很少被用到。

为加上深对未提交读级别的理解,让我们看一个脏读的例子,首先设置事务隔离级别为未提交读

mysql> set session transaction isolation level read uncommitted;  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)

左右分别为两个用户,左边是用户A,右边是用户B,时间线从上至下:

#用户A:查询user表,有一条OneMoreStudy的记录           mysql> select * from user;                           +----+--------------+                               | id | name         |                               +----+--------------+                               |  1 | OneMoreStudy |                               +----+--------------+                               1 row in set (0.00 sec)                                                                                                                                               #用户B:开始事务                                                               mysql> start transaction;                                                               Query OK, 0 rows affected (0.00 sec)                                                                                                                  #用户B:更新user表的一条记录                                                               mysql> update user set name = 'OMS' where id = 1;                                                               Query OK, 1 row affected (0.01 sec)                                                               Rows matched: 1  Changed: 1  Warnings: 0                                                     #用户A:查询user表,有一条OMS的记录,脏读               mysql> select * from user;                           +----+------+                                       | id | name |                                       +----+------+                                       |  1 | OMS  |                                       +----+------+                                       1 row in set (0.00 sec)                                                                                                                                               #用户B:提交事务                                                               mysql> commit;                                                               Query OK, 0 rows affected (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)

左右分别为两个用户,左边是用户A,右边是用户B,时间线从上至下:

#用户A:开始事务  mysql> start transaction;  Query OK, 0 rows affected (0.00 sec)    #用户A:查询user表,有一条OneMoreStudy的记录  mysql> select * from user;  +----+--------------+  | id | name         |  +----+--------------+  |  1 | OneMoreStudy |  +----+--------------+  1 row in set (0.00 sec)                                                            #用户B:更新user表的一条记录                                                          mysql> update user set name = 'OMS' where id = 1;                                                          Query OK, 1 row affected (0.00 sec)                                                          Rows matched: 1  Changed: 1  Warnings: 0    #用户A:查询user表,有一条OMS的记录,不可重复读  mysql> select * from user;  +----+------+  | id | name |  +----+------+  |  1 | OMS  |  +----+------+  1 row in set (0.00 sec)    #用户A:提交事务  mysql> commit;  Query OK, 0 rows affected (0.00 sec)

可重复读(Repeatable Read)

可重复读级别中,保证了在同一个事务中多次读取同样记录的结果是一致的。即使多次读取之间有其他事务对其结果做了修改,同一个事务中多次读取的结果也是一致的。可重复读级别也是MySQL的默认事务隔离级别。

不过,当一个事务在读过某个范围内的记录时,其他事务又在这个范围内插入了新的记录,当之前的事务再一次读取这个范围的记录时,不会读取到新插入的那条记录,这被称为幻读

为了更好的理解幻读,让我们看一个例子,首先把事务隔离级别设置为可重复读

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)

左右分别为两个用户,左边是用户A,右边是用户B,时间线从上至下:

#用户A:开始事务  mysql> start transaction;  Query OK, 0 rows affected (0.00 sec)    #用户A:查询user表,有一条记录  mysql> select * from user;  +----+--------------+  | id | name         |  +----+--------------+  |  1 | OneMoreStudy |  +----+--------------+  1 row in set (0.00 sec)                                                                #用户B:插入一条数据                                                              mysql> insert into user (name) value ('OneMoreStudy');                                                              Query OK, 1 row affected (0.01 sec)    #用户A:查询user表,还是一条记录,幻读  mysql> select * from user;  +----+--------------+  | id | name         |  +----+--------------+  |  1 | OneMoreStudy |  +----+--------------+  1 row in set (0.00 sec)    #用户A:提交事务  mysql> commit;  Query OK, 0 rows affected (0.00 sec)    #用户A:查询user表,两条记录  mysql> select * from user;  +----+--------------+  | id | name         |  +----+--------------+  |  1 | OneMoreStudy |  |  2 | OneMoreStudy |  +----+--------------+  2 rows in set (0.00 sec)

可串行化(Serializable)

可串行化级别中,强制事务串行执行,是最高的隔离级别。在这个级别中,虽然避免了上面提到的幻读,但是会在读取的每一行上加锁,可能导致大量的超时和锁竞争问题,所以在实际应用中很少被用到。除非,非常需要确保数据一致性并且不要求高并发,可以采用可串行化级别。

总结

本文首先简单介绍了事务及其ACID特性,然后着重讲解了事务的四种隔离级别:

  1. 未提交读:事务中的修改即使没有提交,对其他事务也是可见的。
  2. 提交读:事务开始时,只能查询到其他的事务已经提交的修改。
  3. 可重复读:保证在同一个事务中多次读取同样记录的结果是一致的。
  4. 可串行化:强制事务串行执行。

祝大家在2020年工作顺路,家庭幸福,合家团圆