多版本并发控制技术已经被普遍运用于各大数据库系统中,如Oracle,MS SQL Server 2005+, Postgresql, Firebird, Maria等等,开源数据库MYSQL中流行的INNODB引擎也采用了相似的并发控制技术.本文就将结合实例来解析不一样事务隔离等级下INNODB的MVCC实现原理.mysql
MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,再也不单纯的使用行锁来进行数据库的并发控制,取而代之的是,把数据库的行锁与行的多个版本结合起来,只须要很小的开销,就能够实现非锁定读,从而大大提升数据库系统的并发性能.sql
MVCC能够提供基于某个时间点的快照,使得对于事务看来,老是能够提供与事务开始时刻相一致的数据,而无论这个事务执行的时间有多长.因此在不一样的事务看来,同一时刻看到的相同行的数据多是不同的,即一个行可能有多个版本.是否听起来难以想象呢?数据库
原来,为了实现mvcc, innodb对每一行都加上了两个隐含的列,其中一列存储行被更新的”时间”,另一列存储行被删除的”时间”. 可是innodb存储的并非绝对的时间,而是与时间对应的数据库系统的版本号,每当一个事务开始的时候,innodb都会给这个事务分配一个递增的版本号,因此版本号也能够被认为是事务号.对于每个”查询”语句,innodb都会把这个查询语句的版本号同这个查询语句遇到的行的版本号进行对比,而后结合不一样的事务隔离等级,来决定是否返回该行.并发
下面分别以select、delete、 insert、 update语句来讲明:mvc
首先明确,事务越晚开始 ,其事务号越大。性能
1) SELECT大数据
对于select语句,只有同时知足了下面两个条件的行,才能被返回:优化
•行的被修改版本号小于或者等于该事务号spa
•行的被删除版本号要么没有被定义,要么大于事务的版本号:行的删除版本号若是没有被定义,说明该行没有被删除过;若是删除版本号大于当前事务的事务号,说明该行是被该事务后面启动的事务删除的,因为是repeatable read隔离等级,后开始的事务对数据的影响不该该被先开始的事务看见,因此该行应该被返回.索引
2) INSERT
对新插入的行,行的更新版本被修改成该事务的事务号
3) DELETE
对于删除,innodb直接把该行的被删除版本号设置为当前的事务号,至关于标记为删除,而不是实际删除
4) UPDATE
在更新行的时候,innodb会把原来的行复制一份到回滚段中,并把当前的事务号做为该行的更新版本
上述策略的结果就是,在读取数据的时候,innodb几乎不用得到任何锁, 每一个查询都经过版本检查,只得到本身须要的数据版本,从而大大提升了系统的并发度.
这种策略的缺点是,为了实现多版本,innodb必须对每行增长相应的字段来存储版本信息,同时须要维护每一行的版本信息,并且在检索行的时候,须要进行版本的比较,于是下降了查询的效率;innodb还必须按期清理再也不须要的行版本,及时回收空间,这也增长了一些开销
INNODB支持并实现了ISO标准的4个事务隔离等级,即 READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.
1) READ UNCOMMITTED (能够读未提交的): 查询能够读取到其余事务正在修改的数据,即便其余事务的修改尚未提交.这种隔离等级没法避免脏读.
2) READ COMMITTED(只能够读已经提交的):其余事务对数据库的修改,只要已经提交,其修改的结果就是可见的,与这两个事务开始的前后顺序无关.这种隔离等级避免了脏读,可是没法实现可重复读,甚至有可能产生幻读.
3) REPEATABLE READ(可重复读):比read committed更进了一步,它只能读取在它开始以前已经提交的事务对数据库的修改,在它开始之后,全部其余事务对数据库的修改对它来讲均不可见.从而实现了可重复读,可是仍有可能幻读
4) SERIALIZABLE(可串行化):这是事务隔离等级的最高级别.其实现原理就是对于全部的query,即便是查询,也会加上读锁,避免其余事务对数据的修改.因此它成功的避免了幻读.可是代价是,数据库系统的并发处理能力大大下降,因此它不会被用到生产系统中.
咱们对MVCC和标准事务隔离等级有所了解之后,再结合实例来看看其具体表现吧.
MVCC因为其实现原理,只支持read committed和repeatable read隔离等级,
在不一样的事务中,即便执行相同的语句,获得的结果也可能不同,开启事务的时间不一样,获得的结果也可能不一样,由于每行数据都存在多版本的记录,事务老是能够看到自身对数据库数据的修改,当全部的事务都提交以后,它们看到的结果是一致的。这就是由MVCC控制的。MVCC就是经过事务发生的不一样的时间点,与数据行的版原本进行对比,从而取回与事务开始的时间点相一致的数据,来实现非阻塞的一致读。
由于innodb采用了MVCC技术,对于相同的行,可能同时存在多个版本,innodb必须根据查询的时间来过滤掉一些行,才能得出结果,必然要执行全表扫描,而全表扫描是很是耗时的.对于myisam的表,任何行都只有一个版本,mysql甚至不须要扫描就能够直接返回精确的统计结果,咱们用explain也能够看到,对于myisam的表,执行select count(*)的时候,mysql显示” Select tables optimized away”,查询直接被优化了;而对于innodb的表,多是全表扫描,也多是”using index”,总之,速度确定会比myisam的表慢不少.
若是你在数据库中执行了大事务, innodb就会把被修改数据的前映像存放到称为回滚段的公共表空间中,并且对于索引和表中的行的多个版本,若是innodb来不及purge,或者这些行由于要提供一致读而不能被purge,就会占用愈来愈多的空间,甚至有可能短期撑爆你的硬盘.因此应用程序中须要合理控制事务的大小。
禁用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.
在使用myisam引擎的状况下,定长表虽然可能占用较多的存储空间,可是它会加快检索和全表扫描的速度,此时适合选用char的列,而对于表中的变长的列,能够采用分表的方法把变长的列拆分出去,提升定长表的检索性能.而若是使用的是innodb的引擎,因为innodb的mvcc策略的实施,char数据类型相对于varchar类型几乎没有任何优点,反而varchar列可能节省更多的存储空间,建议使用varchar数据类型.