目录
1.摘要
2. 加锁的本质
3. 锁结构详解
3.1 锁所在的事务信息
3.2 索引信息
3.3 表锁/行锁信息
3.4 type_mode
3.5 其他信息
3.6 一堆比特位
在 MySQL 中,有很多种锁,例如行锁,表锁,页锁,全局锁,它们锁的粒度都不一样。更多情况下,我们只是关注这些锁的区别和作用,却很少关注它们本身,本篇我们就来简单了解一下MySQL 中锁的内存结构,锁中都包含了哪些信息。
在 Java 中,我们对一个代码块或一个方法进行加锁,锁住的都是唯一资源,是需要多个线程去进行争抢的;而在MySQL中,我们对一行记录去进行加锁,实质上是在内存结构中创建了一个锁结构与之相关联。
但这样就会出现一个问题。如果一个事务要对多条记录进行加锁,难道要生成多个锁结构吗?如果数据量过多,锁结构也随之增多,会出现大量的冗余,这么做合理吗?
当然是不合理的。因此,MySQL在对不同的记录进行加锁时,会对需要加锁的记录去做判断,如果符合下面这些条件,就会放在同一个锁结构中。
(一)在同一个事务中进行加锁操作;
(二)被加锁的记录在同一个页面中;
(三)加锁的类型是一样的;
(四)等待的状态是一样的;
可以先简单及以下这四句话,下面我会进行详细说明。
MySQL中有很多种存储引擎,这里我们以最为常用的InnoDB存储引擎为例说明,这也是因为只有InnoDB存储引擎支持行级锁。InnoDB存储引擎的锁结构如下所示,行级锁与表级锁的锁信息略有不同,其余信息都是一样的。
记录操作当前数据的事务信息(这里只是一个指针,通过指针我们可以获取当前事务的详细信息,例如事物的id,事务的隔离级别);
对于行锁来说,需要记录加锁的记录属于哪个索引,这里也是一个指针;
表锁结构和行锁结构略有不同。表锁主要记录对那个表进行加锁; 行锁则记录了三个重要信息,分别是 表空间Space ID,页号Page Number,n_bits(对于行锁来说,一条记录就对应着一个比特位,一个页面中包含很多记录,用不同的比特位来区分到底是哪一条记录加了锁。为此在行锁结构的末尾放置了一堆比特位,这个n_bits属性代表使用了多少比特位,这里只是一个记录数,但不存放真正的比特位,真正的比特位是由最下面"一堆比特位"存放的);
锁的模式(lock_mode)占用了低四位,可选值如下
LOCK_IS(十进制的0):表示共享意向锁,即 IS锁;
LOCK_IX(十进制的1):表示独占意向锁,即 IX锁;
LOCK_S(十进制的2):表示共享锁,即 S锁;
LOCK_X(十进制的3):表示独占锁,即 X锁;
LOCK_AUTO_INC(十进制的4):表示 AUTO-INC锁;
在InnoDB存储引擎中,LOCK_IS,LOCK_IX,LOCK_AUTO_INC 都算是表级锁的模式,LOCK_S,LOCK_X 既可以算是表级锁模式,也可以算作行级锁模式
锁的类型(lock_type),占用了第5~8位,不过现阶段只有第5位和第6位被使用:
LOCK_TABLE(十进制的16):也就是第5个比特位为1时,表示表级锁;
LOCK_REC(十进制的32):也就是第6个比特位为1时,表示行级锁;
锁的具体行为(rec_lock_type):使用其余位表示,只有在lock_type 值为LOCK_REC时,也就是该锁为行级锁时,才会细分为更多的类型,如下
LOCK_ORDINARY(十进制的8):表示 next-key 锁;
LOCK_GAP(十进制的512):也就是当第十个比特位为1时,表示GAP锁;
LOCK_REC_NOT_GAP(十进制的1024):即当第11个比特位为1时,表示正经记录锁;
LOCK_INSERT_INTENTION(十进制的2048):即当第12个比特位为1时,表示插入意向锁
:也在这个32位数字中;
LOCK_WAIT(十进制的256):即我们上面提到的等待状态,IS_WAITING(是否处于等待状态)表示第九个比特位为1时,为true,当前事务还尚未获取到锁,正处于等待状态;为0即false时,表示当前事务获取锁成功;
为了更好地管理系统运行过程中生成的各种锁结构而设计了各种哈希表和链表;
对应上面第三条行锁信息,在行锁结构中,末尾还放置了一堆比特位,比特位的数量就是有上面的 n_bits 来决定的,InnoDB数据页中的每条记录在记录头信息中都包含了一个heap_no 属性,伪记录Infimum的heap_no值为0,Supermum的heap_no值为1,之后插入每条记录,heap_no的值就增加1。锁结构最后的一堆比特位就对应着一个页面中的记录,一个比特位映射一个heap_no,即一个比特位映射到业内的一条记录。