MySQL 事务

事务

事务是构成单一的逻辑工作单元的操作集合,要么全部执行,要么全都不执行。

事务特性

  • 原子性:事务是不可分割的,要么执行全部操作,要么根本不执行
  • 一致性:以隔离的方式执行事务,以保持数据库的一致性
  • 隔离性:事务是单一的单元,他的操作不能被其它不属于事务的数据库操作分割开
  • 持久性:在一个事务成功完成后,他对数据库的改变必须是永久的

并发事务的问题

脏读(dirty read)

一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。

这时另一个事务读取了这个还未提交的数据,此时第一个事务回滚,数据并没有被提交到数据库,第二个事务读取到的就是脏数据。

丢失修改(lost to modify)

在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。

不可重复读(unrepeatable read)

一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。

幻读(phantom read)

一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,幻读与不可重复读类似

不可重复读与幻读的区别

  • 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;
  • 幻读的重点在于记录新增比如多次执行同一条查询语句时,发现查到的记录增加了。

幻读其实可以看作是不可重复读的一种特殊情况

并发事务的控制方式

通过锁来显式控制共享资源而不是通过调度手段,MySQL 中主要是通过读写锁来实现并发控制。(悲观锁)

共享锁(S 锁):又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。

排他锁(X 锁):又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条记录加任何类型的锁(锁不兼容)。

可以分为表级锁与行级锁,InnoDB 支持表级锁,也支持行级锁。行级锁的粒度更小,仅对相关的记录上锁即可,对于并发写入操作来说, InnoDB 的性能更高。

MVCC

多版本并发控制(MVCC,Multi-version concurrency control),对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。(乐观锁)

事务隔离级别总结

  • 读取未提交(READ-UNCOMMITTED) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • 读取已提交(READ-COMMITTED):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • 可重复读(REPEATABLE-READ):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • 可串行化(SERIALIZABLE):最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

MySQL InnoDB 存储引擎的默认支持的隔离级别是可重复读

InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,核心思想就是一个事务在操作某张表数据的时候,另外一个事务不允许新增或者删除这张表中的数据了。主要有下面两种情况:

  • 快照读:由 MVCC 机制来保证不出现幻读。
  • 当前读:使用 Next-Key Lock 进行加锁来保证不出现幻读,Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的结合,行锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。