🥁 锁

吞佛童子2022年6月20日
  • db
  • mysql
大约 6 分钟

🥁 锁

1. 锁的拥有者

1) 读锁 | 共享锁

  1. select ... lock in share mode | for share [MySQL 8.0 新增]
  2. 可以保证自己读到的是最新数据,只有没有其他事务竞争时,才可以修改数据

2) 写锁 | 排它锁

  1. select ... for update
    • 若是查询的是索引,那么会加行锁
    • 若是查询的是普通字段,那么会加表锁
  2. 可以保证自己读到的是最新数据,并且该数据只允许自己修改数据,其他事务想要读取和修改,则被阻塞
  3. 只有拥有该锁的事务可以读取和修改,其他事务都不可以读取和修改,并且同一时间只能有一个事务加写锁

2. 锁的粒度

1) 表级锁

  1. 共享锁 & 排它锁
    • 表共享锁
      • lock tables chap read
      • unlock tables 释放表锁
    • 表排它锁
      • lock tables chap update
      • unlock tables 释放表锁
  2. 意向锁
    • 兼容性
      • 意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突
      • 只会阻塞 表级读锁或表级写锁
      • 意向锁也 不会和行锁冲突,行锁只会和行锁冲突
    • 意向共享锁
      • 事务有意向对某些行加共享锁
    • 意向排它锁
      • 事务有意向对某些行加排它锁
    • 作用
      • 解决 表级锁 & 行级锁 共存的问题
      • 若没有意向锁,则我们添加 表锁 时,需要遍历表中所有行来判断有没有行锁,效率低
      • 若是存在意向锁,则我们添加 表锁 时,可以直接一次判断表中是否有数据行被锁定
    • 举例
      • 事务 A 申请 写行锁 之前,数据库会自动给 事务 A 申请表的 意向排他锁
      • 当 事务 B 申请表的互斥锁时,就会失败

2) 页锁


3) 行锁

  • RC 隔离级别不会加任何锁
// id 为主键,只需要在 id == 49 这个主键索引上加 写锁
update user set age = 10 where id = 49;
// name 为普通索引,首先在 name == 'Tom' 这个索引上加 写锁;然后回表,在 id == 49 这个主键索引上加 写锁
update user set age = 10 where name = 'Tom';

① 记录锁 [Record Lock]

  1. 锁定一个记录上的索引,而不是记录本身
  2. 如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用
  3. 当 SQL 语句无法使用索引时,会进行全表扫描,这个时候 MySQL 会给整张表的所有数据行加记录锁
    • 再由 MySQL Server 层进行过滤
    • 但是,在 MySQL Server 层进行过滤的时候,如果发现不满足 WHERE 条件,会释放对应记录的锁。
    • 这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的
    • 因此,更新操作必须要根据索引进行操作,没有索引时,不仅会消耗大量的锁资源,增加数据库的开销,还会极大的降低了数据库的并发性能

② 间隙锁 [Gap Lock]

  1. 锁定索引之间的间隙,但是不包含索引本身
  2. 为开区间
  3. 间隙锁可以防止其他事务在这个范围内插入或修改记录,保证两次读取这个范围内的记录不会变,可以 防止幻读
  4. 间隙锁和间隙锁之间是互不冲突的,间隙锁唯一的作用就是 防止其他事务的 insert

③ 临键锁 [Next-Key Lock]

  1. 不仅锁定一个记录上的索引,也锁定索引之间的间隙
  2. 为 左开右闭 ( left , right ] 区间

④ 插入意向锁

  • 插入意向锁 是 间隙锁 的一种,专门针对 insert 操作,
  • 官方意思是说多个事务在同一个索引同一个范围区间插入记录时候,如果插入位置不冲突,不会彼此阻塞
    • id = 30id = 49 之间如果有两个事务要同时分别插入 id = 32id = 33没问题的,
    • 虽然两个事务都会在 id = 30id = 50 之间加上插入意向锁,但是不会冲突
  • 若是在 RC 隔离级别,此时有个 update 事务还未提交,然后有个 insert 操作,此时 insert 操作不阻塞,不用等待 update 事务提交
  • 若是在 RR 隔离级别,此时有个 update 事务还未提交,然后有个 insert 操作,此时 insert 操作阻塞,需要等待 update 事务提交
    • 从而保证了 RR 隔离级别下,通过 临键锁 实现了 串行化,保证在整个事务执行过程中,无论何时查到的数据记录条数不会改变
  • 插入意向锁 只会和 间隙锁或 Next-key 锁 冲突,
    • 间隙锁唯一的作用就是防止其他事务插入记录造成幻读,正是由于在执行 INSERT 语句时需要加插入意向锁,而插入意向锁和间隙锁冲突,从而阻止了插入操作的执行
  • 如果一个事务加了 插入意向锁,不影响其他事务加任何锁;但若是一个事务加了 临键锁 | 间隙锁,那么当前事务加 插入意向锁 时,会阻塞
    • 这里有个顺序问题

临键锁具体使用 [MySQL 8.0.26]

原始数据:

id [主键]b [普通索引]a [无索引]
000
444
888
161616
323232
  1. 唯一索引等值查询
  2. 唯一索引范围查询
  3. 非唯一索引等值查询
    • 当记录存在时,需要加 左临键锁 & 右间隙锁
    • 作用:
      • 之所以要把记录前后的间隙都锁住,仍然是为了防止幻读
      • 因为是非唯一索引,所以 id = 49 可能会有多条记录,为了防止再插入一条 id = 49 的记录
  4. 非唯一索引范围查询

3. 锁的态度

1) 悲观锁

  1. 优点:
    • 保证数据安全
  2. 缺点:
    • 影响性能
  3. 适用场景:
    • 写多读少的并发环境

2) 乐观锁

  1. 优点:
    • 避免加锁的开销,提高响应性能
  2. 缺点:
    • 写竞争激烈情况下,多次 CAS 重试,CPU 损耗大
  3. 适用场景:
    • 读多写少的并发环境
  4. 如何实现:
    • 开发者自行实现
    • 可通过 添加 版本 | 时间戳 实现

4. 锁的方式

1) 显式锁

2) 隐式锁


5. 其他

1) 全局锁

2) 死锁

  1. 排查流程
    • 查看死锁日志
      • show engine innodb status
    • 找出死锁 sql
    • 分析 sql 死锁情况
    • 模拟 死锁 案发
    • 分析 死锁 日志
    • 分析 死锁 结果
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou