MVCC实现-MYSQL INNODB MVCC实现

来源:互联网 发布:网络小说推荐 知乎 编辑:程序博客网 时间:2024/04/27 23:07
MySQL

MVCC实现-MYSQL INNODB MVCC实现

前言

MVCC 是用来做什么的?为什么要有MVCC?

Multiversion concurrency control (MCC or MVCC), is a concurrency control method commonly used by database management systems to provide concurrent access to the database and in programming languages to implement transactional memory.[1]

http://en.wikipedia.org/wiki/Multiversion_concurrency_control

个人理解,是数据库在实现事务隔离等特性下,尽量不减少并发处理能力的一种解决方案。

 

为了更好说明MVCC的作用,我们最好先复习下事务隔离级别都是哪些:

ANSI/ISO SQL 定义了如下几个事务隔离级别

Serializable、Repeatable reads、Read committed、Read uncommitted

维基百科解释很拗口,很多没有接触过数据库系统的同学更不是很理解,建议大家可以看下如下MySQL中各个事务隔离级别各个客户端的表现,就很明白了,http://www.cnblogs.com/zemliu/archive/2012/06/17/2552301.html 

简单总结如下:

Serialzable是最强的事务隔离级别,对同一数据的更新必须是串行化的,任何写入操作都将建立写入锁,其他并发提交的事务都将会失败,一般实现这个事务隔离级别对性能影响都非常巨大,故应用场景比较有限。

Repeatable read是MySQL默认事务隔离级别,对于任何一个打开的客户端事务,其所示的数据都应是静态的,当然自己更新的数据除外,字面意思可以可以放心的重复读其他事务可能更新的数据,这些数据对于我来言是静态的,当然自己客户端更新的操作除外。

Read committed 是只仅能读到被客户端提交的数据,也可以说只要提交过的数据其他客户即可见。

Read uncommitted 是可以读到客户端未提交的数据,比如客户端a打开一个事务,做了些更新,但是没有提交,但是客户端b却可以看见a更新的数据,也就说数据库不做任何干预的话,应该是这种事务隔离级别。

事务隔离级别从上往下依次减弱,数据库实现的成本也越来越小,可支持的并发相应也越来越大。

 

正文一

  • MySQL innodb MVCC实现

innodb MVCC主要是为Repeatable-Read事务隔离级别做的。在此隔离级别下,A、B客户端所示的数据相互隔离,互相更新不可见,MySQL是如何做到的呢?

首先来看下innodb的行结构、Read-View的结构,这个对于理解innodb mvcc的实现由重要意义:

我们看到的innodb row中包含: DATA_TRX_ID,DATA_ROLL_PTR,DELETE BIT, 6字节的DATA_TRX_ID标记了最新更新这条行记录的transaction id,7字节的DATA_ROLL_PTR指向当前记录项的undo信息,找之前版本的数据就是通过这个指针,DELETE BIT位用于标识该记录是否被删除。

 聚簇索引行结构(与多版本一致读有关的部分,DELETED BIT省略):
二级索引行结构:

引自@何_登成 http://hedengcheng.com/?p=148 推荐大家阅读

Read-view数据结构

我看的MySQL版本时5.5.32,其他版本略有不同

storage/innobase/include/read0read.h 如下

 

及storage/innobase/read/read0read.c    read_view_open_now()

storage/innobase/include/read0read.ic   read_view_sees_trx_id()

 

  • innodb可见性判断

到这里我们也就不难看出实际实现就是这两个数据结构进行比较:

InnoDB每个事务在开始的时候,会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view)在read_vied_sees_trx_id方法里我们有如下比较:

low_limit_id是“高水位”即当时活跃事务的最大id,如果读到row的db_tx_id>=low_limit_id,说明这些id在此之前的数据都没有提交,如注释中的描述,这些数据都不可见。

if (trx_id >= view->low_limit_id) {

return(FALSE);
}

up_limit_id是“低水位”即当时活跃事务列表的最小事务id,如果row的db_tx_id<up_limit_id,说明这些数据在事务创建的id时都已经提交,如注释中的描述,这些数据均可见。

if (trx_id < view->up_limit_id) {

return(TRUE);
}

在两个limit_id之间的我们需要从小到大逐个比较一下:

n_ids = view->n_trx_ids;

for (i = 0; i < n_ids; i++) {
trx_id_t        view_trx_id
= read_view_get_nth_trx_id(view, n_ids – i – 1);

if (trx_id <= view_trx_id) {
return(trx_id != view_trx_id);
}
}

这样我们在要在事务中获取100行数据,我们就能根据这100行的row db_tx_id即本事务的read_view来判断此版本的数据在事务中是否可见。

如果数据不可见我们需要去哪里找上版本的数据呢?就是通过刚才提到过的7BIT的DATA_ROLL_PTR去undo信息中寻找,同时再判断下这个版本的数据是否可见,以此类推。

 

  • innodb更新

更新这个大家一般都比较熟悉,我这里简单表述一下,如一个测试表:

create table test (key int primary key,value varchar(10));

insert

InnoDB为每个新增行记录,如insert into test value(’1′,’aaa’), 会创建新的row,row db_tx_id即为当前系统版本号作为创建ID。

update

如update test set value=’bbb’  where key =’bbb’,InnoDB会复制了一行,这个新行的版本号使用了本次db_tx_id更新的版本号。它也把之前版本号作为了删除行的版本,即把原有row delete bit置为删除,不可见。

delete

InnoDB为每个删除行的记录当前系统版本号作为行的删除ID,也就是说把之前说的BIT位置成不可见的。

我这里主要描述的是Innodb主键更新,二级索引的更新大多会映射或转换为多条的主键更新,之前提到@何_登成 博客http://hedengcheng.com/?p=148 中举了几个主键更新和二级索引更新的例子,大家可以参看下。

后续也会针对Hbase,PostgreSQL,Oracle的MVCC进行对比和分析,来看下其他数据库的实现方式。

 

参考

http://en.wikipedia.org/wiki/Multiversion_concurrency_control

http://en.wikipedia.org/wiki/Isolation_%28database_systems%29

http://hedengcheng.com/?p=148

http://blog.csdn.net/chen77716/article/details/6742128

http://www.cnblogs.com/zemliu/archive/2012/06/17/2552301.html

0 0
原创粉丝点击