MySQL 锁

所有普通的 select 语句在已提交读、可重复读的隔离级别下都是一致性读,也就是使用 MVCC 进行的读取操作。而在有些场景下,如果只允许读取记录的最新版本,我们就需要加锁。使用 MVCC 的话,读-写操作并不冲突,而使用加锁操作的话,读-写操作需要排队执行。

  • 排它锁(Exclusive Lock)/ X锁:事务对数据加上X锁时,只允许此事务读取和修改此数据,并且其它事务不能对该数据加任何锁;
  • 共享锁(Shared Lock)/ S锁:加了S锁后,该事务只能对数据进行读取而不能修改,并且其它事务只能加S锁,不能加X锁

对于 UPDATE、 DELETE 和 INSERT 语句, InnoDB 会自动给涉及数据集加排他锁(X 锁); 对于普通 SELECT 语句,InnoDB 不会加任何锁(使用 MVCC); 事务可以通过以下语句显式给记录集加共享锁或排他锁:

  • 共享锁(S锁):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
  • 排他锁(X锁):SELECT * FROM table_name WHERE ... FOR UPDATE

锁有不同的粒度,可以分为行级锁和表级锁。那么如果想对一个表加X锁,这个时候如果还需要先检测是否有其它事务对该表或者该表中的某一行加了锁,这样效率就太低了,因此 InnoDB 还提出了一个意向锁(Intention Locks)的概念:

  • 一个事务在获得某个数据行对象的 S 锁之前,必须先获得整个表的 IS 锁或更强的锁;
  • 一个事务在获得某个数据行对象的 X 锁之前,必须先获得整个表的 IX 锁;
  • IS/IX 锁之间都是兼容的;

InnoDB 中的行级锁

在 InnoDB 中,行级锁也有很多不同的类型,用于不同的加锁场景:

  • Record Locks:就叫记录锁吧,也就是锁住一条记录,分为X型记录锁和S型记录锁,作用和上面提到的X锁和S锁类似
  • Gap Locks:就叫间隙锁吧。给一条记录加上间隙锁意味着不允许在这条记录前面的间隙插入新记录,它的作用就是防止插入幻影记录,防止出现幻读。

MySQL 锁

间隙锁是加在记录前面的间隙的,那么如何防止别的事务在最后一条记录的后面插入幻影记录呢?其实只需要在页面的 Supremum 记录(表示该页面中最大的记录)上加一个间隙锁就行了。

  • Next-Key Locks:记录锁和间隙锁的结合,既锁住了一条记录,也锁住了记录前面的间隙
  • Insert Intention Locks:插入意向锁。当一个事务想要插入一条记录的时候,如果要插入的位置上已经被加了间隙锁,那么该事务就会生成一个插入意向锁并处于等待状态。等到间隙锁释放之后,该事务就能获取到插入意向锁了。插入意向锁之间互不阻塞,插入意向锁也不会阻止别的事务获取该记录上任何类型的锁。
  • 隐式锁:如果一个事务刚插入一条新记录(此时这条记录上还没有任何锁结构),这时另一个事务想要获取该记录的X锁或者S锁,该怎么办,岂不是会产生脏读或者脏写问题?其实,虽然这条新记录上没有任何锁结构,但在聚簇索引的记录上有一个 trx_id 隐藏列,另一个事务会先判断 trx_id 是否是当前的活跃事务,如果是,则会自动帮助当前事务创建一个X锁,然后自己的锁进入等待状态。相当于对新插入的记录加了一个隐式锁。

InnoDB 锁的内存结构

MySQL 锁

其中行锁信息中的 n_bit 是表示使用了多少比特位,用于和后面的“一堆比特位”用来定位页面中的具体记录,算法比较trick这里略过。

type_mode 被分成了 lock_modelock_typerec_lock_type 三个部分(还有一个比特位用来表示 is_waiting,即是否在等待状态)。lock_mode 用来表示是X锁、S锁、IS锁等等类型。lock_type 用来表示是行级锁还是表级锁。rec_lock_type 则用来表示在行级锁的情况下,具体的锁类型,比如间隙锁。

以上是 MySQL 锁 的全部内容, 来源链接: utcz.com/z/264540.html

回到顶部