MVVC与乐观锁和悲观锁

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

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

实现原理: sql

------------------------------------------------------------------------------------------> 时间轴数据库

|-------R(T1)-----|并发

|-----------U(T2)-----------|post

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

R:read a (T1)优化

U:a = 2    (T2)spa

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

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

上面说了一堆的虚的理论,下面来点干活,看一下mysql的innodb引擎是如何实现MVCC的。innodb会为每一行添加两个字段,分别表示该行建立的版本删除的版本,填入的是事务的版本号,这个版本号随着事务的建立不断递增。innodb MVCC主要是为Repeatable-Read事务隔离级别作的。在此隔离级别下,A、B客户端所示的数据相互隔离,互相更新不可见,在Repeatable-Read的隔离级别下,具体各类数据库操做的实现:

SELECT

  InnoDB会根据如下两个条件检查每行记录:

  一、InnoDB只查找版本小于或等于当前事务版本的数据行,这样能够确保事务读取的行,是在事务开始前就已经存在的,或者是事务自身插入或者修改过的数据。

  二、行的删除版本要么未定义,要么大于当前事务的版本。这能够确保事务读取到的行,在事务开始前未被删除。

  只有符合上述两个条件的记录,才能返回作为查询结果。

INSERT

  InnoDB为新插入的每一行保存当前系统版本号做为行版本号。

DELETE

  InnoDB为删除的每一行保存当前系统版本号做为行删除标识。

UPDATE

  InnoDB为插入一行新记录,保存当前系统版本号做为行版本号,同时保存当前系统版本号到原来的行做为删除标识。

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

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

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

优缺点:

  保存这两个额外的系统版本号,使大多数读操做均可以不用加锁。这样设计使得读数据操做很简单,性能很好。而且也能保证只会读取到符合标准的行。不足之处是每行记录都须要额外的存储空间,须要作更多的检查工做,以及一些额外的维护工做。

innodb 和postgre实现:

  • postgres 是严格地无锁,对写操做也是乐观并发控制;在表中保存同一行数据记录的多个不一样版本,每次写操做,都是建立,而回避更新;在事务提交时,按版本号检查当前事务提交的数据是否存在写冲突,则抛异常告知用户,回滚事务;
  • innodb 则只对读无锁,写操做还是上锁的悲观并发控制,这也意味着,innodb 中只能见到因死锁和不变性约束而回滚,而见不到由于写冲突而回滚;不像 postgres 那样对数据修改在表中建立新纪录,而是每行数据只在表中保留一份,在更新数据时上行锁,同时将旧版数据写入 undo log;表和 undo log 中行数据都记录着事务ID,在检索时,只读取来自当前已提交的事务的行数据;

MVCC有效范围:

  MVCC只在REPEATABLE READ(可重复读)和READ COMMITTED(提交读)两个隔离级别下工做。其余两个隔离级别都和MVCC不兼容。READ UNCOMMITTED(未提交读)老是读取最新的数据行,而SERIALIZBLE(可串行化)则会对事务串行化执行,即对表加锁。
相关文章
相关标签/搜索