MySQL 事务

事务的特性

事务处理几乎在每一个信息系统中都会涉及,它存在的意义是为了保证系统中所有数据都是符合期望的,且相互关联的数据之间不会产生矛盾,即数据状态的一致性 (Consistency)

需要达成数据状态一致性的目标,需要从三个方面来保障:

以上四种属性就是事务的 ACID 特性。A D I 是手段,C 是目的,前者是因,后者是果。在 MySQL 中,把需要保证原子性、持久性、隔离性、一致性的一个或多个数据库操作称为事务

MySQL 通过 undo log (回滚日志) 来保证事务原子性,关于 undo log 的详细介绍可见 undo log

MySQL 通过 redo log (重做日志) 来保证事务持久性,关于 redo log 的详细介绍可见 redo log

MySQL 通过 MVCC 和锁来保证事务隔离性,关于 MVCC 的详细介绍可见 MVCC关于锁的详细介绍可见

事务的状态

在 Redis 事务中,一个事务中的所有命令在事务提交前都不会执行,只会添加到事务队列中,当事务提交后再一起执行

而 MySQL 事务中,一个事务中的所有操作在事务提交前其实已经执行,但仅仅只是对内存中缓冲页修改,并未刷新回磁盘,事务提价后也不会立刻刷回磁盘,脏页刷盘时机可见 脏页刷新回磁盘

PS:一个事务提交后不立刻刷盘,不怕数据丢失吗?这个由 redo log 保障持久性!!

根据事务中操作所执行的不同阶段把事务大致划分成了下面几个状态:

7

并发事务的一致性问题

在 Java 并发中,如果存在多个线程访问共享资源,但不对其进行同步控制,那么一定会出现问题;MySQL 的事务也是如此,一个事务包含一个或多个数据库操作,而且事务中都是对数据库的操作,相当于全都是共享资源,所有事务访问的是同一个数据库

下面模拟两个事务在没有任何约束的条件下执行的情况:假设事务 T1 和 T2 分别对 A - 5,对 B + 5,A 和 B 的初始值分别为 11 和 2,正确的情况下 A 和 B 最终值应该为 1 和 12

8

可以看到如果不对事务的执行顺序加以控制的话,结果就会出问题,实际上是没有保证事务的隔离性。如果让多个事务都串行执行,即同一时刻只有一个事务在执行,类似于单线程,这样会大大降低执行的效率

可以像 Java 并发中一样对共享资源加锁,当多个事务都需要访问同一个共享变量时,使用锁保证同一时刻只有一个事务可以访问,只有当事务提交后其它事务才可以继续访问,这样就实现了可串行化执行

注意:如果两个事务同时读同一个共享资源不会引发并发问题,只有存在写操作时才会出现问题,即:读写、写读、写写。有点类似于 Java 中的读写锁:共享读、独占写

下面来分析在没有任何约束的情况下,并发事务会出现哪些问题!!!

脏写 (Dirty Write)

如果一个事务修改了另一个未提交事务修改过的数据,就意味着发生了脏写现象

假设一致性需求为 x 和 y 始终要保持一致,如果按照下面的执行顺序,最终数据库的数据:x = 2,y = 1

9

假设 x 和 y 的初始值都为 0,如果按照下面的执行顺序,最终数据库的数据:x = 0,y = 2

10

脏读 (Dirty Read)

如果一个事务读到了另一个未提交事务修改过的数据,就意味着发生了脏读现象

假设一致性需求为 x 和 y 始终要保持一致,如果按照下面的两种执行顺序,将会出现一致性问题

11

上图的左边只是读到了一个不一致的状态,虽然数据库最终还是一致性的 (广义上的脏读);上图的右边直接读到了一个根本不存在的值 (严格意义上的脏读)

不可重复读 (Non-Repeatable Read)

如果一个事务修改了另一个未提交事务读取的数据,就意味着发生了不可重复读

假设一致性需求为 x 和 y 始终要保持一致,如果按照下面的执行顺序,最终读取到的结果:x = 0,y = 1 (广义上的不可重复读)

12

假设在一个事务中两次读 x,如果按照下面的执行顺序,将出现两次读取结果不一致的情况 (严格意义上的不可重复读)

13

幻读 (Phantom)

如果一个事务先根据某些搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合那些搜索条件的记录 (这里的写入可以是 insert、update、delete),就意味着发生了幻读现象

注意:幻读针对于一个结果集,而不可重复读针对于一个记录 (值)

14

事务隔离级别

上部分介绍了并发事务中可能会出现的四种不一致问题,这四种问题的严重程度:脏写 > 脏读 > 不可重复读 > 幻读

由于脏写会直接影响了数据库的不一致性,也就是影响了最终一致性;而后面三种只影响了读取过程中的不一致性,并未导致最终不一致。所以 SQL 标准中规定的四种隔离级别都不允许脏写的发生

下面给出四种不同的隔离级别,以及并发事务执行过程中可以发生不同的现象

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

隔离级别从上到下依次递增,但性能依次递减: