简介

image.png

共享锁与排他锁

InnoDB 实现了标准的行级锁,包括两种:共享锁(简称 s 锁)、排它锁(简称 x 锁)

  • 共享锁(S锁):允许持锁事务读取一行。
  • 排他锁(X锁):允许持锁事务更新或者删除一行

如果事务 T1 持有行 r 的 s 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:

  • T2 请求 s 锁立即被允许,结果 T1 T2 都持有 r 行的 s 锁
  • T2 请求 x 锁不能被立即允许

如果 T1 持有 r 的 x 锁,那么 T2 请求 r 的 x、s 锁都不能被立即允许,T2 必须等待T1释放 x 锁才可以,因为X锁与任何的锁都不兼容
image.png

意向锁

  • 意向共享锁( IS 锁):事务想要获得一张表中某几行的共享锁
  • 意向排他锁( IX 锁): 事务想要获得一张表中某几行的排他锁

比如:事务1在表1上加了S锁后,事务2想要更改某行记录,需要添加IX锁,由于不兼容,所以需要等待S锁释放;如果事务1在表1上加了IS锁,事务2添加的IX锁与IS锁兼容,就可以操作,这就实现了更细粒度的加锁。

InnoDB存储引擎中锁的兼容性如下表:
image.png

记录锁(Record Locks)

  • 记录锁是最简单的行锁,仅仅锁住一行。如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
  • 记录锁永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁。
  • 会阻塞其他事务对其插入、更新、删除

记录锁的事务数据(关键词:lock_mode X locks rec but not gap),记录如下:

  1. RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
  2. trx id 10078 lock_mode X locks rec but not gap
  3. Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
  4. 0: len 4; hex 8000000a; asc ;;
  5. 1: len 6; hex 00000000274f; asc 'O;;
  6. 2: len 7; hex b60000019d0110; asc ;;

间隙锁(Gap Locks)

  • 间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙。
  • 使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
  • 间隙锁只阻止其他事务插入到间隙中,他们不阻止其他事务在同一个间隙上获得间隙锁,所以 gap x lock 和 gap s lock 有相同的作用。

间隙锁的事务数据(关键词:gap before rec),记录如下:

  1. RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account`
  2. trx id 38049 lock_mode X locks gap before rec
  3. Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
  4. 0: len 3; hex 576569; asc Wei;;
  5. 1: len 4; hex 80000002; asc ;;

Next-Key Locks

  • Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁

插入意向锁(Insert Intention)

  • 插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,亦即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。
  • 假设有索引值4、7,几个不同的事务准备插入5、6,每个锁都在获得插入行的独占锁之前用插入意向锁各自锁住了4、7之间的间隙,但是不阻塞对方因为插入行不冲突。

事务数据类似于下面:

  1. RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
  2. trx id 8731 lock_mode X locks gap before rec insert intention waiting
  3. Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
  4. 0: len 4; hex 80000066; asc f;;
  5. 1: len 6; hex 000000002215; asc " ;;
  6. 2: len 7; hex 9000000172011c; asc r ;;...

锁模式兼容矩阵(横向是已持有锁,纵向是正在请求的锁):
image.png

备份时的锁

备份的时候, 这个时间点的数据不希望改变,实际上加全局锁
如果是innodb引擎, 有mvcc对版本分发控制, 可以用这个特性来备份, mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。

为什么不设置全局只读?
因为如果客户端发生异常断开, mysql会释放这个全局只读的, 如果客户端发生异常, 那会一直是全局只读

表级锁

表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。

元数据锁MDL, 不需要显示使用
我们对一个表中查询的时候, 不希望表结构改变

  1. 对表中数据curd访问,拿到MDL读锁
  2. 对表结构更改, 需要MDL写锁

问题: 小表更新, 会不会出问题?

image.png

session A, session B都是读锁, 可以正常执行, 到session C, 由于前面的session A, session B的MDL没有释放, session C拿不到写锁, 后续的操作都被session C block了

session C自己在那等,没啥事情, 但是其他线程都被阻塞了
注意这个问题, 如果没有 session C, 他们都可以拿到读锁的

还有个问题, session在这边阻塞了, 后续session来了爆满, session是占用内存的, 导致内存升高.


解决办法?

alter table设置一个超时机制,拿不到锁就不要阻塞后面的sql,先释放, 拿到更好

MariaDB 已经合并了 AliSQL 的这个功能,所以这两个开源分支目前都支持 DDL NOWAIT/WAIT n 这个语法。

  1. ALTER TABLE tbl_name NOWAIT add column ...
  2. ALTER TABLE tbl_name WAIT N add column ...