文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

MySQL 核心模块揭秘 | 锁在内存里长什么样?

2024-11-29 22:06

关注

因为有共同属性,表锁结构和行锁结构都使用结构体 lock_t 来表示锁结构。

在 lock_t 之下,又定义了 lock_table_t、lock_rec_t 分别包含表锁结构和行锁结构的不同属性。

为了更直观的理解表锁结构和行锁结构,我们去掉 lock_t 的一些非核心信息之后,整理如下:

// storage/innobase/include/lock0priv.h
struct lock_t {
  trx_t *trx;
  UT_LIST_NODE_T(lock_t) trx_locks;
  dict_index_t *index;
  lock_t *hash;
  union {
    lock_table_t tab_lock;
    lock_rec_t rec_lock;
  };
  uint32_t type_mode;
};

虽然表锁结构和行锁结构都定义了自己的结构体,用于表示各自不同的属性,但是 lock_t 中 index、hash 这两个只用于行锁结构的属性,并没有放入 lock_rec_t。

我们就不去追溯为什么这两个属性会放在 lock_t 中,而没有放入 lock_rec_t 了。

2. type_mode

从属性名上看,表锁结构和行锁结构的 type_mode 属性存放的是锁类型和锁模式。

实际上,这个属性比较复杂,它占用 4 字节,共 32 位,分为四个部分。

图片

第一部分,1 ~ 4 位,这 4 位是个整体,共同表示一个整数值,就是锁模式。

锁模式部分的 4 字节,作为一个整体使用,而没有按位使用,这是有原因的。

按整体使用,4 字节的无符号整数最大值为 15,最多可以表示 15 种锁模式。

按位使用,每位只能表示一种锁模式,4 位只能表示 4 种锁模式。

第二部分,5 ~ 8 位,按位使用,存放的是锁类型。

第三部分,第 9 位,按位使用,存放的是锁等待状态(LOCK_WAIT),置为 0 表示已经获得锁,置为 1 表示处于锁等待状态。

第四部分,10 ~ 32 位,按位使用,存放的是锁的精确模式,这部分只有行锁和谓词锁会用到,表锁不会用到。

看到这里,大家有没有觉得奇怪,怎么没有用于标识 Next-Key 的位置?

锁模式为行锁(LOCK_REC)时,如果 10 ~ 32 位中所有位都被设置为 0,就表示加的行锁是 Next-Key 锁。

3. 表锁结构

lock_t 中,表锁结构只使用 trx、trx_locks、type_mode 三个属性,加上 lock_table_t 的 table、locks 属性,就是表锁结构的全部属性了。

图片

MySQL 执行 DDL、DML 语句时,InnoDB 都会有对应的事务(用户手动启动或者 InnoDB 自动启动)来执行这些语句对应的操作。

加锁操作自然也是在事务中进行的,trx 属性就是加这个表锁的事务对象。

事务执行一条或多条 DML 语句,可能涉及多个表,也就有可能加多个表锁。事务除了加表锁,还有可能加行锁,同一个事务加的一个或多个表锁和一个或多个行锁的锁结构通过 trx_locks 属性形成一个表锁结构和行锁结构混合的链表。

表锁是加在表上的,自然就需要知道表锁结构属于哪个表了,table 属性就是这个表锁结构所属的表对象。

同一时刻,可能有多个事务已经或者想要对同一个表加锁。对于兼容的表锁,多个事务可以同时加锁,对于不兼容的表锁,后加锁的事务就会处于等待状态。

事务想要对某个表加锁,InnoDB 怎么判断事务可以立即获得锁,还是要进入等待状态?

这就是 locks 属性的用武之地了。

多个事务对同一个表加了表锁,这些表锁的锁结构会通过 locks 属性形成一个链表。

事务想要对某个表加表锁,InnoDB 就会遍历这个链表。

如果链表中还没有表锁结构,或者所有锁结构对应的表锁都和事务当前要加的表锁兼容,事务就可以立即获得锁,否则就需要进入等待状态。

type_mode 属性的第 5 位用于标识锁结构是否为表锁(LOCK_TABLE)。

对于表锁,锁结构中 type_mode 属性的第 5 位会被设置为 1,第 1 ~ 4 位会写入锁模式对应的整数值。

如果事务不能立即获得表锁,type_mode 属性的第 9 位会被设置为 1,表示处于锁等待状态。

4. 行锁结构

lock_t 中,行锁结构使用 trx、trx_locks、index、hash、type_mode 五个属性,加上 lock_rec_t 的 page_id、n_bits 两个属性,外加行锁结构最后外挂了一块没有属性名的内存区域(我们暂且命名为 bitmap),就是行锁的整体结构了。

图片

4.1 有名有姓的那些属性

和表锁结构一样,行锁结构里也有 trx 和 trx_locks 两个属性。

trx 属性是加这个行锁的事务对象。同一个事务加的一个或多个表锁和一个或多个行锁的锁结构,通过 trx_locks 属性形成一个表锁结构和行锁结构混合的链表。

主表的记录存储在主键索引中,二级索引(包括唯一索引、非唯一索引)的记录存储在二级索引中,行锁都是对主键索引或二级索引的记录加锁。index 属性就是这个行锁结构所属的索引对象。

InnoDB 可能同时有很多个事务在运行,这些事务加的行锁,可能会产生多个行锁结构。

每个行锁结构都会根据 page_id 属性中保存的表空间 ID、数据页号计算得到一个哈希值。哈希值相同的多个行锁结构通过 hash 属性形成一个行锁结构链表。

n_bits 属性的值是个无符号整数,表示这个锁结构能保存多少条记录的行锁状态,也就是最多有多少记录能共用这个行锁结构。

对于行锁,锁结构中 type_mode 属性的第 6 位会被设置为 1,第 1 ~ 4 位会被写入锁模式对应的整数值。

行锁的不同精确模式,type_mode 属性第四部分(10 ~ 32 位)各位的赋值情况如下:

如果事务不能立即获得行锁,type_mode 属性的第 9 位会被设置为 1,表示处于锁等待状态。

4.2 隐姓埋名的内存区域

前面介绍的那些,都是 InnoDB 给取了名字的行锁结构属性。

还有一块没有名字的内存区域没有介绍。在前面的行锁结构图中,我们给这块内存区域取了个名字,为 bitmap。

bitmap 这块内存区域是干嘛用的呢?

待我们细细说来。

我们先忽略 bitmap 内存区域的存在,假设一个事务对一条记录加行锁,会产生一个行锁结构,对多条记录加行锁,就会产生多个行锁结构。

又假设事务对多条记录加的都是共享 Next-Key 锁,并且已经获得了锁,巧合的是这些记录又位于同一个数据页,那么,这些锁结构除了加锁记录不一样,其它属性的值都相同。

如果真这么设计行锁结构,是不是太浪费内存空间了?

当然是了。虽然现在内存越来越便宜,但是毕竟还要花钱,也不能那么铺张浪费。

本着勤俭节约的原则,InnoDB 把加锁记录不同、其它属性值都相同的多个行锁结构合并成一个,另外开辟一块内存区域用于标识加锁记录,于是就有了我们命名为 bitmap 的内存区域。

bitmap 内存区域按位使用,每一位都用于标识事务是否对某条记录加了行锁。如果某一位被设置为 1,就表示事务对该位对应的记录加了行锁。

图片

上图是事务对象初始化时,预先创建的一个行锁结构的 bitmap 内存区域示意图,大小为 256 字节,可以用于标识这个事务对 2048 条记录加行锁的情况。

示意图中,第 3 位和第 5 位被设置为 1,说明事务对数据页中序号为 0 和 4 的记录加了行锁。

没有规矩不成方圆,InnoDB 不会胡乱的把多个行锁结构合并成一个。

事务对多条记录加行锁,想要共用一个行锁结构,需要同时满足以下个条件:

4.3 共用行锁结构的两个问题

问题一:多个处于等待状态的行锁能共用一个锁结构吗?

理论上是可以的,但实际上不会出现这种情况。

因为共用一个行锁结构需要满足的条件之一,是一个事务对多条记录加行锁。

然而,一个事务对某条记录加行锁处于等待状态,在获得锁或者锁超时之前(不考虑异常情况),这个事务不会继续往下执行。

这样一来,一个事务在某一时刻,最多只有一个行锁结构(对应一条记录)处于等待状态,也就不存在多个处于等待状态的行锁共用一个行锁结构的情况了。

获得锁或者锁等待超时之后,行锁结构中 type_mode 的第 9 位就会被设置为 0,表示这个行锁处于非等待状态,后续在满足共用条件的情况下,这个锁结构才可以被共用。

问题二:多个插入意向锁能共用一个锁结构吗?

同样,理论上是可以的,但实际上不会出现这种情况。

首先,插入意向锁的加锁场景,是事务 T 想要在某条记录前面的间隙插入一条记录,而这个间隙被其它事务加了间隙锁或者 Next-Key 锁,导致事务 T 必须在这个间隙上加插入意向锁,并等待其它事务释放间隙锁或者 Next-Key 锁。

前面已经介绍过,处于等待状态的行锁结构,是不能共用的。

然后,事务 T 获得锁之后,它的精确模式为 LOCK_GAP + LOCK_INSERT_INTENTION,其它间隙锁也不能共用这个锁结构,因为间隙锁的精确模式为 LOCK_GAP。

虽然插入意向锁的锁结构不能共用,会浪费一些内存,但好在加插入意向锁的情况也不会非常多,浪费的内存也就不会太多。

5. 总结

InnoDB 的表锁结构和行锁结构,有一部分属性是相同的,也有一部分属性是专用的,所以,代码里定义了三个结构体来描述表锁结构和行锁结构。

一个事务对每个表加表锁,都会产生一个表锁结构。

一个事务对多条记录加行锁,满足条件时,多条记录的行锁可以共用一个行锁结构,以节省内存。

处于等待状态的行锁结构,不能共用。获得行锁或者锁等待超时之后,这个锁结构会变为非等待状态,之后满足条件时,这个锁结构可以被共用。

插入意向锁的锁结构不能共用。

来源:爱可生开源社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯