› 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 · Apr 9, 2015 · 14050 views
    This topic created in 4197 days ago, the information mentioned may be changed or developed.
    悲观锁貌似没法解决更新丢失的问题。见下面的例子,两个用户张三,李四,他们两人可以更新同一条数据库记录。假设记录为(sex,age) = (‘male’, 25)。在张三的查询和修改的时间间隔内,李四更新了记录,而张三对这种情况不知情,最后导致李四的更新丢失了。无论加不加悲观锁,都解决不了这种问题。我的问题是

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

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

    Supplement 1  ·  Apr 9, 2015
    大家能否举出一个应该使用悲观锁,而不使用乐观锁的例子啊
    19 replies  •  2015-04-11 05:23:42 +08:00
    cloudhunter
        1
    cloudhunter  
       Apr 9, 2015   ❤️ 1
    1)可以看看事务隔离级别的定义
    2)select ... for update,我们用它主要为了保证业务的强一致性,对某一行的记录加锁,然后让分布式的更新或读取变成串行。
    bsbgong
        2
    bsbgong  
       Apr 9, 2015   ❤️ 4
    两种锁都是为了解决并发情况下的写冲突。
    用那种机制,取决于你的场景。要记住,总目标和原则都是:提高写效率。
    悲观锁是early lock,乐观锁是late lock。因此:
    1. 对于数据更新频繁的场合,悲观锁效率更高
    2. 对于数据更新不频繁的场合,乐观锁效率更高
    caoyue
        3
    caoyue  
       Apr 9, 2015   ❤️ 2
    我的理解是,数据库只能保证操作的结果正确而不能替你做业务逻辑上的「正确」
    题目所说的场景可以将读和写作为一个事务,同时选择合适的隔离级别。
    当然更高的隔离级别也意味着更低的并发
    如楼上所说,两种方式开销都不小,具体怎么实现取决于楼主的场景
    ajianrelease
        4
    ajianrelease  
    OP
       Apr 9, 2015
    @cloudhunter
    @bsbgong
    @caoyue
    上面的场景怎么将读和写做为一个事务呢?貌似没法做到吧。
    第一步:用户A先调用服务端的读api,服务端读后就返回给了用户,即使读的时候加锁,那返回给用户响应时也已经把锁释放掉了。
    第二步:用户A收到响应后,编辑数据,然后再调用服务端的写api,完成更新。
    如果在这两步之间,用户B更新了数据,那用户A执行第二步后,B的更新就丢掉了。
    以知乎为例,知乎上的问题是所有人都能编辑的,如果有两个用户像上面这么操作,那用户B的编辑就被干掉了
    200cc
        5
    200cc  
       Apr 9, 2015
    这个业务的目的是什么?
    (1) 只保留A的修改
    (2) 只保留B的修改
    (3) A可以覆盖B的修改. 但在日志里面能体现A,B的修改记录.
    caoyue
        6
    caoyue  
       Apr 9, 2015   ❤️ 1
    @ajianrelease
    对数据库了解不多,个人感觉哈 :)
    1. 一般场景下竞争冲突的时候不多。除非完全不可接受的情况,使用乐观锁比较常见。
    2. 有些 ORM 是内建乐观锁支持的
    3. 这种情况下要使用悲观锁,可以在更新前做一次 select for update
    4. 互联网应用有时候对数据一致性的要求没有那么高
    5. 有些场景会用到多级缓存缓解这些问题
    6. 如果是数据正确性很重要比如金融业务之类,加上操作日志是更保险的做法
    ajianrelease
        7
    ajianrelease  
    OP
       Apr 9, 2015
    @200cc 目的是他们两个的更改都要保留。即最后结果希望是(‘female’, 30),也就是说A在更新时要提示他其它用户已经更改过了,所以需要乐观锁。
    bsbgong
        8
    bsbgong  
       Apr 9, 2015
    @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  
       Apr 9, 2015
    @ajianrelease 关于4楼,我的理解是:
    如果加悲观锁的话,张三读完之后就不释放锁,直到写入完成再释放。也就是说后来的李四从第一步就不能开始。其实也能解决问题,但是性能就很悲剧了。
    ajianrelease
        10
    ajianrelease  
    OP
       Apr 9, 2015
    @bsbgong 多谢,回答的很详细。既然悲观锁在数据更新方面不如乐观锁效率高,那全用乐观锁就行了呗,悲观锁还有什么用呢?能否举出一个使用悲观锁而不使用乐观锁的例子啊
    ajianrelease
        11
    ajianrelease  
    OP
       Apr 9, 2015
    @ryd994 恩,你说的这种方法貌似不适用我举的这个例子。因为张三读完后,服务端要给他返回数据的,返回数据时,事务已经结束了,锁不可能还在啊。
    clino
        12
    clino  
       Apr 9, 2015
    @bsbgong "对于数据更新频繁的场合,悲观锁效率更高" 这个不理解,感觉应该是也是乐观锁效率更高吧?
    ryd994
        13
    ryd994  
       Apr 9, 2015 via Android
    @ajianrelease 用两种命令,一种是读,一种是读加锁。没人规定返回数据了服务器程序就要结束。用fcgi的话完全可以自己做一个锁池解决。当然,这样蛮考验性能的。
    mfaner
        14
    mfaner  
       Apr 10, 2015
    客户端只提交数据变化应该就能用悲观锁了吧,比如转帐。
    你这种情况悲观锁不是没锁住吗,除非事务边界延伸到客户端...好吧其实我什么都不懂。
    snnn
        15
    snnn  
       Apr 10, 2015
    悲观锁是提前加锁,楼主是不是搞反了啊?

    并发控制协议需要关注的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  
       Apr 10, 2015   ❤️ 1
    加锁不就是为了防止更新丢失的么, 为什么会防止不了呢
    悲观锁:从一开始都不让其他事务读
    乐观锁:只要有人在我读之后改过,我就放弃了
    这两种都能防止呀
    ajianrelease
        17
    ajianrelease  
    OP
       Apr 11, 2015
    @ryd994 奥,可以这样啊,学习了。那这样感觉很不靠谱啊,如果用户读加锁,服务端返回数据后还保持锁,那用户不修改数据,那锁不就一直在那吗?现实中,你使用过锁池这种东西吗?或者你见面其它人用吗?
    ajianrelease
        18
    ajianrelease  
    OP
       Apr 11, 2015
    @snnn 说实话,你说的对我来说有点高深了,我得好好理解一下
    ryd994
        19
    ryd994  
       Apr 11, 2015 via Android
    @ajianrelease 我没用过,但是见过有人用
    如果怕用户改一半跑了可以做超时
    但是无论怎么样,在与人交互的情况下,用悲观锁的性能肯定是很悲观的
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2376 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 43ms · UTC 08:07 · PVG 16:07 · LAX 01:07 · JFK 04:07
    ♥ Do have faith in what you're doing.