V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MySQL 5.5 Community Server
MySQL 5.6 Community Server
Percona Configuration Wizard
XtraBackup 搭建主从复制
Great Sites on MySQL
Percona
MySQL Performance Blog
Severalnines
推荐管理工具
Sequel Pro
phpMyAdmin
推荐书目
MySQL Cookbook
MySQL 相关项目
MariaDB
Drizzle
参考文档
http://mysql-python.sourceforge.net/MySQLdb.html
ajianrelease
V2EX  ›  MySQL

乐观锁与悲观锁各自适用场景是什么?

  •  
  •   ajianrelease · 2015-04-09 14:08:48 +08:00 · 12524 次点击
    这是一个创建于 3515 天前的主题,其中的信息可能已经有所发展或是发生改变。
    悲观锁貌似没法解决更新丢失的问题。见下面的例子,两个用户张三,李四,他们两人可以更新同一条数据库记录。假设记录为(sex,age) = (‘male’, 25)。在张三的查询和修改的时间间隔内,李四更新了记录,而张三对这种情况不知情,最后导致李四的更新丢失了。无论加不加悲观锁,都解决不了这种问题。我的问题是

    1)对于这种并发写冲突,是不是只能用乐观锁(给表加一个版本号字段)来防止更新丢失?
    2)那select ... for update这种悲观锁在什么场景下使用,悲观锁的使用应该是为了解决并发写冲突,但貌似它又不能解决更新丢失问题,感觉有点鸡肋啊,亦或是我理解有误.

    有一篇相关文章,参见http://www.douban.com/note/204830640/

    第 1 条附言  ·  2015-04-09 20:28:56 +08:00
    大家能否举出一个应该使用悲观锁,而不使用乐观锁的例子啊
    19 条回复    2015-04-11 05:23:42 +08:00
    cloudhunter
        1
    cloudhunter  
       2015-04-09 14:55:54 +08:00   ❤️ 1
    1)可以看看事务隔离级别的定义
    2)select ... for update,我们用它主要为了保证业务的强一致性,对某一行的记录加锁,然后让分布式的更新或读取变成串行。
    bsbgong
        2
    bsbgong  
       2015-04-09 15:03:54 +08:00   ❤️ 4
    两种锁都是为了解决并发情况下的写冲突。
    用那种机制,取决于你的场景。要记住,总目标和原则都是:提高写效率。
    悲观锁是early lock,乐观锁是late lock。因此:
    1. 对于数据更新频繁的场合,悲观锁效率更高
    2. 对于数据更新不频繁的场合,乐观锁效率更高
    caoyue
        3
    caoyue  
       2015-04-09 15:25:35 +08:00   ❤️ 2
    我的理解是,数据库只能保证操作的结果正确而不能替你做业务逻辑上的「正确」
    题目所说的场景可以将读和写作为一个事务,同时选择合适的隔离级别。
    当然更高的隔离级别也意味着更低的并发
    如楼上所说,两种方式开销都不小,具体怎么实现取决于楼主的场景
    ajianrelease
        4
    ajianrelease  
    OP
       2015-04-09 15:57:51 +08:00
    @cloudhunter
    @bsbgong
    @caoyue
    上面的场景怎么将读和写做为一个事务呢?貌似没法做到吧。
    第一步:用户A先调用服务端的读api,服务端读后就返回给了用户,即使读的时候加锁,那返回给用户响应时也已经把锁释放掉了。
    第二步:用户A收到响应后,编辑数据,然后再调用服务端的写api,完成更新。
    如果在这两步之间,用户B更新了数据,那用户A执行第二步后,B的更新就丢掉了。
    以知乎为例,知乎上的问题是所有人都能编辑的,如果有两个用户像上面这么操作,那用户B的编辑就被干掉了
    200cc
        5
    200cc  
       2015-04-09 16:36:09 +08:00
    这个业务的目的是什么?
    (1) 只保留A的修改
    (2) 只保留B的修改
    (3) A可以覆盖B的修改. 但在日志里面能体现A,B的修改记录.
    caoyue
        6
    caoyue  
       2015-04-09 16:50:50 +08:00   ❤️ 1
    @ajianrelease
    对数据库了解不多,个人感觉哈 :)
    1. 一般场景下竞争冲突的时候不多。除非完全不可接受的情况,使用乐观锁比较常见。
    2. 有些 ORM 是内建乐观锁支持的
    3. 这种情况下要使用悲观锁,可以在更新前做一次 select for update
    4. 互联网应用有时候对数据一致性的要求没有那么高
    5. 有些场景会用到多级缓存缓解这些问题
    6. 如果是数据正确性很重要比如金融业务之类,加上操作日志是更保险的做法
    ajianrelease
        7
    ajianrelease  
    OP
       2015-04-09 17:06:32 +08:00
    @200cc 目的是他们两个的更改都要保留。即最后结果希望是(‘female’, 30),也就是说A在更新时要提示他其它用户已经更改过了,所以需要乐观锁。
    bsbgong
        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的更新之上。
    ryd994
        9
    ryd994  
       2015-04-09 19:28:51 +08:00
    @ajianrelease 关于4楼,我的理解是:
    如果加悲观锁的话,张三读完之后就不释放锁,直到写入完成再释放。也就是说后来的李四从第一步就不能开始。其实也能解决问题,但是性能就很悲剧了。
    ajianrelease
        10
    ajianrelease  
    OP
       2015-04-09 20:25:23 +08:00
    @bsbgong 多谢,回答的很详细。既然悲观锁在数据更新方面不如乐观锁效率高,那全用乐观锁就行了呗,悲观锁还有什么用呢?能否举出一个使用悲观锁而不使用乐观锁的例子啊
    ajianrelease
        11
    ajianrelease  
    OP
       2015-04-09 20:27:04 +08:00
    @ryd994 恩,你说的这种方法貌似不适用我举的这个例子。因为张三读完后,服务端要给他返回数据的,返回数据时,事务已经结束了,锁不可能还在啊。
    clino
        12
    clino  
       2015-04-09 20:40:04 +08:00
    @bsbgong "对于数据更新频繁的场合,悲观锁效率更高" 这个不理解,感觉应该是也是乐观锁效率更高吧?
    ryd994
        13
    ryd994  
       2015-04-09 21:42:33 +08:00 via Android
    @ajianrelease 用两种命令,一种是读,一种是读加锁。没人规定返回数据了服务器程序就要结束。用fcgi的话完全可以自己做一个锁池解决。当然,这样蛮考验性能的。
    mfaner
        14
    mfaner  
       2015-04-10 11:12:12 +08:00
    客户端只提交数据变化应该就能用悲观锁了吧,比如转帐。
    你这种情况悲观锁不是没锁住吗,除非事务边界延伸到客户端...好吧其实我什么都不懂。
    snnn
        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,都能用来实现可串行化的隔离度。
    dingyaguang117
        16
    dingyaguang117  
       2015-04-10 17:55:28 +08:00   ❤️ 1
    加锁不就是为了防止更新丢失的么, 为什么会防止不了呢
    悲观锁:从一开始都不让其他事务读
    乐观锁:只要有人在我读之后改过,我就放弃了
    这两种都能防止呀
    ajianrelease
        17
    ajianrelease  
    OP
       2015-04-11 01:02:23 +08:00
    @ryd994 奥,可以这样啊,学习了。那这样感觉很不靠谱啊,如果用户读加锁,服务端返回数据后还保持锁,那用户不修改数据,那锁不就一直在那吗?现实中,你使用过锁池这种东西吗?或者你见面其它人用吗?
    ajianrelease
        18
    ajianrelease  
    OP
       2015-04-11 01:08:23 +08:00
    @snnn 说实话,你说的对我来说有点高深了,我得好好理解一下
    ryd994
        19
    ryd994  
       2015-04-11 05:23:42 +08:00 via Android
    @ajianrelease 我没用过,但是见过有人用
    如果怕用户改一半跑了可以做超时
    但是无论怎么样,在与人交互的情况下,用悲观锁的性能肯定是很悲观的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2819 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 14:07 · PVG 22:07 · LAX 06:07 · JFK 09:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.