前言
MySQL最吸引人的特点在于它“可拔插式”的插件式引擎,而对于多种多样的数据库引擎来说,InnoDB存储引擎是功能和特性最丰富的一款。其中,“多版本并发控制”,缩写MVCC。是它最强大的一个功能。本文基于MySQL 5.7.X的InnoDB引擎,介绍MySQL的MVCC特性。
先导内容
InnoDB基础:
在了解MVCC之前,需要先知道一些InnoDB的基础知识,可以看下博主之前写的文章:
MySQL系列(一)5.7.X InnoDB引擎新特性
事务:
事务是数据库最重要的内容之一。然而,现如今市面上各种厂商所开发的数据库对于事务特性的支持又有很大的不同,下面简单介绍几款主流的数据库所支持的“事务隔离等级”:
- Oracle
仅支持两种事务隔离等级:READ COMMITTED(读已提交)和REPEATABLE READ(可重复读) - MySQL
支持所有的隔离等级,从弱到强分别为:READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)、SERIALIZABLE(串行化)。下文将逐一介绍。
事务四大原则
可总结为ACID原则
- Atomic(原子性)
事务中大都包含许多条增删改查的sql语句,不管是多条还是单独的一条语句,原子性都要保证他们执行的过程是不可中断的。即无数条sql的执行要被当成像执行一条sql一样。 - Consistency(一致性)
事务在开始执行前和执行后的各种约束状态和数据都必须是一致的,比如一个列的唯一约束。相信大家也都知道过最常见的银行转账例子,这里不再赘述。 - Isolation(隔离性)
因为各种数据库对隔离性有着不同的理解,所以各种数据库的隔离等级支持也是不一样的。简单来说,隔离性指的是两个及以上的事务在并发执行的过程中,对数据的修改是不是相互干扰的。不管事务是否提交、回滚、执行中。 - Durability(持久性)
事务一旦执行它最后的结果状态必须是永久保留的,这里不一定指的是数据而是事务提交或者回滚后的状态。
事务隔离级别
-
READ UNCOMMITTED(读未提交)
一个事务可以读取到其他未提交的事务。这相当于没有隔离等级,所以导致的问题也是显而易见:脏读、不可重复读、幻读。这一级主要了解下脏读的概念:读取到未提交的数据。这个数据可能会被事务提交或者回滚。 -
READ COMMITTED(读已提交)
一个事务只可以读到其他事务已经提交了数据。故此,它从根本上避免了脏读因为当前等级获取到的所有数据都是已经提交了的,然而,不可重复读和幻读是不可避免的 (不可重复读和幻读两者的概念非常相似,官方文档中把不可重复读定义为幻读Phantom) 。这里解释一下不可重复读的概念:在一个事务执行中,对同一条数据行的每一次读操作得到的结果都是不同的。虽然听起来像是很棘手的问题,但是这些数据都是其他事务已经提交的。只要提交了那么都是可以容忍的,Oracle的默认隔离级别就是这一级;InnoDB对所有读操作,会从Undo Log中获取最新的已提交版本。下面可以简单了解一下执行锁定读
SELECT...FOR UPDATE
、SELECT...LOCK IN SHARE MODE
,UPDATE
,DELETE
三条语句情况下,InnoDB对行加的锁:
UPDATE t SET b = 5 WHERE b = 3;
InnoDB会锁住所有满足b = 3
条件的行,然后unlock
所有读到但是不满足条件的行,这就是Gap Lock
。假如此时还有别的事务执行同样的sql语句,则会采用semi-consistent
方式来update
。对InnoDB锁的知识会在下一篇博客中详细介绍。
-
REPEATABLE READ(可重复读)
实现这一级别的隔离,InnoDB同时使用了快照和锁。对于快照:事务只会读取一次Undo Log中最新版本的已提交版本,然后贯穿整个事务过程直到结束。很显然,由于快照只会读取一次当然保证了数据的可重复读要求。对于锁:即使用了
Gap Lock
也使用了Next Key Lock
两种锁结合。考虑跟之前同样的sql语句UPDATE t SET b = 5 WHERE b = 3;
它锁住所有满足条件的行,同时也锁住了行与行之前的“间隙”。
-
SERIALIZABLE(串行化)
事务只能一个接着一个执行,只有上一个完成才能执行下一个,串行化使事务不能并发执行。
MVCC
概念
先解释一下英文缩写的意思:其全称为 Multiple Version Concurrency Control 翻译过来就是多版本并发控制。
何为版本?何为控制?
MySQL InnoDB存储引擎为了实现MVCC,它专门设计了三种概念:Undo Log、行字段、快照。
-
Undo Log
Undo Log又可以分为 insert和update undo log。insert logs被用在回滚时,当事务提交之后就会被从Undo Log中移除。
update logs被用在一致性读和构建数据库快照版本。它在事务提交后不会立即从Undo Log中移除,直到没有任何一个事务需要用到前面两个作用。
在InnoDB内存和磁盘架构中专门有一个区域是用来分别存放Undo Log缓存和文件数据的。注意Undo Log与Redo Log有着很大的区别。这个Undo Log就可以理解为上文提到的版本。这个文件中会记录旧版本中改变的所有行。以此用来并发和回滚控制。
-
行字段
InnoDB给每一个表中的各行创建了3种类型的字段,分别叫做:DB_TRX_ID (6B)、DB_ROLL_PTR (7B)、DB_ROW_ID (6B)。括号中表示的是字段所占的字节数。
字段名 | 作用 |
---|---|
DB_TRX_ID | 对这一行insert 或update 的最新的事务id,表示此行有改动 |
DB_ROLL_PTR | 指向“回滚段” Undo Log里面的记录指针。假如此行被修改,那么可以通过Undo Log记录来重建行内容 |
DB_ROW_ID | 这一行的唯一id,跟建表时经常设置的PRIMARY KEY 、AUTO_INCREMENT 有关系。只有设置了聚集索引才会有ROW_ID,否则这个字段不会显示地出现。 |
对行执行delete
语句,InnoDB会记录到Undo Log而不会立即删除。只有当Undo Log里的记录被废弃时才会真正地删除这一行。