MySQL实现乐观锁和悲观锁

  1. 前言
  2. 悲观锁
    1. 实现悲观锁
    2. for update 悲观锁的问题
  • 悲观锁的弱化版本
  • 乐观锁
    1. 实现乐观锁
    2. 乐观锁使用建议
  • 前言

    基于数据库的乐观锁和悲观锁主要目的是为了解决在数据库并发时, 对数据更新不一致导致的问题.

    悲观锁

    悲观锁具有独占和排他两种特性, 修改数据时必须先拿到锁, 否则直接拒绝. 在操作数据的过程中, 全称持有锁, 操作完毕后, 释放锁.

    实现悲观锁

    一般在MySQL中实现悲观锁, 使用 select … for update 实现, 当一个事务对某资源调用了该语句, 其他所有调用该资源的事务只有等待, 直到第一个事务释放.

    # 1. 构造测试表
    CREATE TABLE `test` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) NOT NULL,
      `age` int(11) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
    insert into test (name,age) values('zhao',20);
    insert into test (name,age) values('xi',21);
    
    # 2. 设置数据库不自动提交
    set autocommit = 0;
    
    # 3. 事务1 执行
    select * from test where id = 1 for update; 
    
    # 4. 事务2 执行
    update test set age = 22 where id = 1;
    commit;
    
    ## 可以观察事务2 会一直阻塞在update 语句
    
    # 5. 在事务1 执行 commit;
    
    ## 可以观察事务2 在事务1执行完毕后, 事务2也执行成功.

    for update 悲观锁的问题

    当 for update的字段为索引或者主键的时候, 只锁住索引或者主键对应的行, 否则锁住整个表, 使用时一定注意.

    
    # 1. 事务1 执行
    select * from test where name = 'zhao' for update;
    
    # 2. 事务2 执行
    select * from test where name = 'xi' for update;
    
    ## 可以观察事务2 会一直阻塞
    
    # 3. 事务1 执行 rollback
    
    ## 观察事务2 在事务1 执行完毕后, 事务2也执行成功
    
    # 4. 为age字段添加索引
    create index idx_test_age on test(age);
    commit;
    
    # 5. 事务1 执行
    select * from test where age = 22 for update;
    
    # 6. 事务2 执行
    select * from test where age = 21 for update;
    commit;
    
    ## 可以观察事务2 并不会阻塞
    
    # 7. 事务1 执行 rollback
    rollback;
    
    # 8. 设置数据库自动提交
    set autocommit = 1;
    

    悲观锁的弱化版本

    悲观锁也可以使用 select … lock in share mode 来实现, 不过相对select … for update 来说, 前者不会阻塞查询. 因为 前者使用 IS (意向共享锁) 锁, 后则使用 IX (意向排他锁) 锁.
    下面验证下 select … lock in share mode.

    # 1. 设置数据库不自动提交
    set autocommit = 0;
    
    # 2. 事务1 执行
    select * from test where id = 1 lock in share mode; 
    
    # 3. 事务2 执行
    select * from test where id = 1 lock in share mode;  
    commit;
    
    ## 可以观察事务2 并没有阻塞
    
    # 4. 事务2 执行
    update test set age = 23 where id = 1;
    commit;
    
    ## 可以观察事务2 会阻塞在update语句
    
    # 5. 事务1 执行rollback;
    rollback;
    
    ## 可以观察事务2 事务正常提交
    
    # 6. 设置数据库自动提交
    set autocommit = 1;
    

    乐观锁

    乐观锁大部分实现基于版本机制(Version 或者 Timestamp)来实现, 相对悲观锁更加宽松, 大多数情况下, 并发比悲观锁高, 所以开发实际运用更趋近于乐观锁.

    实现乐观锁

    # 1. 更新数据库, 新增version 字段
    ALTER table test add version int not null;
    
    # 2. 设置数据库不自动提交
    set autocommit = 0;
    
    # 3. 事务1 执行
    select * from test where id = 1 ; 
    
    ## 可以观察事务2 version 字段为 0
    
    # 4. 事务2 执行
    select * from test where id = 1;
    
    ## 可以观察事务2 version 字段为 0
    
    # 5. 事务1 执行
    update test set age = 24 , version = version+1 where id = 1 and version = 0;
    commit;
    
    ## 可以观察事务1 事务正常提交
    
    # 6. 事务2 执行
    update test set age = 24 , version = version+1 where id = 1 and version = 0;
    commit;
    
    ## 可以观察事务2 正常提交, 但是受影响的行数为 0.
    
    # 7. 设置数据库自动提交
    set autocommit = 1;
    commit;

    乐观锁使用建议

    乐观锁在使用的时候, 不一定在同一事务中, 只是检测version字段更新时是否和读取时值保持一致, 若一致则更新, 否则更新失败. 现在很多orm 都有实现version自动更新的功能, 使用非常方便, 也推荐使用该方式处理并发.


    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 zhao4xi@126.com

    文章标题:MySQL实现乐观锁和悲观锁

    文章字数:1k

    本文作者:Zhaoxi

    发布时间:2019-01-03, 14:58:25

    最后更新:2019-09-21, 15:19:44

    原始链接:http://zhao4xi.github.io/2019/01/03/Mysql实现乐观锁和悲观锁/

    版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

    目录