也谈乐观并发控制(转)

add by zhj: 本文主要谈的是乐观并发控制,虽然乐观并发控制不太适用于并发写冲突很频繁的场景下,因为这样会导致事务回滚,需要用户重试retry,

但是如果不用乐观并发控制的话,貌似也没有其它什么好的办法了,悲观锁并不能解决更新丢失的问题,比如本文中的例子,我们也可以想想Git

遇到这种情况时是怎么处理的,其实Git也会像本文一样处理。为什么说悲观锁也不能完全解决更新丢失的问题呢?我们看下面的例子,两个用户

张三,李四,他们两人可以更新同一条数据库记录,假设记录为(sex,age) = (‘male’, 25),下面的操作之后,李四的更新还是丢失了。这个例子其实

跟下面讲的甲乙丙的例子差不多,都是会导致更新丢失的。解决办法就是用乐观并发控制。


时间


用户张三


用户李四


T1


通过客户端查看记录

 

T2


服务端收到请求后,返回(‘male’, 25)


T3

 
通过客户端查看记录


T4

 
服务端收到请求后,返回(‘male’, 25)


T5


在客户端编辑记录为(‘male’, 30),并提交服务端


T6


服务端收到后更新成功


T7


在客户端编辑记录为(‘female’, 25),并提交服务端


T8


服务端收到后更新成功


T9

 

另,本文作者认为乐观并发控制与MVCC是一回事,在此,我个人的观点还是乐观并发控制完全无锁,而MVCC一般与锁机制结合使用,至少在MySQL中是这样的,当修改数据时,会加X锁。

原文:http://www.jayxu.com/2012/03/13/13326/

今天酷壳上发布了一篇网友投稿的讨论MVCC的文章。写得很浅显,很明白。MVCC是每个接触数据库尤其是分布式互联网应用的开发人员应知应会的内容,而架构师更应该知道如何在悲观锁和乐观锁之间进行平衡与选择,这里不做展开,只想补充以下内容,来自于之前和现在的项目经验:

乐观并发控制在web应用还有一种应用场景就是在前端页面事务无法控制到的位置通过version检查避免脏数据的覆盖操作。比如在悲观锁环境下,当多个用户在各自的浏览器上修改同一份数据的不同域时,由于事务延伸不到客户的浏览器上,因此当他们提交时,服务器、数据库会认为是多份独立的事务提交,将相继全部成功,最终导致最后提交的有效,前几次提交的数据都被最后一次提交的数据覆盖,形象一点:

数据库原始数据

+----+------+--------+-----+--------+

| id | name | gender | age | height |

+----+------+--------+-----+--------+

| 1  | Jay  | male   | 29  | 1.88   |

+----+------+--------+-----+--------+

此时三个用户都打开了“编辑用户信息”页面,拿到了相同的数据,但是

- 甲将性别改成“unknown”
- 乙将年龄改成28
- 丙将身高改成1.85
假设他们依次提交,最终的结果将是

+----+------+--------+-----+--------+

| id | name | gender | age | height |

+----+------+--------+-----+--------+

| 1  | Jay  | male   | 29  | 1.85   |

+----+------+--------+-----+--------+

丙的提交生效,其他人的修改被脏数据覆盖

如果使用乐观锁version机制,情况会有很大不同

数据库原始数据

+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | male   | 29  | 1.88   | 1       |

+----+------+--------+-----+--------+---------+

还是按照上面的场景修改数据,还是按照上面的顺序提交,服务器会将version字段返回至客户端,客户端提交时也会带上version信息:

- 甲提交,数据更新为

+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | unknown| 29  | 1.88   | 2       |

+----+------+--------+-----+--------+---------+

-
乙提交,由于version已经更新为2,数据库认为有冲突(其实MVCC的version和传统的VCS是一样的,会有冲突发生),更新失败,抛异常,服务器提示“数据已被更新,请刷新后重试”,乙刷新获得甲更新后的数据,修改年龄为28,提交,数据更新为

+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | unknown| 28  | 1.88   | 3       |

+----+------+--------+-----+--------+---------+

- 丙提交,和乙一样,被告知“数据已被更新,请刷新后重试”,刷新,更新,提交,数据更新为

+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | unknown| 28  | 1.85   | 4       |

+----+------+--------+-----+--------+---------+

这应该是大家所预期的结果。而如果使用悲观锁要解决这个问题,只能在服务器端做额外的处理,辨识此次更新的字段,然后更新前查询一次数据库,获得最新的数据,仅更新发生变化的字段,然后提交。或者,页面就应该避免多字段大表单的提交,把每次可更新的内容进行拆分,比如现在几乎所有的SNS的用户信息更新页面都会按“基本信息”、“学校信息”、“就业信息”等分段保存(当然这么做的原因有很多,比如用户体验,对于一个长长的大表格,用户更能接受“少量多次”的提交方式,而且如果在保存前出现浏览器崩溃、死机等意外情况,未保存而需要重填的数据量不会很大。脏数据覆盖问题只是其中一个)

最后补充两点实战经验

- 如果需要在Hibernate中使用MVCC,直接在entity中定义一个int类型的字段,然后使用@Version修饰该字段
-
在真实环境中,若使用MVCC并且允许用户重复更新,每次页面提交后,应该将数据库最新的version值传回客户端。如果使用REST,直接放在response的header里是一种可行的做法

时间: 2024-10-18 15:54:28

也谈乐观并发控制(转)的相关文章

浅谈数据库并发控制 - 锁和 MVCC

在学习几年编程之后,你会发现所有的问题都没有简单.快捷的解决方案,很多问题都需要权衡和妥协,而本文介绍的就是数据库在并发性能和可串行化之间做的权衡和妥协 - 并发控制机制. 如果数据库中的所有事务都是串行执行的,那么它非常容易成为整个应用的性能瓶颈,虽然说没法水平扩展的节点在最后都会成为瓶颈,但是串行执行事务的数据库会加速这一过程:而并发(Concurrency)使一切事情的发生都有了可能,它能够解决一定的性能问题,但是它会带来更多诡异的错误. 引入了并发事务之后,如果不对事务的执行进行控制就会

Elasticsearch的乐观并发控制和分片管理(更新中)

1. 乐观并发控制 首先,需要明确Elasticsearch的三个特性: 分布式的:当文档创建,删除或更新的时候,新版本的文档必须被复制到集群中的其他节点: 并发的:这些复制请求将被并行发送: 异步的:这些复制请求到达目的地的顺序是乱的. 因此,Elasticsearch需要保证文档的旧版本不会覆盖新版本.Elasticserch通过_version字段来确保并更以正确的顺序得到执行.如果旧版本的文档在新版本之后到达,它可以被简单的忽略. 2. 分片管理 2.1 动态索引 采用Luence的pe

Entity Framework 乐观并发控制

一.背景 我们知道,为了防止并发而出现脏读脏写的情况,可以使用Lock语句关键字,这属于悲观并发控制的一种技术,,但在分布式站点下,锁的作用几乎不存在,因为虽然锁住了A服务器的实例对象,但B服务器上的锁是不知道的A服务器上锁的情况的,所以,面对分布式站点.单一数据库这种架构,我们可以使用EntityFramework的乐观并发控制来解决这个问题,EF对并发控制有不管控和乐观并发控制两种,默认情况是不管控,但EF不支持悲观并发. lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区.

MySQL 的乐观并发控制Optimistic concurrency control

默认情况下, MySQL的Innodb事务隔离级别是重复读 repeatable read, SELECT @@GLOBAL.tx_isolation, @@tx_isolation;REPEATABLE-READ    REPEATABLE-READ 进行以下测试, 同时开两个session, S1 和 S2, 都将autocommit关掉 set autocommit=0; 测试使用的是一张简单的表, 只有一行数据 CREATE TABLE `t1` ( `v1` tinyint(2) NO

EF6 Code First 系列 (四):SQLite的DropCreateDatabaseIfModelChanges和乐观并发控制

没什么好说的,能支持DropCreateDatabaseIfModelChanges和RowVersion的Sqlite谁都想要.EntityFramework7正在添加对Sqlite的支持,虽然EF7不知道猴年马月才能完成正式版,更不知道MySql等第三方提供程序会在什么时候跟进支持,但是EF7中的确出现了Sqlite的相关代码.Sqlite支持EF6的CodeFirst,只是不支持从实体生成数据库,估计有很多人因为这个原因放弃了使用它.现在SQLite.CodeFirst的简单实现可以让我们

并发控制:(二)乐观锁 悲观锁

悲观锁:(pessimistic locking):假定:发生冲突的概率比较高,实现:在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking).这样其他事务如果想操作该记录,需要等待锁的释放特点: 当并发量较大,频繁访问时,等待时间较长,并发访问性不好例如: java的synchronized,SqlServer页级锁,Oracle行级锁 乐观锁:(optimistic locking)假设:发生冲突的概率比较低实现:在提交对记录的更改时才将对象锁住,提交前需要检查

[转]数据库并发控制 乐观锁,悲观锁

在数据库中,并发控制有乐观锁和悲观锁之间,什么时候用乐观锁比较好什么时候用悲观锁比较好? 实际生产环境里边,如果并发量不大,完全可以使用悲观锁定的方法,这种方法使用起来非常方便和简单.但是如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以就要选择乐观锁定的方法. 悲观锁假定其他用户企图访问或者改变你正在访问.更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁.悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长

浅谈sql优化

问题的发现:      菜鸟D在工作的时候发现项目的sql语句很怪,例如 : select a.L_ZTBH, a.D_RQ, a.VC_BKDM, (select t.vc_name from tbkxx t where t.vc_code = a.vc_bkdm) vc_name, a.VC_BZ, a.L_SCLB, a.EN_ZS, a."ROWID", s."ROWID",--冗余列 decode(nvl(a.l_cjsl, 0), 0, 0, round

[数据库事务与锁]详解七: 深入理解乐观锁与悲观锁

注明: 本文转载自http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性. 乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段. 无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想.其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像memcache.hibernate.