【锁专题】锁常见问题

本问题对应的项目链接:高频题回答模版训练
这块一般不难,可能会结合性能优化来问

1、mysql 有哪些锁,介绍一下?

2、mysql是怎么实现乐观锁和悲观锁的?

3、哪些情况下会使用乐观锁,哪些情况使用悲观锁,可以举一些 sql 例子吗?

4、间隙锁的原理?什么时候会加间隙锁?

共以下 3 个回答

  • 終达·斯维达尼亚 永久会员 2024年3月7日 下午10:33

    1、mysql 有哪些锁,介绍一下?

    mysql中有全局锁、表锁、行锁、间隙锁等。

    • 全局锁:对整个mysql进行加锁,一般在整张表逻辑备份的时候使用
    • 表锁:锁整张表,一般在当前引擎不支持行锁的时候使用
    • 行锁:锁上当前操作的行,可能会存在幻读问题,为解决幻读引入间隙锁
    • 间隙锁:为解决幻读问题,锁上索引之间的间隙

    2、mysql是怎么实现乐观锁和悲观锁的?

    • MySQL 中通常使用版本号或者时间戳来实现乐观锁。以版本号为例,可以在数据表中添加一个版本号字段,然后当某个事务中需要更新一条记录时,直接用 where 语句对版本号进行等值判断,如果与给定的版本号相同则更新,不相同则整个事务回滚或重试。用时间戳实现也是类似的方式。整个过程不需要锁整个表,只需简单判断版本号或时间戳即可判断是否基于修改权限。
    • 悲观锁则是在事务开启时先执行 select … for update 给所有记录加上临键锁,防止其他事务干扰,然后执行更新操作,在事务结束时释放锁,后续事务才能获得锁而继续进行。

    3、哪些情况下会使用乐观锁,哪些情况使用悲观锁,可以举一些 sql 例子吗?

    当对数据进行数据读多写少的时候一般用乐观锁,如果是对数据进行改多读少则使用悲观锁。

    4、间隙锁的原理?什么时候会加间隙锁?

    间隙锁是指在两个数据索引的间隙加上锁,使得其他线程对该间隙的增删改操作都被阻塞。当我们对一组数据上锁的时候,并且隔离级别为可重复读的时候,便会自动加上间隙锁。

    0 赞同 0 条回复
  • 秊 永久会员 2023年8月12日 下午3:45

    1、mysql 有哪些锁,介绍一下?

    按照锁的粒度,可以分为全局锁,表级锁和行锁。

    全局锁会使整个数据库处于只读状态,在做全库逻辑备份时经常用到;表级锁在操作数据时会锁定整张表或表结构,具体可以分为表锁,MDL 锁和意向锁;行锁可以做到只锁定需要操作的记录行或间隙,具体可以分为记录锁,间隙锁和 临键锁,也就是记录锁加间隙锁的组合,它锁的是一个范围。

    2、mysql是怎么实现乐观锁和悲观锁的?

    (看情况简单说一下乐观锁是…多线程基础那有嘞)

    MySQL 中通常使用版本号或者时间戳来实现乐观锁。以版本号为例,可以在数据表中添加一个版本号字段,然后当某个事务中需要更新一条记录时,直接用 where 语句对版本号进行等值判断,如果与给定的版本号相同则更新,不相同则整个事务回滚或重试。用时间戳实现也是类似的方式。整个过程不需要锁整个表,只需简单判断版本号即可。

    (悲观锁是…)

    MySQL 中通常使用 for update 或 lock in share mode 语句来实现悲观锁。具体的,在事务开启时先执行 select ... for update 给所有记录加上 临键锁,防止其他事务干扰,然后执行更新操作,在事务结束时释放锁,后续事务才能获得锁而继续进行。整个过程就是悲观锁的思想:先悲观的整体加锁,等所有更新操作完成再释放。

    3、哪些情况下会使用乐观锁,哪些情况使用悲观锁,可以举一些 sql 例子吗?

    乐观锁适用于读多写少,也就是并发冲突很少的场景,这样可以省去锁的开销,提高系统的吞吐量。悲观锁适用于读少写多的场景,这样可以避免在并发冲突多的情况下,使用乐观锁带来的不断重试问题。这时用悲观锁为每个事务上锁就比较合适。

    举例的话,可以考虑这样的场景:有用户 A 和用户 B,同时去买同一件商品,但商品的数量只有一个。

    乐观锁的实现是在商品表中添加一个 version 字段,然后直接指定版本号更新。假设 MySQL 为 RR 隔离级别,都要买 id = 1 的商品,version 初始为 0,用户 A 对应 SQL 为

    begin;
    select num, version from t_goods where id = 1;  # T1 时刻先查出商品数量和对应的版本号
    update t_goods set num = num - 1, version = version + 1 where id = 2 and version = 0;  # T3 时刻更新商品数量时带上版本号,并使其自增
    commit;
    

    用户 B 对应的 SQL 为

    begin;
    select num, version from t_goods where id = 1;  # T2 时刻先查出商品数量和对应的版本号
    update t_goods set num = num - 1 where id = 2 and version = 0;  # T4 时刻更新商品数量时带上版本号,并使其自增
    commit;
    

    虽然用户 A 和 用户 B 在 T1,T2 时刻都看到还有一件商品,但最终只有用户 A 在 T3 时刻能够获得最后一件商品,此时 version 自增为 1,那么用户 B 在 T4 时刻将无法找到这条记录,也就无法购买成功。

    悲观锁的实现直接在事务开始时给所有记录上锁,直到事务结束再释放锁。用户 A 和 B 对应的 SQL 一样,都是

    begin;
    select num from t_goods where id = 1 for update;  # 先使用 for update 将商品数据加上 next-key lock
    update t_goods set num = num - 1 where id = 1;  # 然后再更新
    commit;
    

    由于用户 A 在事务开始时就对数据加了 临键锁,用户 B 只能等到用户 A 在事务结束释放锁时再执行,最终还是只有用户 A 能购买。

    4、间隙锁的原理?什么时候会加间隙锁?

    对于行锁来说,数据行是可以加上锁的实体,但其实数据行之间的间隙也是可以加上锁的实体,对应的锁就是间隙锁。间隙锁锁的是数据库中两个值的间隙,目的是防止往这个间隙中插入一条新记录,以此来防止幻读。

    间隙锁通常出现在 InnoDB 引擎的可重复读隔离级别下,并且行锁加间隙锁,也就是临键锁是加锁的基本单位。但在某些情况下,临键锁也会退化成行锁或者间隙锁。

    0 赞同 0 条回复
  • 小肸 永久会员 2023年8月7日 下午10:41

    mysql有全局锁,在全库备份时使用。表级别的读锁、写锁实现读写互斥,Innodb存储引擎还有更细粒度的行锁实现读写互斥。表级别的元数据锁用来保证表结构的一致性。行级别的间隙锁解决幻读导致的数据不一致问题。

    mysql通过CAS操作来比较版本号或者时间戳判断原来数据是否被其它线程修改实现乐观锁,update和delete语句存储引擎层会自动加上悲观锁,select语句可以加上for update加上悲观锁。

    在锁冲突频率小、响应速度要求高、重试代价不大的场景下,就可以使用乐观锁。

    比如每日优惠劵的抢购。

    #MyISAM存储引擎
    #版本号
    update stock set quantity = quantity - 1, version = version + 1 where quantity > 0 and version = \#{version}
    and product_id = \#{productId}
    #时间戳timestamp
    update stock set quantity = quantity - 1, update_time = unix_timpstamp(now()) where quantity > 0 and update_time = \#{updateTime} and product_id = \#{productId}
    

    在锁冲突比较严重、响应速度要求不高或者重试代价很大的场景下,就采用悲观锁。

    比如秒杀场景。1000万用户在10分钟内抢购商品,一分钟就有100万次请求,一秒16万次请求。

    #Innodb存储引擎
    update stock set quantity = quantity - 1 where quantity > 0;
    
    0 赞同 0 条回复

# 回答此问题

后才能回答