Mysql进行操作时锁的具体行为


场景一:单个事务更新一条存在的数据

假设有表 user (id PK, name, age),数据:[id=1, name='Alice', age=25]

你的 SQL: UPDATE user SET age = 26 WHERE id = 1;

底层动作:

  1. 事务 A (主动方) 发起更新请求。
  2. Lock Manager 介入:
    • 查找 id=1 的索引记录: Lock Manager 根据 id=1(主键)找到对应的主键索引树叶子节点中的那条索引记录
    • 检查这条索引记录上的锁状态: 发现 id=1 这条索引记录此刻是无锁状态
    • 在索引记录上“粘贴”一个锁标记: Lock Manager 会在 id=1 这条具体的索引记录上,打上一个**“X 锁 (Exclusive Lock)”**的标记。
      • 这个标记就是一条内部的内存数据结构,记录着:“id=1 这条索引记录,现在被事务 AX 模式锁住,并且引用计数+1”。
    • 在对应的表头部“粘贴”一个意向锁标记: 同时,Lock Manager 还会顺手在 user 表的内部元数据结构上,打上一个**“IX 锁 (Intention Exclusive Lock)”**的标记。
      • 这个标记是:“user 表的某个地方,有事务正在尝试或已经持有 X 型行锁。”
  3. 事务 A 执行更新: 事务 A 获得锁,可以安全地修改 id=1 这条索引记录的 age 值。
  4. 事务 A 提交/回滚: 事务 A 结束时,Lock Manager 会根据之前的记录,移除 id=1 上的 X 锁标记,同时检查 user 表是否还有其他 IX 锁持有者,如果没有,也移除 user 表上的 IX 锁标记。

场景二:事务 A 更新数据,事务 B 随后读取同一条数据

数据:[id=1, name='Alice', age=25]

你的 SQL (事务 A): UPDATE user SET name = 'Alicia' WHERE id = 1;
你的 SQL (事务 B): SELECT * FROM user WHERE id = 1;

底层动作:

  1. 事务 A 获得 id=1X 锁 (如场景一所述)。
    • id=1 索引记录上:X 锁,持有者 事务 A
    • user 表元数据上:IX 锁,持有者 事务 A
  2. 事务 B 发起读取请求。
  3. Lock Manager 介入:
    • 查找 id=1 的索引记录。
    • 检查这条索引记录上的锁状态: 发现 id=1 这条索引记录上有一个 X 锁,并且持有者是**事务 A**。
    • 判断冲突: 事务 B 尝试读取,但 事务 A 持有的是 X 锁 (排他锁)。X 锁会阻止所有其他事务的读写。
    • 不授予锁,并等待: Lock Manager 不授予事务 B 任何锁,而是把事务 B 放入一个等待队列,同时启动事务 B 的等待计时器
      • “事务 B 正在等待 id=1 这条索引记录上的锁。”
  4. 事务 A 提交: 释放 id=1 上的 X 锁,也释放 user 表的 IX 锁。
  5. Lock Manager 通知: id=1 上的锁被移除,Lock Manager 发现等待队列中有事务 B。
  6. 事务 B 被唤醒: 事务 B 获得执行权限,读取 id=1 这条记录的新数据(比如 name='Alicia')。

场景三:间隙锁 (Gap Lock) 的具体行为 (防止幻读)

数据:user (id PK),记录只有 [id=10], [id=30] (没有 id=20)

你的 SQL (事务 A): SELECT * FROM user WHERE id BETWEEN 15 AND 25 FOR UPDATE; (注意这是范围查询且带 FOR UPDATE)

底层动作:

  1. 事务 A 发起请求。
  2. Lock Manager 介入:
    • 查找索引: Lock Manager 根据条件 id BETWEEN 15 AND 25,在主键索引树上进行查找。
    • 发现没有符合条件的记录 (这是一个空区间/间隙)。
    • 在“间隙”上打锁标记: 尽管没有找到具体的数据行,Lock Manager 依然会在索引结构中,针对 id=10id=30 之间的**“范围”(即 (10, 30) 这个间隙),打上一个“间隙锁 (Gap Lock)”**的标记。
      • 这个标记就是:“索引中 id 值在 1030 之间的空地,现在被事务 A 锁住,禁止插入新数据。”
      • 通常,这个间隙锁是 X 类型的,因为它阻止其他事务在这个间隙中进行 INSERT 操作。
    • 在对应的表头部“粘贴”一个意向锁标记 (IX)
  3. 事务 B 尝试插入数据: INSERT INTO user (id) VALUES (20);
  4. Lock Manager 介入:
    • 判断插入位置: 发现 id=20 应该插入到 id=10id=30 之间。
    • 检查该间隙的锁状态: 发现这个 (10, 30) 的间隙上有一个间隙锁,持有者是**事务 A**。
    • 不授予锁,并等待: Lock Manager 不授予事务 B 任何锁,将事务 B 放入等待队列
  5. 事务 A 提交: 释放 (10, 30) 上的间隙锁,以及 user 表的 IX 锁。
  6. Lock Manager 通知: 间隙锁被移除,事务 B 被唤醒,可以成功插入 id=20

这些“锁标记”本质上都是数据库系统内部维护的内存数据结构,它们记录着:哪个事务在哪个资源(索引记录或间隙或表)上持有哪种类型的锁。当其他事务请求时,Lock Manager 就去查这些标记,进行兼容性判断,决定是立即授予、等待还是死锁。
内存中的锁管理数据结构,它们并不是简单的“标记”那么纯粹,而是一系列精巧组织的对象。

要理解这个,我们得从 Lock Manager (锁管理器) 的核心工作开始。Lock Manager 维护着一张“活的地图”,这张地图记录了哪些资源被锁了被谁锁了锁的类型是什么,以及谁在等待这些锁


最底层数据结构模拟:Lock Manager 的“活地图”

想象 Lock Manager 就好比一个大型交通控制中心,它有几块巨大的显示屏和一些重要的记录本。

核心数据结构 1:锁哈希表 (Lock Hash Table) 或 锁链表 (Lock List)

这是所有正在活动的锁及其等待者的“索引”。

  • 目的快速查找某个资源(比如某行数据)上是否有锁,以及有哪些事务在等待。
  • 实现:通常是一个哈希表std::unordered_map 类似),键是资源标识符,值是一个链表或队列,里面包含了所有作用在该资源上的锁对象和等待者。因为哈希表的查找速度快,能迅速定位到某个资源。

模拟其内部结构:

// 这是一个高度简化的伪代码,模拟内存中的核心结构// 1. 资源标识符 (Resource Identifier) - 锁住哪个具体的“东西”
//    这是锁的“粒度”所在,可以是一个Page ID + Index ID + Record ID,也可以是表ID
struct LockResource {enum ResourceType {TABLE_LOCK,    // 表级RECORD_LOCK,   // 行级GAP_LOCK       // 间隙};ResourceType type;long long tableId; // 表的唯一标识long long pageId;  // 数据页的唯一标识 (行锁和间隙锁可能需要)long long indexId; // 索引的唯一标识 (行锁和间隙锁需要)// 对于Record Lock,可能还需要存储记录的在页面内的具体位置或哈希值// 对于Gap Lock,可能需要存储间隙的起始和结束点(如索引键值,或其他内部指针)std::string recordKeyHash; // 简化表示:实际是索引键值的hash或物理位置// 确保 LockResource 可以作为哈希表的键bool operator==(const LockResource& other) const { /* 比较所有成员 */ }size_t operator()(const LockResource& res) const { /* 计算哈希值 */ }
};// 2. 具体的锁对象 (Lock Object) - 锁本身的信息
struct LockObject {enum LockMode {IS_LOCK,   // Intention Shared (表级意向共享)IX_LOCK,   // Intention Exclusive (表级意向排他)S_LOCK,    // Shared (读锁,共享锁)X_LOCK     // Exclusive (写锁,排他锁)};LockMode mode;long long transactionId; // 持有这个锁的事务IDint lockCount;           // 锁计数 (用于可重入性), 比如 SELECT ...FOR UPDATE 两次bool isWaiting;          // 这个事务是否正在等待这个锁?// 指向下一个等待这个资源的锁对象(如果存在的话)// 或者指向下一个被该事务持有的锁对象LockObject* nextLockInResourceList; // 针对同一资源的所有锁和等待者链表LockObject* nextLockInTxList;       // 某个事务持有的所有锁链表
};// 3. 锁哈希表 - 核心的数据结构
// Key: LockResource (哪个资源被锁)
// Value: 一个链表/队列,包含所有作用在该资源上的 LockObject
std::unordered_map<LockResource, std::list<LockObject>> globalLockHashTable;

理解 globalLockHashTable 里的“东西”:

  • 每个节点上
    • 没有独立的“锁标记”。相反,数据库管理着一个集中的 Lock Manager
    • 当你说的“节点”是时,表上会有意向锁ISIX)的记录,这些记录也会被存放在 globalLockHashTable 中。LockResourcetype 会是 TABLE_LOCK
    • 当你说的“节点”是时,它指的就是索引记录 (Index Record)。这才是 InnoDB 行级锁的真正目标。LockResourcetype 会是 RECORD_LOCKGAP_LOCK
核心数据结构 2:事务持有的锁列表 (Transaction’s Lock List)

除了按资源查找锁,Lock Manager 还需要知道一个事务到底持有哪些锁,以便在事务提交或回滚时能迅速释放它们。

  • 目的快速释放一个事务持有的所有锁。
  • 实现:每个活跃事务内部,或者 Lock Manager 维护一个映射Transaction ID -> List of LockObject

模拟其内部结构:

// 4. Per-Transaction Lock List - 每个活跃事务会有一个这样的内部列表
//   一个事务 A 内部可能有一个指针指向它所持有的第一个 LockObject
//   或者 Lock Manager 维护一个 map:
std::unordered_map<long long, std::list<LockObject*>> transactionLocksMap;
// 这个 list 里面的 LockObject* 都是上面 globalLockHashTable 里的指针

可视化模拟:

假设有表 user (id PK, name),数据:[id=1], [id=5], [id=10]

事务 A 操作:UPDATE user SET name='New' WHERE id=1;
事务 B 操作:SELECT * FROM user WHERE id BETWEEN 3 AND 7 FOR UPDATE;

Lock Manager 内部状态(简化):

{"globalLockHashTable": {// 资源1: 用户表, TABLE_LOCK类型"Resource_Table_user": [{"mode": "IX_LOCK",          // 意向排他锁"transactionId": "TxA","lockCount": 1,"isWaiting": false},{"mode": "IX_LOCK",          // 意向排他锁 (TxB也会加IX)"transactionId": "TxB","lockCount": 1,"isWaiting": false}],// 资源2: id=1 的索引记录, RECORD_LOCK类型"Resource_Record_user_id_1": [{"mode": "X_LOCK",           // 排他锁"transactionId": "TxA","lockCount": 1,"isWaiting": false}],// 资源3: "(1,5)" 间隙(id=5前面),GAP_LOCK类型"Resource_Gap_user_(1,5)": [{"mode": "X_LOCK",           // 间隙锁是排他的"transactionId": "TxB","lockCount": 1,"isWaiting": false}],// 资源4: "id=5" 记录,RECORD_LOCK类型"Resource_Record_user_id_5": [{"mode": "X_LOCK",           // Next-key lock会包含记录本身"transactionId": "TxB","lockCount": 1,"isWaiting": false}],// 资源5: "(5,10)" 间隙,GAP_LOCK类型"Resource_Gap_user_(5,10)": [{"mode": "X_LOCK",           // 间隙锁是排他的"transactionId": "TxB","lockCount": 1,"isWaiting": false}]// ... 其他资源},"transactionLocksMap": {"TxA": ["Resource_Table_user[IX_LOCK_TxA]","Resource_Record_user_id_1[X_LOCK_TxA]"],"TxB": ["Resource_Table_user[IX_LOCK_TxB]","Resource_Gap_user_(1,5)[X_LOCK_TxB]","Resource_Record_user_id_5[X_LOCK_TxB]","Resource_Gap_user_(5,10)[X_LOCK_TxB]"]}
}
死锁检测器的“行为”:

死锁检测器会定期(或在每次等待发生时)遍历 globalLockHashTable 中的等待链表,并结合 transactionLocksMap 来构建一个**“等待图 (Waits-for Graph)”**。

等待图:

  • 节点:事务 ID (TxA, TxB)。
  • 边:如果 TxA 在等待 TxB 释放某个锁,则从 TxA 指向 TxB。

伪算法:

  1. “老铁,数据库里现在谁在等谁啊?”
  2. 遍历 globalLockHashTable 里的每一个 LockObject
  3. 如果 LockObject.isWaitingtrue
    • 找出这个 LockObject 对应的 LockResource
    • 找出目前正在持有这个 LockResource 上的冲突锁的那个 LockObjecttransactionId (假设是 TxC)。
    • 那么,LockObject.transactionId 正在等待 TxC
    • 在内存的**“等待图”**中,就画一条边:LockObject.transactionId --> TxC
  4. “图画好了!现在我们看看有没有循环
  5. 在“等待图”中进行深度优先搜索 (DFS)拓扑排序等算法来检测是否存在
    • 如果发现 TxA --> TxB --> TxC --> TxA 这样的循环,警报!死锁!
  6. “有了循环!挑选一个受害者,把它回滚,让它释放所有锁,打破这个循环!”

“每个节点上每个表上都有锁标记吗?”

  • 不是每个表“节点”上都有独立的锁标记,而是统一由 Lock Manager 在内存中管理这些 LockObject 实例。
  • 表上:会有 IS/IX 意向锁的 LockObject
  • 行上特指索引记录 (Index Record) 上,会有 S/X 共享/排他锁的 LockObject
  • 间隙上特指索引的空闲区域 (Gap) 上,会有 Gap LockLockObject

所有这些 LockObject 都被组织在 globalLockHashTable 中(按资源分类)以及 transactionLocksMap 中(按事务分类),供 Lock Manager 高效地查找、管理、冲突检测和死锁检测。它们是实时变化的内存数据,支撑着数据库的并发控制。

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

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

相关文章

人工智能领域、图欧科技、IMYAI智能助手2025年7月更新月报

IMYAI 平台 2025 年 7 月重要功能更新与优化汇总 2025年07月31日更新 细节优化&#xff1a; 修复了移动端提交后自动弹出侧边栏的BUG。优化对话高级配置界面&#xff0c;增加滚动条并固定高度&#xff0c;避免内容超出屏幕。音乐生成界面的人声选择新增“合唱”选项&#xff…

HTTP 与 HTTPS 的区别深度解析:从原理到实践

HTTP 和 HTTPS 是现代 Web 开发中不可或缺的协议&#xff0c;它们决定了浏览器与服务器之间数据传输的方式。HTTPS 作为 HTTP 的安全版本&#xff0c;在安全性、性能和用户体验上都有显著提升。本文将通过万字篇幅&#xff0c;结合图表和代码示例&#xff0c;详细剖析 HTTP 与 …

STM32F407VET6学习笔记11:smallmodbus_(多从机)创建新的slave从机

今日记录一些smallmodbus 创建新的slave 从机 的过程&#xff0c;以及使用的关键点. 目录 创建新的从机对应操作函数与buffer 创建新的从机线程与操作代码&#xff1a; slave使用的要点&#xff1a; 完整的slave代码&#xff1a; 能正常通信&#xff1a; 创建新的从机对应操作函…

【论文阅读】Transformer Feed-Forward Layers Are Key-Value Memories

Transformer Feed-Forward Layers Are Key-Value Memories 原文摘要 研究背景与问题&#xff1a; 前馈层占Transformer模型参数总量的2/3&#xff0c;但其功能机制尚未得到充分研究 核心发现&#xff1a;提出前馈层实质上是键值存储系统 键&#xff1a;这里的键与训练数据中出…

昇思+昇腾开发板:DeepSeek-R1-Distill-Qwen-1.5B 模型推理部署与 JIT 优化实践

目录 引言 模型推理部署 环境准备 安装 MindSpore 查看当前 mindspore 版本 安装 MindNLP 模型与分词器加载 导入必要的库 加载分词器 加载模型 对话功能实现 设置系统提示词 构建对话历史输入 推理函数实现 交互界面实现 推理JIT优化 基础环境安装 JIT 优化配置…

用phpstudy安装php8.2后报错:意思是找不到php_redis.dll拓展时

1.地址&#xff1a;https://pecl.php.net/package/redis/6.2.0/windows 2.下载3.解压后复制php_redis.dll到phpstudy_pro\Extensions\php\php8.2.9nts\ext目录 4.打开php.ini&#xff0c;加上 extension_dir “D:\software\phpstudy_pro\Extensions\php\php8.2.9nts\ext”

开源列式分布式数据库clickhouse

这里写自定义目录标题开源列式OLAP数据库clickhouseclickhouse使用 ClickHouse 的场景如何理解行式存储和列式存储clickhouse-go开源列式OLAP数据库clickhouse OLAP (分析型)&#xff1a;专为快速扫描、聚合、分析海量数据设计。OLTP (事务型)&#xff1a;专为处理大量短事务&…

Java Stream API 详解(Java 8+)

1. Stream 操作分类Stream 操作分为两类&#xff1a;中间操作&#xff08;Intermediate Operations&#xff09;返回新的 Stream&#xff0c;可以链式调用&#xff08;如 filter, map, sorted, distinct&#xff09;。惰性求值&#xff1a;只有遇到终止操作时才会执行。终止操作…

「源力觉醒 创作者计划」_文心大模型4.5系列开源模型, 从一行代码到一个生态:聊聊开源战略那些事儿,顺便扯扯文心大模型 4.5 的使用心得

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录从一行…

算法专题(二)回文链表

1、源代码class Solution {public boolean isPalindrome(ListNode head) {ListNode fasthead,slowhead; //快慢指针都在头结点//快指针走2步&#xff0c;慢指针走一步。//双数快指针最后是null&#xff0c;单数快指针下一位是nullwhile(fast!null && fast.next!null){f…

2025《艾诺提亚失落之歌》逆向工程解包尝试

前言 想开发一下光明之魂&#xff0c;看能不能解包《艾诺提亚失落之歌》的模型。 之前写了&#xff08;https://blog.csdn.net/weixin_42875245/article/details/148616547?spm1001.2014.3001.5501&#xff09; 沿用这个思路进行逆向工程解包。 文章目录请添加图片描述前言…

JVM 03 类加载机制

JVM 将字节码二进制流加载到内存称为类加载。 什么时候加载类 new 实例化对象。而对象所属类还没被加载。读取/设置类的静态非常量字段&#xff0c;常量字段在常量池。调用类的静态方法。类初始化&#xff0c;优先初始化父类。虚拟机启动时&#xff0c;先加载用户指定的主类。 …

STM32H7+FreeRTOS+LwIP移植EtherCAT开源主站SOEM

代码下载什么的就不多说了&#xff0c;直接看需要移植修改的代码。 1、osal.c修改 /******************************************************************************* * *** **** *** *** …

VijosOJ:中文信息学竞赛的二十年开源之路

VijosOJ&#xff1a;中文信息学竞赛领域的老牌开源在线判题系统 在中文编程教育与信息学竞赛的发展历程中&#xff0c;在线判题系统&#xff08;OJ&#xff09;扮演了至关重要的角色。它们不仅是选手训练的 “战场”&#xff0c;更是知识传递与社区交流的枢纽。VijosOJ&#x…

QPainter::CompositionMode解析

基本概念目标(Destination)&#xff1a;已经存在的像素。源(Source)&#xff1a;要绘制的新像素。组合模式&#xff1a;决定源和目标如何混合。总结SourceOver&#xff1a;源绘制在目标之上。DestinationOver&#xff1a;目标绘制在源之上。Clear&#xff1a;二者重叠区域被清空…

对接钉钉审批过程记录(C#版本)

钉钉开放平台&#xff1a;API总览 - 钉钉开放平台 按照开放平台操作指引&#xff0c;进入到钉钉开发者后台&#xff1a;开发者后台统一登录 - 钉钉统一身份认证&#xff0c;进行应用创建。 按照开放平台指引下载钉钉SDK&#xff08;新版&#xff09;。 在vs引入钉钉dll文件。 获…

AFSIM入门教程03.03:更新所有依赖库版本

系列索引&#xff1a;AFSIM入门教程索引 上一篇中更新了tiff库版本&#xff0c;本文将更新所有使用到的依赖库版本。 失败了 依赖库 首先获取哪些库被使用了。打开源码目录&#xff0c;搜索# Configure the 3rd_party&#xff0c;可以看到调用第三方库的代码。 官方提供的…

完美解决hive external表中csv字段内容含“,“逗号的问题

为解决hive表中csv字段内容含","逗号的问题&#xff0c;网上几乎都是说要用org.apache.hadoop.hive.serde2.OpenCSVSerde。 使用方法为&#xff1a; 1、mysql导出时&#xff0c;加一个ENCLOSED BY ‘"’&#xff0c; 示例&#xff1a; mysql -h 10.16.0.10 -P …

【Git】修改本地和远程的分支名称

其原理是&#xff1a; 对于本地&#xff1a;可直接修改分支名称&#xff1b;对于远程&#xff1a;不可直接重命名分支&#xff0c;所以应该将修改好名称的分支以新分支的形式推送上远程仓库&#xff0c;之后将新分支与远程新分支关联&#xff0c;之后可选择删除旧分支# 例子&am…

ubuntu24.04安装selenium、chrome、chromedriver

实验环境&#xff1a;kaggle notebook、colab notebook1、安装chrome!wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb!sudo dpkg -i google-chrome-stable_current_amd64.deb!sudo apt-get install -f!export QT_QPA_PLATFORMoffscreen!sudo…