1
cloudhunter 2015-04-09 14:55:54 +08:00 1
1)可以看看事务隔离级别的定义
2)select ... for update,我们用它主要为了保证业务的强一致性,对某一行的记录加锁,然后让分布式的更新或读取变成串行。 |
2
bsbgong 2015-04-09 15:03:54 +08:00 4
两种锁都是为了解决并发情况下的写冲突。
用那种机制,取决于你的场景。要记住,总目标和原则都是:提高写效率。 悲观锁是early lock,乐观锁是late lock。因此: 1. 对于数据更新频繁的场合,悲观锁效率更高 2. 对于数据更新不频繁的场合,乐观锁效率更高 |
3
caoyue 2015-04-09 15:25:35 +08:00 2
我的理解是,数据库只能保证操作的结果正确而不能替你做业务逻辑上的「正确」
题目所说的场景可以将读和写作为一个事务,同时选择合适的隔离级别。 当然更高的隔离级别也意味着更低的并发 如楼上所说,两种方式开销都不小,具体怎么实现取决于楼主的场景 |
4
ajianrelease OP @cloudhunter
@bsbgong @caoyue 上面的场景怎么将读和写做为一个事务呢?貌似没法做到吧。 第一步:用户A先调用服务端的读api,服务端读后就返回给了用户,即使读的时候加锁,那返回给用户响应时也已经把锁释放掉了。 第二步:用户A收到响应后,编辑数据,然后再调用服务端的写api,完成更新。 如果在这两步之间,用户B更新了数据,那用户A执行第二步后,B的更新就丢掉了。 以知乎为例,知乎上的问题是所有人都能编辑的,如果有两个用户像上面这么操作,那用户B的编辑就被干掉了 |
5
200cc 2015-04-09 16:36:09 +08:00
这个业务的目的是什么?
(1) 只保留A的修改 (2) 只保留B的修改 (3) A可以覆盖B的修改. 但在日志里面能体现A,B的修改记录. |
6
caoyue 2015-04-09 16:50:50 +08:00 1
@ajianrelease
对数据库了解不多,个人感觉哈 :) 1. 一般场景下竞争冲突的时候不多。除非完全不可接受的情况,使用乐观锁比较常见。 2. 有些 ORM 是内建乐观锁支持的 3. 这种情况下要使用悲观锁,可以在更新前做一次 select for update 4. 互联网应用有时候对数据一致性的要求没有那么高 5. 有些场景会用到多级缓存缓解这些问题 6. 如果是数据正确性很重要比如金融业务之类,加上操作日志是更保险的做法 |
7
ajianrelease OP @200cc 目的是他们两个的更改都要保留。即最后结果希望是(‘female’, 30),也就是说A在更新时要提示他其它用户已经更改过了,所以需要乐观锁。
|
8
bsbgong 2015-04-09 18:32:36 +08:00
@ajianrelease
乐观锁是假定读取的数据,在写之前不会被更新。适用于数据更新不频繁的场景。 Dynamodb支持乐观锁。 悲观锁也是类似,mysql支持悲观锁。 当你执行select xx from xx where xx for update后,在另一个事务中如果对同一张表再次执行select xx from xx where xx for update,那么第二个事务会一直等到第一个事务结束才会被触发,也就是一直处于阻塞的状态,无法查询。可以看到在数据更新不频繁的时候,悲观锁效率很低。 相反,当数据更新频繁的时候,乐观锁的效率很低,因为基本上每次写的时候都要重复读写两次以上。 根据你的描述,应该使用乐观锁(加一个版本号字段)。 A更新成功之后,ver++ B在尝试更新的时候,发现欲更新的记录的的ver跟数据库对应记录的ver不一致。于是重新读取该记录,也就是A更新之后的记录。 至于重新读取之后是怎样提示用户,就是你UX的设计问题了,跟数据库这边无关。 A的更新是保存到了数据库的。B要再更新,必须基于A的更新之上。 |
9
ryd994 2015-04-09 19:28:51 +08:00
@ajianrelease 关于4楼,我的理解是:
如果加悲观锁的话,张三读完之后就不释放锁,直到写入完成再释放。也就是说后来的李四从第一步就不能开始。其实也能解决问题,但是性能就很悲剧了。 |
10
ajianrelease OP @bsbgong 多谢,回答的很详细。既然悲观锁在数据更新方面不如乐观锁效率高,那全用乐观锁就行了呗,悲观锁还有什么用呢?能否举出一个使用悲观锁而不使用乐观锁的例子啊
|
11
ajianrelease OP @ryd994 恩,你说的这种方法貌似不适用我举的这个例子。因为张三读完后,服务端要给他返回数据的,返回数据时,事务已经结束了,锁不可能还在啊。
|
13
ryd994 2015-04-09 21:42:33 +08:00 via Android
@ajianrelease 用两种命令,一种是读,一种是读加锁。没人规定返回数据了服务器程序就要结束。用fcgi的话完全可以自己做一个锁池解决。当然,这样蛮考验性能的。
|
14
mfaner 2015-04-10 11:12:12 +08:00
客户端只提交数据变化应该就能用悲观锁了吧,比如转帐。
你这种情况悲观锁不是没锁住吗,除非事务边界延伸到客户端...好吧其实我什么都不懂。 |
15
snnn 2015-04-10 13:05:56 +08:00
悲观锁是提前加锁,楼主是不是搞反了啊?
并发控制协议需要关注的3个问题: 冲突何时发生? 冲突如何被检测到? 冲突如何解决? 并发控制协议总的来说,可以分为乐观和悲观两种。 两种版本管理模式: 1、eager version management:直写型。必须要有undo log。 2、lazy version management:延迟更新。所有的修改在提交前都是针对当前事务私有的。 加锁的时机: ETL:encounter-time locking。事务第一次访问这个地址时加锁。 CTL:commit-time locking。事务提交时加锁。 ETL可以支持eager version management和lazy version management。 CTL只能支持lazy version management。不能支持eager version management是很显然的,因为如果直写,但是事务提交时才加锁,那就乱套了。 我们平时所说的悲观锁是指encounter-time locking。无论是ETL还是CTL,都能用来实现可串行化的隔离度。 |
16
dingyaguang117 2015-04-10 17:55:28 +08:00 1
加锁不就是为了防止更新丢失的么, 为什么会防止不了呢
悲观锁:从一开始都不让其他事务读 乐观锁:只要有人在我读之后改过,我就放弃了 这两种都能防止呀 |
17
ajianrelease OP @ryd994 奥,可以这样啊,学习了。那这样感觉很不靠谱啊,如果用户读加锁,服务端返回数据后还保持锁,那用户不修改数据,那锁不就一直在那吗?现实中,你使用过锁池这种东西吗?或者你见面其它人用吗?
|
18
ajianrelease OP @snnn 说实话,你说的对我来说有点高深了,我得好好理解一下
|
19
ryd994 2015-04-11 05:23:42 +08:00 via Android
|