深入简出的掌握InnoDB引擎 MVCC协议

MVCC(Multi-Version Concurrent Control),即多版本并发控制协议,广泛使用于数据库系统(mysql、HBase)。由于MVCC没有一个统一的实现标准,本人将针对mysql的InnoDB引擎谈谈它的应用。

1.是什么

MVCC是行锁的一种变种,但是他在很多情况下避免了加锁的操作,因此开销更低。多版本并发控制实现了非阻塞的读操作,也可称之为一致性非锁定读。它通过行的多版本控制方式来读取当前执行时间数据库中的行数据。实质上使用的是快照数据

2.为什么需要

  • 消除锁的开销;如果要保证数据的一致性,最简单的方式就是对操作数据进行加锁,但是加锁不可避免的会有锁开销。所以,如果有能避免进行加锁的方式当然是最好的。
  • 提高并发度,后面会重点介绍。

3.它与事务隔离级别的关联

事务隔离级别“读已提交”与“可重复读”下 ,InnoDB存储引擎使用MVCC机制,

在“读已提交”事务隔离级别下,对于快照数据,MVCC读总是读取被锁定行的最新的快照数据

而“可重复读”读到的总是读取事务开始时的行数据版本

4.原理介绍

对于“可重复读”读到的总是读取事务开始时的行数据版本,那MVCC机制是如何保证可重复读的?

还要从它的数据结构上谈起,

1.行记录,Innodb引擎会为每一行添加3个字段实现的,DATA_TRX_ID、DATA_ROLL_PTR与DELETED_BIT

DATA _TRX_ID 表示产生当前记录项的事务ID(每开启一个新的事务,其对应的事务id会自动递增)
DATA_ROLL_PTR 一个指向此条记录项的undo信息的指针,undo信息是指此条记录被修改前的信息;
DELETED_BIT 用于标识该记录是否被删除

2.read view

它很重要,innodb的read view确定一条记录能否看到,它遵循的原则:

  • 看不到read view创建时刻以后启动的事务,场景1,Rule 1: When the read view object is created it notes down the smallest transaction identifier that is not yet used as a transaction identifier (trx_sys_t::max_trx_id).   The read view calls it the low limit. So the transaction using the read view must not see any transaction with identifier greater than or equal to this low limit.
  • 看不到read view创建时活跃的事务,场景2,Rule 2: The transaction using the read view must not see a transaction that was active when the read view was created.

RR(REPEATABLE-READ)读:每个事务在开始都会根据当前系统的活跃事务链表创建一个read_view,read_view是用来检索行的可见性的。
RC(READ-COMMITTED)读:事务中,每个语句都会创建read_view。

---------------------

Read View中的的变量(当前的活跃事务链表current-trx —> trx7 —> trx5 —> trx3 —> trx1)则按如下方式初始化:

read_view->creator_trx_id = current-trx;                       当前的事务id
read_view->up_limit_id = trx1;                                      当前活跃事务的最小id
read_view->low_limit_id = trx7;                                     当前活跃事务的最大id
read_view->trx_ids = [trx1, trx3, trx5, trx7];                   当前活跃的事务的id列表
read_view->m_trx_ids = 4;                                            当前活跃的事务id列表长度

---------------------

low_limit_id,即当时活跃事务的最大id,如果读到row的data_trx_id>=low_limit_id,说明这些数据在当前事务开始时都还没有提交,如注释中的描述,这些数据都不可见。

up_limit_id,即当时活跃事务列表的最小事务id,如果row的data_trx_id<up_limit_id,说明这些数据在当前事务开始时都已经提交,如注释中的描述,这些数据均可见。

data_trx_id在up_limit_id和low_limit_id之间的row,如果这个data_trx_id在trx_ids的集合中,就说明开启当前的事务的时候,这个data_trx_id还处于活跃状态,即还未提交,那么这个row是不可见的;如果这个data_trx_id不在trx_ids的集合中,就说明开启当前的事务的时候,这个data_trx_id已经提交,那么这个row是可见的。

实例:

 CREATE TABLE `test` (  
  `id` int(11) NOT NULL,  
  `b` int(11) NOT NULL,  
  PRIMARY KEY (`id`)  
) ENGINE=InnoDB 

表中有数据(1,1)

autocommit为false, tx_isolation 是REPEATABLE-READ

场景1:

session A

session B

start transaction;(A)

 

 

start transaction

 

update test set b=2 where id=1;

 

commit;

select * from test;(B)

 

B处结果为(1,2),似乎不符合那段话里的a条件,A事务看到了transaction version更大的B事务

场景2:

session A  

  session B

                          

start transaction

                         

update test set b=2 where id=1;

start transaction

 

select * from test;

 

                           

commit;

select * from test;(C)

 

C处结果为(1,1),也就是说,A事务没有看到transaction version更小的B事务

5.缺点

为了实现多版本,innodb必须对每行增加相应的字段来存储版本信息,同时需要维护每一行的版本信息,而且在检索行的时候,需要进行版本的比较,因而降低了查询的效率;innodb还必须定期清理不再需要的行版本,及时回收空间,这也增加了一些开销

6.为什么select count(*)在myisam表上很快,而在Innodb的表上很慢?

因为innodb采用了MVCC技术,对于相同的行,可能同时存在多个版本,innodb必须根据查询的时间来过滤掉一些行,才能得出结果,必然要执行全表扫描,而全表扫描是非常耗时的.对于myisam的表,任何行都只有一个版本,mysql甚至不需要扫描就可以直接返回精确的统计结果,我们用explain也可以看到,对于myisam的表,执行select count(*)的时候,mysql显示” Select tables optimized away”,查询直接被优化了;而对于innodb的表,可能是全表扫描,也可能是”using index”,总之,速度肯定会比myisam的表慢很多.

7.能禁用MVCC吗?

禁用MVCC可以降低innodb引擎的开销,而同时innodb又可以支持外键约束,可以实现自动恢复.MVCC本身不支持read uncommitted等级,所以可以通过设置transaction_isolation = read uncommitted 来禁用MVCC.但是任何改变innodb默认隔离等级的操作,都会起到innodb_locks_unsafe_for_binlog=off类似的效果,这会导致诸如insert into t select * from t_src 之类的语句不再给源表t_src加锁,也不再使用innodb的间隙锁,从而产生幻读,直接导致binlog中记录的sql语句不能正确的串行化,从而主从数据库的数据不再一致,而且基于binlog的增量备份也不再有效.所以除非不需要记录binlog,否则别这么做.当然我们可以这样做来优化从库的性能,因为从库不需要记录binlog.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章