跳转至

🟢 锁

约 2191 个字 1 张图片 预计阅读时间 11 分钟

MySQL 的全局锁有什么作用?

  • 作用:让整个数据库处于 只读状态,增删改会被阻塞
  • 使用场景:全局锁 主要应用于做全库逻辑备份,不会因为数据或表结构的更新而出现备份文件的数据与预期的不一样
  • 缺陷:数据库里有很多数据,备份会花费很多的时间。备份期间,业务 只能读数据而不能更新数据,这样会造成业务停滞
  • 改进:可重复读的隔离级别。在备份数据库之前先开启事务,会先创建 Read View,然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View,这样备份期间备份的数据一直是在开启事务时的数据

MySQL 的表级锁有哪些?作用是什么?

元数据锁

  • 作用:对数据库表进行操作时 自动 给这个表加上元数据锁。可以保证当用户对表执行 CRUD 操作时,其他线程 无法 对这个表结构做变更。元数据锁在事务提交后才会释放

意向锁

  • 作用:对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」,对某些记录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」。普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的
  • 意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,意向锁之间也不会发生冲突,只会和共享表锁和独占表锁发生冲突。意向锁的目的是为了快速判断表里是否有记录被加锁

提示

SELECT 也可以对记录加共享锁和独占锁

  • 使用 SELECT ... LOCK IN SHARE MODE(或 MySQL 8.0+ 的 FOR SHARE)时,会对查询到的记录加 共享锁
  • 使用 SELECT ... FOR UPDATE 时,会对查询到的记录加 独占锁。这两种语句需要显式指定,普通的 SELECT 不加行级锁

AUTO-INC 锁

  • 作用:表里的主键通常在设置成自增后可以在插入数据时不指定主键的值,数据库会自动给主键递增赋值。 递增值是通过 AUTO-INC 锁实现的。在插入数据时会加一个表级别的 AUTO-INC 锁并将 AUTO_INCREMENT 修饰的字段递增赋值,等插入语句执行完成后才会把 AUTO-INC 锁释放掉。在此期间其他事务向该表插入语句都会被阻塞,从而保证插入数据时字段的值是连续递增的
  • 缺陷:对大量数据进行插入的时候会影响插入性能,因为其他事务中的插入会被阻塞
  • 改进:InnoDB 存储引擎提供了一种轻量级的锁来实现自增。在插入数据的时候,会为被 AUTO_INCREMENT 修饰的字段加上轻量级锁,然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁

AUTO-INC 锁控制

设置 innodb_autoinc_lock_mode 的系统变量,是用来控制选择用 AUTO-INC 锁

  • 当 innodb_autoinc_lock_mode = 0,采用 AUTO-INC 锁,语句执行结束后才释放锁
  • 当 innodb_autoinc_lock_mode = 2,采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放
  • 当 innodb_autoinc_lock_mode = 1
    • 普通 insert 语句,自增锁在申请之后就马上释放
    • 类似 insert … select 这样的批量插入数据的语句,自增锁要等语句结束后才被释放

innodb_autoinc_lock_mode = 2 是性能最高的方式,但是当搭配 binlog 的日志格式是 statement 时,在主从复制的场景中会发生 数据不一致 的问题

binlog 日志格式要设置为 row,这样在 binlog 里面记录的是主库分配的自增值。到备库执行的时候,主库的自增值是什么,从库的自增值就是什么

所以,当 innodb_autoinc_lock_mode = 2 并且 binlog_format = row 时既能提升并发性,又不会出现数据一致性问题

MySQL 的行级锁有哪些?作用是什么?

记录锁(Record Lock)

  • 作用:锁住的是一条记录,记录锁分为 共享锁(S 锁)排他锁(X 锁)。其中 S 锁之间不互斥

间隙锁(Gap Lock)

  • 作用:只存在于可重复读隔离级别,目的是为了 解决可重复读隔离级别下幻读的现象。间隙锁之间是兼容的,两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系

临键锁(Next-Key Lock)

Next-Key Lock 临键锁是 Record Lock + Gap Lock 的组合

  • 作用:锁定一个范围,并且锁定记录本身。next-key lock 既能保护该记录,又能阻止其他事务将新记录插入到被保护记录前面的间隙中,形成 左开右闭 的插入保护区间

插入意向锁

一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。如果有的话,插入操作就会发生阻塞,直到拥有间隙锁的那个事务提交为止,在此期间会生成一个插入意向锁,表明有事务想在某个区间插入新记录,但是现在处于等待状态。

MySQL 如何加锁

区间理解法——便于记忆

可以把三种行级锁的锁定范围类比为数轴上的区间,这样更容易理解和推导加锁行为:

  • 记录锁 → 闭区间 [x]:只锁单条记录,相当于数轴上的一个点
  • 间隙锁 → 开区间 (a, b):锁两条记录之间的空隙,不包含边界,阻止在空隙中插入新记录
  • 临键锁 → 左开右闭 (prev, curr]:锁「前一条记录之后、当前记录及之前」的范围,是记录锁 + 间隙锁的组合

记忆要点:临键锁默认加在每一条扫描到的记录上;当能确定「不会产生幻读」或「只需保护间隙」时,才会退化成记录锁或间隙锁,从而减小锁范围、提升并发度。

唯一索引等值查询

  • 记录存在:定位到对应记录后,将该记录的 next-key lock 退化成记录锁。因为唯一索引保证只有一条记录,无需锁间隙防幻读
  • 记录不存在:定位到「第一条大于查询值」的记录后,将该记录的 next-key lock 退化成间隙锁。只需锁住「本应插入的位置」所在的间隙,防止幻读

非唯一索引等值查询

  • 记录存在:可能有多条相同索引值的记录。扫描过程中对二级索引记录加 next-key lock;扫描到 第一个不满足条件 的二级索引记录时退化成 间隙锁;符合条件记录对应的 主键索引记录锁
  • 记录不存在:第一条不满足条件的二级索引记录退化成 间隙锁;无满足条件的记录,故不对主键索引加锁

唯一索引范围查询

  • > value:对每条大于 value 的索引记录加 next-key lock(含 supremum 伪记录),等价于 (value, +∞)
  • ≥ value:对 value 做等值查询,退化为 记录锁;对大于 value 的加 next-key lock,等价于 [value, +∞)
  • < value:对每条小于 value 的索引加 next-key lock;扫描到「第一条 ≥ value 的记录」时,其 next-key 退化为 间隙锁(即该记录左侧的间隙)。等价于对 (-∞, value) 加锁
  • ≤ value
    • value 对应记录存在:对 (-∞, value] 内的记录加 next-key lock,不退化(因为 value 需被包含)
    • value 对应记录不存在:与 < value 相同——对小于 value 的索引加 next-key lock,在「第一条 > value 的记录」左侧加间隙锁 (pre-value, next-value)。等价于对 (-∞, value) 加锁

非唯一索引范围查询

  • 非唯一索引范围查询时,不会发生 next-key lock 向间隙锁或记录锁的退化
  • 二级索引:对扫描到的记录一律加 next-key lock
  • 主键索引:对符合查询条件的记录加 记录锁

评论