第 22 章 工作面试老大难——锁

news2024/11/25 5:39:45

22.1 解决并发事务带来问题的两种基本方式

当一个事务想对一条记录做改动时,首先会看看内存中有没有与这条记录关联的锁结构,当没有的时候就会在内存中生成一个锁结构与之关联。

  • trx 信息:代表这个锁结构是哪个事务生成的。
  • is_waiting:代表当前事务是否在等待。

在这里插入图片描述

  1. 当事务 T1 改动了这条记录后,就生成了一个锁结构与该记录关联,因为之前没有有别的事务为这条记录加锁,所以 is_waiting = false,这个场景称为获取锁成功,或者加锁成功,可以继续执行操作。
  2. 此时事务 T2 也想改动这条记录,它首先看有没有锁结构与该记录关联,发现有,于是就生成一个 is_waiting = true 的锁结构与这条记录关联,这个场景称为获取锁失败,或者加锁失败,需要等待执行。
  3. 事务 T1 提交之后,会把它生成的锁结构释放掉,并且看有没有其他事务在等等获取锁,如果有,就把该事务的锁结构 is_waiting 属性设置为 false,并唤醒该事务。

与 SQL 标准不同的是,MySQL 在 REPEATABLE READ 隔离级别不但解决了脏读和不可重复读,还解决了幻读问题。

关于如何解决脏读、不可重复读和幻读,有两种方案:

方案一:读操作利用 MVCC,写操作进行加锁。

通过生成一个 ReadView,然后通过 ReadView 找到符合条件的记录版本,其实就像是在生成 ReadView 的那个时刻做了一次时间静止,查询语句只能读到在生成 ReadView 之前已提交事务所做的更改,在生成 ReadView 之前未提交的事务或者之后才开启的事务所做的更改是看不到的。而写操作肯定针对的是最新版本的记录,读记录的历史版本和改动记录的最新版本本身并不冲突。

方案二:读、写操作都进行加锁。

如果我们的一些业务场景不允许读取记录的旧版本,而是每次都必须去读取记录的最新版本,这样在读取记录的时候也就需要对其进行加锁操作,这样也就意味着读操作和写操作也像写-写操作那样排队执行。

22.1.1 一致性读(Consistent Reads)

事务利用 MVCC 进行的读取操作称为一致性读,或者一致性无锁读,或者快照读。所有普通的 SELECT 语句,在 READ COMMITED、REPEATABLE READ 隔离级别下都算是一致性读。

一致性读不会对表的中的任何记录做加锁操作,其他事务可以自由的对表中的记录做改动。

22.1.2 锁定读(Locking Reads)
22.1.2.1 共享锁和独占锁
  • 共享锁:Shared Locks,即 S 锁。事务要读取一条记录时,需要先获取该记录的 S 锁。
  • 独占锁:Exclusive Locks,即 X 锁。事务要改动一条记录时,需要先获取该记录的 X 锁。
兼容性XS
X不兼容不兼容
S不兼容兼容
22.1.2.2 锁定读的语句
  • 对读取的记录加 S 锁:
SELECT ... LOCK IN SHARE MODE;

如果当前事务执行了该语句,那么它会为读取到的记录加 S 锁,这样允许别的事务继续获取这些记录的 S 锁,但是不能获取这些记录的 X 锁。

  • 对读取的记录加 X 锁:
SELECT ... FOR UPDATE;

如果当前事务执行了该语句,那么它会为读取到的记录加 X 锁,这样即不允许别的事务获取这些记录的 S 锁,也不允许获取这些记录的 X 锁。

22.1.3 写操作

  1. DELETE

先在 B+ 树中定位到这条记录的位置,然后获取一下这条记录的 X 锁,然后再执行 delete mark 操作。我们也可以把这个定位待删除记录在 B+ 树中位置的过程看成是一个获取 X 锁的锁定读。

  1. UPDATE

    • 如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化,则先在 B+ 树中定位到这条记录的位置,然后再获取一下记录的 X 锁,最后在原记录的位置进行修改操作。我们也可以把这个定位待修改记录在 B+ 树中位置的过程看成是一个获取 X 锁的锁定读。
    • 如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化,则先在 B+ 树中定位到这条记录的位置,然后获取一下记录的 X 锁,将该记录彻底删除掉,最后再插入一条新记录。这个定位待修改记录在 B+ 树中位置的过程看成是一个获取 X 锁的锁定读,新插入的记录由 INSERT 操作提供的隐式锁进行保护。
    • 如果修改了该记录的键值,则相当于在原记录上做 DELETE 操作之后再来一次 INSERT 操作。
  2. INSERT

一般情况下,新插入一条记录的操作并不加锁,InnoDB 通过隐式锁来保护新插入的记录在本事务提交前不被别的事务访问。

22.2 多粒度锁

意向共享锁:Intention Shared Lock,简称 IS 锁。当事务准备在某条记录上加 S 锁时,需要先在表级别加一个 IS 锁。

意向独占锁:Intention Exclusive Lock,简称 IX 锁。当事务准备在某条记录上加 X 锁时,需要先在表级别加一个 IX 锁。

IS、IX 是表级锁,它们的提出仅仅为了在之后加表级别的 S 锁和 X 锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录。也就是说其实 IS 锁和 IX 锁是兼容的,IX 锁和 IX 锁是兼容的。

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

22.3 MySQL 中的行锁和表锁

22.3.1 其他存储引擎中的锁

MyISAM、MEMORY、MEGE 这些存储引擎只支持表级锁,而且并不支持事务。

22.3.2 InnoDB 存储引擎中的锁

InnoDB 存储引擎既支持表锁,也支持行锁。

22.3.2.1 InnoDB 中的表级锁
  1. 表级别的 S 锁、X 锁

在对某个表执行 SELECT、INSERT、DELETE、UPDATE 语句时,InnoDB 是不会为这个表添加表级别的 S 锁或者 X 锁的。

另外,在对某个表执行一些诸如 ALTER TABLE、DROP TABLE 这类的 DDL 语句时,其他事务对这个表并发执行 DQL、DML 语句会发生阻塞,这个过程其实是通过在 server 层使用一种称为**元数据锁(Metadata Locks,MDL)**来实现的,一般情况下也不会使用 InnoDB 提供的表级别的 S、X 锁。

InnoDB 提供的表级 S 锁和 X 锁相当鸡肋,只会在一些特殊情况下,比方说崩溃恢复过程中用到。

  1. 表级别的 IS 锁、IX 锁

IS 锁和 IX 锁的使命只是为了后续在加表级别的 S 锁 和 X 锁时判断表中是否有已经被加锁的记录,以避免用遍历的方式来查看表中有没有上锁的记录。

  1. 表级别的 AUTO-INC 锁

AUTO_INCREMENT 属性原理:

  • 采用 AUTO-INC 锁:在执行插入语句时就在表级别加一个 AUTO-INC 锁,然后为每条待插入记录分配递增的值,在该语句执行结束后,再把 AUTO-INC 锁释放掉。
  • 采用一个轻量级的锁:在为插入语句生成 AUTO_INCREMENT 值时获取一下这个轻量级锁,然后生成本次插入语句需要用到的值后,就把该轻量级锁释放掉,并不需要等到整个插入语句执行完才释放锁。

TIPS:InnoDB 提供 innodb_autoinc_lock_mode 系统变量来控制使用上述哪种方式生成 AUTO_INCREMENT 列,0:AUTO-INC;2:轻量级锁;1:混用(主从复制时不安全)。

22.3.2.2 InnoDB 中的行级锁
CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number),
KEY idx_name (name)
) Engine=InnoDB CHARSET=utf8;

INSERT INTO hero VALUES
(1, 'l刘备', '蜀'),
(3, 'z诸葛亮', '蜀'),
(8, 'c曹操', '魏'),
(15, 'x荀彧', '魏'),
(20, 's孙权', '吴');

在这里插入图片描述

常用的行锁类型:

  1. Record Locks:LOCK_REC_NOT_GAP,正经记录锁

    在这里插入图片描述

  2. Gap Locks:LOCK_GAP,gap 锁

    通过加锁的方式解决幻读问题时,由于幻影记录尚不存在无法加锁,于是就在可能插入幻影记录的地方加上 gap 锁。

    在这里插入图片描述

    此时就保证了 number 列在(3,8)区间不会有新记录插入。

    在这里插入图片描述

    使用 Supremum[1](####5.3.1 记录头信息的秘密) 记录来保证(20,+∞)区间不会有新记录插入。

    gap 锁的提出仅仅是为了防止插入幻影记录

  3. Next-Key Locks:LOCK_ORDINARY,next-key 锁

    既可以锁住某条记录,又可以阻止其他事务在该记录前面的间隙插入新记录。

    在这里插入图片描述

  4. Insert Intention Locks:LOCK_INSERT_INTENTION,插入意向锁

    一个事务在插入一条记录是需要判断插入位置有没有 gap 锁(包括 next-key 锁),如果有的话就需要等待那个持有 gap 锁的事务提交,并生成一个插入意向锁的锁结构。

    在这里插入图片描述

    多个插入意向锁之间不会相互阻塞,事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁(就是这么鸡肋)。

  5. 隐式锁

    一个事务对新插入的记录可以不显式地加锁(生成一个锁结构),但是由于事务 id的存在,相当于加了一个隐式锁。别的事务在对这条记录加 S 锁 或者 X 锁时,由于隐式锁的存在,会称帮助当前事务生成一个锁结构,然后自己再生成一个锁结构后进入等待状态。

22.3.3 InnoDB锁的内存结构

对一条记录加锁的本质就是在内存中创建一个与之关联的锁结构

对多条记录加锁时可以使用同一个锁结构,但要符合以下条件:

  • 在同一个事务中
  • 被加锁的记录在同一个页面
  • 加锁的类型相同
  • 等待状态相同

在这里插入图片描述

  1. 锁所在的事务信息:指向内存中事务的指针

  2. 索引信息:对于行锁来说,记录加锁的记录属于哪个索引

  3. 表锁/行锁信息

    • 表锁:记录被加锁的表
    • 行锁:Space ID(所在表空间),Page Number(页号),n_bits(使用了多少用来区分是记录是否加锁的比特位)
  4. type_mode:32位,分为3个部分

    在这里插入图片描述

    • lock_mode:锁的模式,占用低4位

      名称十进制表示含义
      LOCK_IS0共享意向锁,IS 锁
      LOCK_IX1独占意向锁,IX 锁
      LOCK_S2共享锁,S 锁
      LOCK_X3独占锁,X 锁
      LOCK_AUTO_INC4AUTO-INC 锁
    • lock_type:锁的类型,占用低5~8位

      名称十进制表示含义
      LOCK_TABLE16(第5个bit为1)表级锁
      LOCK_REC32(第6个bit为1)行级锁
    • rec_lock_type:行锁的具体类型,占用其余位

      名称十进制表示含义
      LOCK_ORDINARY0next-key 锁
      LOCK_GAP512(第10个bit为1)gap 锁
      LOCK_REC_NOT_GAP1024(第11个bit为1)记录锁
      LOCK_INSERT_INTENTION2048(第12个bit为1)插入意向锁
      LOCK_WAIT256(第9个bit为1)第9个比特位为1时,表示 is_waiting = true,否则 is_waiting = false
  5. 其他信息:为了更好地管理各种锁结构而设计的各种哈希表和链表

  6. 一堆比特位:

    对应着一个页面中的记录,一个比特位映射一个 heap_no

    在这里插入图片描述

22.4 语句加锁分析

在这里插入图片描述

22.4.1 普通的SELECT语句

在不同隔离级别下,有不同表现:

  1. READ UNCOMMITTED:不加锁;直接读取记录的最新版本;可能出现脏读、不可重复读和幻读
  2. READ COMMITED:不加锁;每次查询时会生成一个 ReadView,避免了脏读,可能会出现不可重复读和幻读
  3. REPAEATABLE READ:不加锁;只在第一次查询时生成一个 ReadView,避免了脏读、不可重复读和幻读。
  4. SERIALIZABLE:
    • autocommit = 0,禁用自动提交时,普通 SELECT 会被转换为 SELECT……LOCK IN SHARE MODE 获取 S 锁,具体加锁情况与 REPAEATABLE READ 一致。
    • autocommit = 1,启用自动提交时,不加锁,会生成 ReadView 来读取记录,这时一个事务中只包含一条语句,也就不会出现不可重复记、幻读的现象了。

TIPS:MVCC 并不能完全避免幻读现象

在 REPAEATABLE 隔离级别下,T1 第一次执行普通的 SELECT 语句时生成了一个 ReadView,之后 T2 向 hero 表中新插入一条记录 R1 并提交。由于 ReadView 并不能阻止 T1 执行 UPDATE 或 DELETE 语句来改动 R1,导致 R1 的 trx_id 变成了 T1,之后 T1 再次执行普通 SELECT 时就可以看到这条记录了,也即出现了幻读现象。

22.4.2. 锁定读的语句
# MySQL 规定的两种锁定读的语法格式
SELECT……LOCK IN SHARE MODE;

SELECT……FOR UPDATE;

# 由于在执行过程中需要首先定位到被改动的记录并加锁,因为也可以被认为是一种锁定读
UPDATE……

DELETE……
  1. 匹配模式(match mode)

    在使用索引执行查询时,查询优化器首先会生成若干个扫描区间,针对每个区间使用单向链表进行扫描。如果被扫描的区间是一个单点区间,此时的匹配模式就是精确匹配,否则就不是精确匹配。

  2. 唯一性搜索(unique search)

    如果在扫描某个区间前,就能确定该区间内最多只包含一条记录,那么就把这种情况称为唯一性搜索。当查询符合以下条件时,就可以认为是唯一性搜索了:

    • 匹配模式为精确匹配
    • 使用的是聚簇索引或唯一二级索引
    • 唯一二级索引可为 NULL 时,搜索条则不能为索引列 IS NULL
    • 索引中包含多个列时,每一个列都要被用到

分析语句加锁过程

描述得很啰嗦,不妨参考本地事务 | 凤凰架构 (icyfenix.cn)

22.4.3 半一致性读的语句

半一致性读(Semi-Consistent Read)是一种介于一致性读和锁定读之间的读取方式。当隔离级别不大于 READ COMMITED 且执行 UPDATE 语句时将使用半一致性读。

当 UPDATE 语句读取到已经被其他事务加了 X 锁的记录时,InnoDB 会将该记录的最新版本读出来,然后判断该版本是否与 UPDATE 语句中的搜索条件相匹配。如果不匹配,则不对该记录加锁,从而提高不大于 READ COMMITED 隔离级别的并发。

22.4.4 INSERT语句
  • INSERT 语句一般情况下不需要在内存中生成锁结构,而是依靠隐式锁保护插入的记录
  • 插入前需要定位被插入记录在 B+ 树中的位置,如果该位置已经有 gap 锁,则需要加上插入单向锁并进入等待状态

但有两种需要生成锁结构的特殊情况:

  1. 遇到重复键(duplicate key)

    插入时,主键或唯一二级索引重复时,会对已经在 B+ 树中的那条记录加 next-key 锁

  2. 外键检查

22.5 查看事务加锁情况

22.5.1 使用information_schema数据库中的表获取锁信息
  1. INNODB_TRX:存储了 InnoDB 存储引擎当前正在执行的事务信息
  2. INNODB_LOCKS:记录了锁信息
  3. INNODB_LOCK_WAITS:表明每个阻塞的事务是因为获取不到哪个事务持有的锁而阻塞
22.5.2 使用 SHOW ENGINE INNODB STATUS 获取锁信息
SHOW ENGINE INNODB STATUS;

22.6 死锁

  1. 不同事务由于互相持有对方需要的锁而导致事务都无法继续执行的情况称为死锁

  2. InnoDB 有一个死锁检测机制,当它检测到死锁发生时,会选择一个较小的事务进行回滚,并向客户端发送一条消息

    ERROR 1212 (40001): Deadlock found when trying to get lock; try restarting transaction

  3. 使用 SHOW ENGINE INNODB STATUS 语句来查看最近发生的一次死锁信息

22.7 总结

  1. MVCC 和加锁是解决并发事务带来的一致性问题的两种方式
  2. 共享锁(S锁)、独占锁(X)锁,共享锁与共享锁兼容,独占锁不与其他锁兼容
  3. 事务利用 MVCC 进行的读取操作称为一致性读;在读取记录前加锁的读取操作称为锁定读(有特定写法)
  4. IS、IX是表级锁,以便快速判断表中的记录是否有被上锁
  5. InnoDB 的行级锁有多种类型
  6. InnoDB 的锁结构
  7. infomation_schema 库下有关于事务的锁的表
  8. 死锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2187993.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringBoot详细教程】-10-SpringBoot整合swagger【持续更新】

🌿 Swagger2构建Restful API 文档 🍁 Swagger简介 由于SpringBoot能够快速开发、便捷部署等特性,相信有很⼤⼀部分SpringBoot的⽤户会⽤来构建 RESTful API。⽽我们构建RESTfulAPI的⽬的通常都是由于多终端的原因,这些终端会共⽤…

[Python学习日记-37] Python 中的内置函数(下)

简介 在 Python 中有很多内置函数,例如len(),这些函数是Python解释器自带的,可以直接使用。本篇将介绍 O-Z 的内置函数,下图是官方给出的所有 Python 解释器自带的函数 内置函数官方详解:Built-in Functions — Python…

新160个crackme - 073-abexcrackme3

运行分析 需要破解keyfile PE分析 疑似C程序,32位,EP Section是CODE,猜测无壳 静态分析&动态调试 ida搜索字符串,进入函数 call analysis failed,无法查看伪代码 找到上面提示的地址401088,发现是个Exi…

鼓组编写:SsdSample鼓映射 GM Map 自动保存 互换midi位置 风格模板 逻辑编辑器

SsdSample音源的键位映射 方便编写鼓的技巧 可以这样去设置键位关系的面板和钢琴卷帘窗的面板,方便去写鼓。 可以先按GM的midi标准去写鼓,然后比对下鼓的键位映射的关系,去调整鼓。 可以边看自己发b站等处的图文笔记,然后边用电…

订阅ROS2中相机的相关话题并保存RGB、深度和点云图

系统:Ubuntu22.04 ROS2版本:ROS2 humble 1.订阅ROS2中相机的相关话题并保存RGB图、深度图和点云图 ros2 topic list/stellar_1/rgb/image_raw /camera/depth/image_raw /stellar_1/points2CMakeLists.txt cmake_minimum_required(VERSION 3.15) projec…

Deathnote解题过程

主机扫描,发现192.168.1.194 arp-scan -l 端口扫描,发现80和22端口 nmap -sS 192.168.1.194 访问80端口发现自动跳转到http://deathnote.vuln/wordpress添加绑定地址就可以访问了 vim /etc/hosts 192.168.1.194 deathnote.vuln 访问发现并没有什么东西…

IPsec自动方式

文章目录 实验要求实验配置 实验要求 配置 IPsec VPN 采用自动方式同时要满足上网和VPN两种需求使用NAT进行地址映射认证方法和加密算法自行配置采用安全的方法 实验配置 R1: #基本配置 sy sy R1 dhcp enable acl 3001 rule 1 deny ip des 192.168.3.0 0.0.0.255 …

【Python】解密用户代理:使用 Python User Agents 库探索浏览器和设备信息

Python User Agents 是一个专为解析 User Agent 字符串而设计的 Python 库。它能够轻松识别访问设备的类型(如移动设备、桌面设备或平板),并获取设备、浏览器、操作系统等详细信息。借助它,开发者可以更好地了解访问用户的设备属性…

SSM人才信息招聘系统-计算机毕业设计源码28084

摘要 本研究旨在基于Java和SSM框架设计并实现一个人才信息招聘系统,旨在提升招聘流程的效率和精准度。通过深入研究Java和SSM框架在Web应用开发中的应用,结合人才招聘领域的需求,构建了一个功能完善、稳定高效的招聘系统。利用SSM框架的优势&…

如何使用ssm实现政务大厅管理系统+vue

TOC ssm761政务大厅管理系统vue 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代,是一直在变革当中,突如其来的互联网让传统的信息管理看到了革命性的曙光,因为传统信息管理从时效性,还是安全性,还是…

Qt QWidget控件

目录 一、概述 二、Qwidget常用属性及函数介绍 2.1 enable 2.2 geometry 2.3 windowTitle 2.4 windowIcon 2.5 cursor 2.6 font 设置字体样式 2.7 toolTip 2.8 focusPolicy焦点策略 2.9 styleSheet 一、概述 widget翻译而来就是小控件,小部件。…

Linux shell编程学习笔记85:fold命令——让文件瘦身塑形显示

0 引言 我们使用的电脑屏幕有宽有窄,我们有时候希望文件能按照我们的屏幕宽度来调整和匹配,这时我们可以使用fold命令。 1 fold命令 的帮助信息、功能、命令格式、选项和参数说明 1.1 fold 命令 的帮助信息 我们可以输入命令 fold--help 来查看fold …

Spring Boot实现新闻个性化推荐

1系统概述 1.1 研究背景 如今互联网高速发展,网络遍布全球,通过互联网发布的消息能快而方便的传播到世界每个角落,并且互联网上能传播的信息也很广,比如文字、图片、声音、视频等。从而,这种种好处使得互联网成了信息传…

大模型训练环境搭建

硬件资源说明 本教程基于GPU 3090的服务器 资源类型 型号 核心指标 CPU Intel(R) Xeon(R) Bronze 3204 CPU 1.90GHz 12核 内存 / 125Gi GPU NVIDIA GeForce RTX 3090 24G显存 注意:接下来的部分命令需要使用科学上网,需要事先配置好。 安…

基于SpringBoot+Vue的摄影社团管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…

RK3576部署llama2.c

llama2.c 是一个用纯 C 语言实现的轻量级推理引擎,无需依赖任何第三方库即可高效地进行推理任务。与 llama.cpp 相比,其代码更加直观易懂,并且可以在 PC、嵌入式 Linux 乃至 MCU 上部署。以下是 llama2.c 在 RK3576 开发板上的部署步骤。 工…

Linux系统安装教程

Linux安装流程 一、前置准备工作二、开始安装Linux 一、前置准备工作 安装好VMWare虚拟机,并下载Linux系统的安装包; Linux安装包路径为:安装包链接 , 提取码为:4tiM 二、开始安装Linux

C/C++复习(一)

1.sizeof 关于sizeof我们是经常使用的,所以使用方法就不需要提及了,这里我们需要注意的是,sizeof 后面如果是表达式可以不用括号,并且sizeof实际上不参与运算,返回的是内容的类型大小(size_t类型&#xff0…

SpringCloud Config配置中心 SpringCloud Bus消息总线

一、SpringCloud Config 使用git储存配置信息 1)什么是 SpringCloud Config项目实现的目标是将配置文件从本地项目中抽出来放到git仓库中,项目启动时自动从git仓库中取配置文件。 但是本地项目不直接和git仓库通信,而是通过配置服务器中转。…