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

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

实现原理:

------------------------------------------------------------------------------------------> 时间轴

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

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

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

R:read a (T1)

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会为每一行添加两个字段,分别表示该行创建的版本删除的版本,填入的是事务的版本号,这个版本号随着事务的创建不断递增。innodb MVCC主要是为Repeatable-Read事务隔离级别做的。在此隔离级别下,A、B客户端所示的数据相互隔离,互相更新不可见,在Repeatable-Read的隔离级别下,具体各种数据库操作的实现:

SELECT

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

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

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

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

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(可串行化)则会对事务串行化执行,即对表加锁。

最新文章

  1. 在Asp.Net MVC 中如何用JS访问Web.Config中appSettings的值
  2. 为Asp.net WebApi 添加跨域支持
  3. http://jingyan.baidu.com/article/fcb5aff78e6a48edab4a7146.html
  4. 今天同事给介绍了一个LINQ的工具,LINQPad
  5. 简单易懂的现代魔法——Play Framework攻略4
  6. Look and say numbers
  7. VTK中国文字显示和简单的医疗图像浏览软件
  8. c# 【MVC】WebApi设置返回Json
  9. EL 快速开始
  10. SQLAlchemy——获取某列为空的数据
  11. Linux:编译安装boost 1.69库
  12. [转]asp+oracle分页
  13. ADO.Net 数据库修改
  14. CSRF攻击详解
  15. SQL与NoSQL的CRUD对照
  16. 002-字段不为null
  17. 「Python」_init_理解与学习
  18. VMware Workstation内存不足问题的解决!
  19. PS 证件照换颜色
  20. java网络通信:HTTP协议 之 Sessions与Cookies

热门文章

  1. Django--母版
  2. JavaScript---Bom树的操作,内置方法和内置对象(window对象,location对象,navigator对象,history对象,screen对象)
  3. ES6 对象解构赋值(浅拷贝 VS 深拷贝)
  4. springboot如何读取配置文件中的参数(例如:application-consts.properties) 又结合maven读取配置文件的顺序
  5. SVM 实现多分类思路
  6. Centos6.5配置防火墙
  7. linux开发中常用的命令及技巧(连载)
  8. 纯数据结构Java实现(11/11)(散列)
  9. PHP、asp、aspx、JSP一句话
  10. 一个兼容IE7\IE8,H5的多功能视频播放器,H5视频播放器兼容Flash视频播放器