mvcc

1、 事务是指对系统进行的一组操做,为了保证系统的完整性,事务须要具备ACID特性,具体以下:
1. 原子性(Atomic)
     一个事务包含多个操做,这些操做要么所有执行,要么全都不执行。实现事务的原子性,要支持回滚操做,在某个操做失败后,回滚到事务执行以前的状态。
     回滚其实是一个比较高层抽象的概念,大多数DB在实现事务时,是在事务操做的数据快照上进行的(好比,MVCC),并不修改实际的数据,若是有错并不会提交,因此很天然的支持回滚。
     而在其余支持简单事务的系统中,不会在快照上更新,而直接操做实际数据。能够先预演一边全部要执行的操做,若是失败则这些操做不会被执行,经过这种方式很简单的实现了原子性。
2. 一致性(Consistency)
     一致性是指事务使得系统从一个一致的状态转换到另外一个一致状态。事务的一致性决定了一个系统设计和实现的复杂度。事务能够不一样程度的一致性:
     强一致性:读操做能够当即读到提交的更新操做。
     弱一致性:提交的更新操做,不必定当即会被读操做读到,此种状况会存在一个不一致窗口,指的是读操做能够读到最新值的一段时间。
     最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其余事务更新一样的值的话,最终全部的事务都会读到以前事务更新的最新值。若是没有错误发生,不一致窗口的大小依赖于:通讯延迟,系统负载等。
     其余一致性变体还有:
     单调一致性:若是一个进程已经读到一个值,那么后续不会读到更早的值。
     会话一致性:保证客户端和服务器交互的会话过程当中,读操做能够读到更新操做后的最新值。
3. 隔离性(Isolation)
     并发事务之间互相影响的程度,好比一个事务会不会读取到另外一个未提交的事务修改的数据。在事务并发操做时,可能出现的问题有:
     脏读:事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,若是事务A提交失败,事务B读到的就是脏数据。
     不可重复读:在同一个事务中,对于同一份数据读取到的结果不一致。好比,事务B在事务A提交前读到的结果,和提交后读到的结果可能不一样。不可重复读出现的缘由就是事务并发修改记录,要避免这种状况,最简单的方法就是对要修改的记录加锁,这回致使锁竞争加重,影响性能。另外一种方法是经过MVCC能够在无锁的状况下,避免不可重复读。
     幻读:在同一个事务中,同一个查询屡次返回的结果不一致。事务A新增了一条记录,事务B在事务A提交先后各执行了一次查询操做,发现后一次比前一次多了一条记录。幻读是因为并发事务增长记录致使的,这个不能像不可重复读经过记录加锁解决,由于对于新增的记录根本没法加锁。须要将事务串行化,才能避免幻读。
     事务的隔离级别从低到高有:
     Read Uncommitted:最低的隔离级别,什么都不须要作,一个事务能够读到另外一个事务未提交的结果。全部的并发事务问题都会发生。
     Read Committed:只有在事务提交后,其更新结果才会被其余事务看见。能够解决脏读问题。
     Repeated Read:在一个事务中,对于同一份数据的读取结果老是相同的,不管是否有其余事务对这份数据进行操做,以及这个事务是否提交。能够解决脏读、不可重复读。
     Serialization:事务串行化执行,隔离级别最高,牺牲了系统的并发性。能够解决并发事务的全部问题。
     一般,在工程实践中,为了性能的考虑会对隔离性进行折中。
4. 持久性(Durability)
     事务提交后,对系统的影响是永久的。java

2、mvccmysql

在并发读写数据库时,读操做可能会不一致的数据(脏读)。为了不这种状况,须要实现数据库的并发访问控制,最简单的方式就是加锁访问。因为,加锁会将读写操做串行化,因此不会出现不一致的状态。可是,读操做会被写操做阻塞,大幅下降读性能。在Java concurrent包中,有copyonwrite系列的类,专门用于优化读远大于写的状况。而其优化的手段就是,在进行写操做时,将数据copy一份,不会影响原有数据,而后进行修改,修改完成后原子替换掉旧的数据,而读操做只会读取原有数据。经过这种方式实现写操做不会阻塞读操做,从而优化读效率。而写操做之间是要互斥的,而且每次写操做都会有一次copy,因此只适合读大于写的状况。sql

MVCC的原理与copyonwrite相似,全称是Multi-Version Concurrent Control,即多版本并发控制。在MVCC协议下,每一个读操做会看到一个一致性的snapshot,而且能够实现非阻塞的读。MVCC容许数据具备多个版本,这个版本能够是时间戳或者是全局递增的事务ID,在同一个时间点,不一样的事务看到的数据是不一样的。数据库

实现原理: 服务器

------------------------------------------------------------------------------------------> 时间轴并发

|-------R(T1)-----|mvc

|-----------U(T2)-----------|性能

如上图,假设有两个并发操做R(T1)和U(T2),T1和T2是事务ID,T1小于T2,系统中包含数据a = 1(T1),R和W的操做以下:优化

R:read a (T1).net

U:a = 2    (T2)

R(读操做)的版本T1表示要读取数据的版本,而以后写操做才会更新版本,读操做不会。在时间轴上,R晚于U,而因为U在R开始以后提交,因此对于R是不可见的。因此,R只会读取T1版本的数据,即a = 1。

因为在update操做提交以前,不能影响已有数据的一致性,因此不会改变旧的数据,update操做会被拆分红insert + delete。须要标记删除旧的数据,insert新的数据。只有update提交以后,才会影响后续的读操做。而对于读操做并且,只能读到在其以前的全部的写操做,正在执行中的写操做对其是不可见的。

上面说了一堆的虚的理论,下面来点干活,看一下MySQL的innodb引擎是如何实现MVCC的。innodb会为每一行添加两个字段,分别表示该行建立的版本删除的版本,填入的是事务的版本号,这个版本号随着事务的建立不断递增。在repeated read的隔离级别(事务的隔离级别请看这篇文章)下,具体各类数据库操做的实现:

select:知足如下两个条件innodb会返回该行数据:(1)该行的建立版本号小于等于当前版本号,用于保证在select操做以前全部的操做已经执行落地。(2)该行的删除版本号大于当前版本或者为空。删除版本号大于当前版本意味着有一个并发事务将该行删除了。

insert:将新插入的行的建立版本号设置为当前系统的版本号。

delete:将要删除的行的删除版本号设置为当前系统的版本号。

update:不执行原地update,而是转换成insert + delete。将旧行的删除版本号设置为当前版本号,并将新行insert同时设置建立版本号为当前版本号。

其中,写操做(insert、delete和update)执行时,须要将系统版本号递增。

因为旧数据并不真正的删除,因此必须对这些数据进行清理,innodb会开启一个后台线程执行清理工做,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫作purge。

经过MVCC很好的实现了事务的隔离性,能够达到repeated read级别,要实现serializable还必须加锁

相关文章
相关标签/搜索