MySQL 锁

130 阅读5分钟

MySQL 锁

锁结构

锁结构中两个比较重要的字段

trx信息:表明该锁与哪个事务相联系;

is_waiting:表明当前事务是否在等待;

再谈锁定读

在并发事务的情况下,读 - 读 不会引起问题,而 读 - 写,写 - 读,写 - 写 会发生问题,需要使用 MVCC 或 加锁的方式来解决问题

共享锁和独占锁

共享锁:又称读锁,S锁,MySQL 在读取一条数据时,首先先获取该记录的 S 锁

独占锁:又称写锁,X锁,MySQL 在更改一条数据时,首先先获取该记录的 X 锁

互斥情况

兼容性X 锁S 锁
X 锁不兼容不兼容
S 锁不兼容兼容
  • 一个事务获取了该记录的 S 锁,其他事务可以继续获取该记录的 S 锁,但不能获取该记录 X 锁,会陷入阻塞状
  • 一个事务获取了该记录的 X 锁,其他事务不可以获取该记录的 S 锁和 X 锁

锁定读的语句

对读取记录加 S 锁:select ... lock in share mode

对读取记录加 X 锁:select ... for update

写语句

update:给记录加 X 锁,然后再执行 delete mark 操作

delete:给记录加 X 锁

insert:加 "隐式锁",其实是根据事务 ID 进行判断

多粒度锁(表锁)

前面提到的锁是针对记录的,可以称为行锁,行锁的粒度较细,其实一个事务也可以在表的层面进行加锁,称为表锁。表锁也分为共享锁(S 锁)和独占锁(X 锁

给表加 S 锁

  • 可以给表中的记录加 S 锁
  • 可以给表加 S 锁
  • 不可以给表中的记录加 X 锁
  • 不可以给表加 X 锁

给表加 X 锁表明该事务要独占这个表

  • 不可以给表中的记录加 S 锁
  • 不可以给表加 S 锁
  • 不可以给表中的记录加 X 锁
  • 不可以给表加 X 锁

在给表加 S 锁时,要确保记录中没有加 X 锁;

在给表加 X 锁时,要确保记录中没有加 S 锁 和 X 锁;

意向共享锁:当给表中的记录加 S 锁时,会给表加上 IS 意向共享锁;

意向独占锁:当给表中的记录加 X 锁时,会给表加上 IX 意向独占锁;

  • 在加 S 锁(表锁)时,若发现含有 IX 锁,则阻塞等待,否则加上 S 锁即可;
  • 在加 X 锁(表锁)是,若发现含有 IS 或者 IX 锁,则阻塞等待,否则加上 X 锁即可;

保证了加表锁时不用去遍历整个表,检查表中行锁的情况,大大提高了性能,IX 锁 和 IS 锁两者并不互斥,只是一种标记手段而已

表锁的兼容性

兼容性XIXSIS
X不兼容不兼容不兼容不兼容
IX不兼容兼容不兼容兼容
S不兼容不兼容兼容兼容
IS不兼容兼容兼容兼容

MySQL 中的行锁和表锁

在 InnoDB 事务中,对记录加锁带基本单位是 next-key 锁,但是会因为一些条件会退化成间隙锁,或者记录锁。加锁的位置准确的说,锁是加在索引上的而非行上。

其他存储引擎中的锁

在MyISAM、Memory 存储引擎中,只支持表锁,当我们为使用这些存储引擎的表加锁时,一般都是针对当前会话进行的加锁;

InnoDB 存储引擎中的锁

InnoDB 中的表锁

普通的 select、update、delete、insert 不会给表加表锁。

LOCK TABLES t READ:InnoDB 存储引擎会给表 t 加 S 锁;

LOCK TABLES t WRITE:InnoDB 存储引擎会给表 t 加 X 锁;

InnoDB 中的行锁

Record Lock - 记录锁


  1. 给某个记录直接上锁,官方的名称为 LOCK_RES_NOT_GAP
  2. 该锁也有 S 锁和 X 锁之分的

Gap Lock - 间隙锁


MySQLRepeatable Read 隔离级别下利用 MVCC 和 加锁 的方法是可以很大程度解决幻读的。对于加锁的方式存在一个问题,刚开始时并没有 幻影记录,无法对这些幻影记录上锁,这时就要用到 Gap Lock 了;

Gap Lock 的作用:给一个记录加 Gap Lock 表示不能在该记录的前面的间隙插入新的记录

例如给 number = 8 的记录加上Gap Lock,表明(3,8)之间的间隙不能插入新的记录,注意:number 为 8 的记录不会被锁,间隙锁的作用仅仅是锁住前面的间隙而已

若要插入 number = 4 的记录,先会定位到该新记录的下一条记录,即 number = 8 的记录,发现 number = 8 的记录中加上了 Gap Lock ,表明(3,8)之间的间隙不能插入新的数据,该插入语句会被阻塞

若给 (20,∞) 间隙锁,则要给页面中的最大值 Supremum 加上间隙锁

  • Supremum 记录:页面中的最大值
  • Infimum 记录:页面中的最小值

Next-Key Lock - 临键锁


临键锁:即锁住当前的记录,又锁住该记录前面的间隙

临键锁 等于 记录锁 + 间隙锁,保护当前的记录,又阻止其他事务向该记录前面的间隙中插入新的数据;

Insert Intention Lock - 插入意向锁


当一个事务插入到加了间隙锁的间隙中时,该事务在内存中会生成一个锁结构,即插入意向锁。等到 gap 锁释放后,插入意向锁会把 is_waiting 字段改成 false,然后执行插入操作;

隐式锁


减少在内存中生成锁结构,从而提出了 隐式锁 的概念

如果一个事务插入了一条记录,另一个事务使用 select ... lock in share mode 来获取 S 锁或者 使用 select ... for update 来获取 X 锁时,若获取成功则会造成 脏读 / 脏写的问题;

而隐式锁就正好处理这个问题:insert 的记录会把当前事务的 ID,赋值为 trx_id 的字段,另一个事务获取时,若该记录的 trx_id 位于活跃列表 m_ids 中,则会阻塞当前的 select 语句,为自身事务生成锁结构,is_waiting 字段设置为 true,而当前事务生成锁结构,is_waiting 字段设置为 false;