June's Studio.

MySQL-事务隔离级别与MVCC

字数统计: 2.5k阅读时长: 9 min
2020/03/14

事务隔离级别

查看数据库的隔离级别命令: select @@tx_isolation;

事务并发执行遇到的问题

  • 脏写(Dirty Write)
    如果一个事务修改了另一个未提交事务修改过的数据,那就意味着发生了脏写

  • 脏读(Dirty Read)
    如果一个事务读到了另一个未提交事务修改过的数据,那就意味着发生了脏读

  • 不可重复读
    如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,那就意味着发生了不可重复读
    事务中前后两次读取到的值不一样( 在Session B中提交了几个隐式事务(注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了number列为1的记录的列name的值,每次事务提交之后,如果SessionA中的事务都可以查看到最新的值,这种现象也被称之为不可重复读)

  • 幻读
    如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来,那就意味着发生了幻读
    幻读强调的是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录

问题的严重性:
脏写 > 脏读 > 不可重复读 > 幻读

SQL标准中的四种隔离级别

  • READ UNCOMMITTED:未提交读
  • READ COMMITTED:已提交读
  • REPEATABLE READ:可重复读
  • SERIALIZABLE:可串行化

SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED Possible Possible Possible
READ COMMITTED Not Possible Possible Possible
REPEATABLE READ Not Possible Not Possible Possible
SERIALIZABLE Not Possible Not Possible Not Possible
  • READ UNCOMMITTED隔离级别下,可能发生脏读不可重复读幻读问题。

  • READ COMMITTED隔离级别下,可能发生不可重复读幻读问题,但是不可以发生脏读问题。

  • REPEATABLE READ隔离级别下,可能发生幻读问题,但是不可以发生脏读不可重复读的问题。

  • SERIALIZABLE隔离级别下,各种问题都不可以发生。

脏写的问题太严重了 ,所以 无论哪种隔离级别都不允许脏写的情况发生

如何设置事务的隔离级别

SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;

查看隔离级别:

SHOW VARIABLES LIKE ‘transaction_isolation’;
or SELECT @@transaction_isolation;

MVCC原理

版本链

Innodb聚簇索引记录中都包含两个必要的隐藏列: trx_id, roll_pointer (row_id非必要,当创建的表中有主键或者非NULL的UNIQUE键时都不会包含row_id列)

roll_pointer 指向一条undolog

insert undolog 只在事务回滚时起作用,当事务提交后 ,该类型的undo日志就没有用了

innodb使用锁来保证不会有脏写情况的发生

每次对记录进行改动,都会记录一条undo日志,每条undo日志都会有一个roll_pointer属性,可以将这些undo日志都连起来,串成一个链表。这个链表我们称为 版本链

版本链的头节点就是当前记录的最新的值。另外,每个版本中还包含生成该版本时对应的事务id

ReadView

ReadUncommited隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本。
对于使用serializable隔离级别的事务,规定了使用加锁的方式来访问记录
对于使用READ COMMITTED 和 REPEATABLE READ隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录,也就是说不能无脑读取最新版本的记录,需要判断版本链中哪个版本对当前事务是可见的。
为此,提出了ReadView的概念,包含4个比较重要的内容:

  • m_ids: 表示在生成ReadView是当前系统中活跃的读写事务的id列表
  • min_trx_id: 在生成ReadView时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
  • max_trx_id: 在生成ReadView时,系统应该分配给下一个事务的id值
  • creator_trx_id: 表示生成该ReadView的事务id

有了ReadView,就可以判断记录的某个版本是否可见:

  • trx_id 等于 creator_trx_id ,可见
  • trx_id < min_trx_id ,可见
  • trx_id >= max_trx_id ,不可见
  • trx_min_id < trx < trx_max_id, 如果trx_id在m_ids中,不可见,否则可见。

如果当前ReadView不可见,就顺着版本链往下查找,直到最后一个版本。

READ COMMITTED 和 REPEATABLE READ的主要区别就是生成ReadView的时机不同:

READ COMMITED — 每次读取数据前都生成一个ReadView
REPEATABLE READ — 在第一次读取数据时生成一个ReadView

MVCC小结

所谓MVCC,指的就是使用READ COMMITIED、REPEATABLE READ这两种隔离级别的事务在执行普通的select操作时,访问记录版本链的过程。这样可以使不同事务的读-写,写-读操作并发执行,从而提升系统性能。
READ COMMITTED、REPEATABLE READ 这两个隔离级别很大的不同就是生成ReadView的时机不同,RC是每次select操作前都会生成一个ReadView,RR只在第一次进行普通select操作前生成一个ReadView,之后的查询操作都重复使用和这个ReadView
ps: 关于purge(清除)

  • insert undo在事务提交后就被释放了,而update undo由于还需要支持MVCC,不能立即删除
  • 为了支持mvcc ,delete mark操作仅仅是在记录上打一个删除标记,并没有真正删除。等后台的purge线程去删除

ACID的底层实现原理

mysql基础架构:

连接线程处理( 连接处理,授权认证,安全)
查询缓存、分析器、优化器(查询解析,分析,优化,缓存,所有内建函数(日期、时间、数字、加密函数等)
存储引擎(存储和提取数据. myisam、innodb)

ACID特性:
原子性 Atomicity
一致性 Consistency
隔离性 Isolation
持久性 Durability

原子性:

基于undo log : sql执行的相关信息记录在操作日志里,回滚执行相反的操作

持久性:

redo log
mysql数据存在磁盘中,io效率低。 innodb提供了一个缓存buffer,包含了部分磁盘数据页的一个映射,作为访问数据库的一个缓冲
写数据也会先向buffer写数据,然后再同步到磁盘上。提高效率但是也有数据丢失的风险。引入redo log解决这个问题

redo log预写式日志,所有的日志会先写到日志里面再同步到磁盘。如果mysql宕机了 还有redolog可以恢复数据

redolog为什么比buffer同步数据快:
buffer随机写的io,每次修改的数据位置随机。buffer持久化数据是以数据页page为单位,mysql默认的page数据页大小为16k,很多无效io
redo log文件尾部追加,顺序io。

redo log持久化机制:
参数innodb_flush_log_at_trx_commit
0: 等待主线程每秒shuax
1:事务提交同步写入磁盘
2:事务提交后异步写入磁盘

隔离性

写-写:锁 , 行锁、表锁、间隙锁
写-读:MVCC(脏读、不可重复读、幻读)

一致性

上面的三个特性保证了数据的一致性,在操作前或者操作后数据都是有效且正确的

什么是MVCC

Multi-Version Concurrency Control 即:多版本并发控制,为了提高数据库的并发性能

当前读select

  • select lock in share mode (共享锁)
  • select for update (排它锁)
  • update (排它锁)
  • insert (排它锁)
  • delete (排他锁)
  • 串行化事务隔离级别

快照读
快照读的实现是据图MVCC实现的,因为是多版本,所以快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据

  • 不加锁的select操作

数据库四种隔离级别:

  • 读未提交
  • RC 读已提交(常用)
  • RR 可重复读(常用,mysql默认的隔离级别,RR默认是开启了间隙锁的)
  • 串行化

MVCC undolog+版本链+readview(快照)

读已提交通过版本链和ReadView实现:
readView的数据结构:
m_ids: 在生成ReadView时当前系统中活跃的镀锡数据的 事务id 列表
min_trx_id: 在生成readView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值
max_trx_id: 生成ReadView时,系统应该分配给下一个事务的id值
creator_trx_id: 生成该ReadVIew的事务的事务id

ReadView怎么判断版本链中的哪个版本可用?

  • trx_id == creator_id: 可以访问这个版本(这条记录的这个事务是自己本身创建)
  • trx_id < min_trx_id : 可以访问,都是已经commit了的事务
  • trx_id > max_trx_id : 不可以访问
  • min_trx_id <= trx_id <= max_trx_id : 不可以访问,都是未commit的事务,反之可以

读已提交和可重复读生成readView的时机是不同的:
RC是每次执行select查询的时候,就会生成一个ReadView,以每一个select查询为单位。 所以会导致在同一个事务里面两个select会查询出不同的结果

RR可重复读生成ReadView是以一个事务为单位,即同一个事务里面多次select都使用同一个ReadView。
RR虽然解决了不可重复读的问题,但是会存在幻读的问题。幻读在innodb这个引擎里面是解决了的

快照读怎么解决幻读的问题的?
幻读:在一个读的事务里面,第一个select语句和第二个select语句之间有insert语句,导致两次select的结果不一样
通过间隙锁锁住一段范围,给select条件的范围加锁

CATALOG
  1. 1. 事务隔离级别
    1. 1.1. 事务并发执行遇到的问题
    2. 1.2. SQL标准中的四种隔离级别
  2. 2. MVCC原理
    1. 2.1. 版本链
    2. 2.2. ReadView
      1. 2.2.1. READ COMMITED — 每次读取数据前都生成一个ReadView
      2. 2.2.2. REPEATABLE READ — 在第一次读取数据时生成一个ReadView
  3. 3. MVCC小结
  4. 4. ACID的底层实现原理
    1. 4.1. mysql基础架构:
    2. 4.2. 原子性:
    3. 4.3. 持久性:
    4. 4.4. 隔离性
    5. 4.5. 一致性
    6. 4.6. 什么是MVCC