Jamzy Wang

life is a struggle,be willing to do,be happy to bear~~~

InnoDB中的MVCC

2015-03-21 19:49

原创声明:本作品采用知识共享署名-非商业性使用 3.0 版本许可协议进行许可,欢迎转载,演绎,但是必须保留本文的署名(包含链接),且不得用于商业目的。

MVCC (Multi-Version Concurrency Control), 多版本的并发控制协议,与MVCC相对的,是基于锁的并发控制——Lock-Based Concurrency Control)。MVCC将数据分为多个版本,每个版本与一个时间戳相关联,事务中的所有读操作只能读取小于其时间戳的最大时间戳的数据。对于写操作采用两阶段封锁协议。这种协议的最大好处是:

1
读不加锁,读写不冲突。

在读多写少的应用中,读写不冲突是非常重要的,这会极大的增加系统的并发性能,这也是为什么现阶段,几乎所有的RDBMS,都支持了MVCC,如Oracle、DB2(since 9.7)、SQL Server(since 2005)、 Mysql Innodb, Postgres等等。

在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。记录何时创建何时过期有两种实现方式,一种是基于时间戳的(如Oracle),一种是基于事务ID的(如Innodb、Postgres)。下面以事务ID为例说明MVCC的具体实现。

在事务ID实现中,每开启一个新事务,事务的版本号就会递增。select、insert、delete update操作数据的规则如下(在可重读Repeatable reads事务隔离级别下):

1
2
3
4
1) SELECT时,读取创建版本号 <= 当前事务版本号 AND 删除版本号为空或 > 当前事务版本号。
2) INSERT时,保存当前事务版本号为行的创建版本号
3) DELETE时,保存当前事务版本号为行的删除版本号
4) UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

通过MVCC,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。

在这种并发控制方式下,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本(有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例 (摘自:MySQL 加锁处理分析):

快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析)

1
select * from table where ?;

当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。

1
2
3
4
5
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values ();
update table set ? where ?;
delete from table where ?;

所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。

未完待续。

Comments