MySQL如何实现并发控制?(上)

news2024/11/15 5:08:27

前言

最开始学习数据库的时候都会被问到一个问题:“数据库系统相比与文件系统最大的优势是什么?”。具体的优势有很多,其中一个很重要的部分是:数据库系统能够进行更好的并发访问控制

那么,数据库系统到底是怎么进行并发访问控制的?

本系列文章以 MySQL 8.0.35 代码为例,分为上、下两篇尝试对 MySQL 中的并发访问控制进行整体介绍。本篇为上篇,将重点介绍表级别的并发访问控制

总体介绍

按照近些年流行的概念来讲,MySQL 是一个典型的存储计算分离的架构,MySQL Server 作为计算层,Storage Engine 作为存储层。所以并发访问的控制也需要在计算层和存储层分别进行处理。这里多说一句,MySQL 在设计之初就支持多存储引擎,这也是 MySQL 快速流行的重要原因之一,只是随着 MySQL 的发展,到 MySQL 8.0 时代,基本变成了 InnoDB 一家独大的情况。所以本文后续的分析,主要都是围绕 InnoDB 引擎展开。

从数据访问的角度,用户视角下,MySQL 的数据分为:表、行、列。MySQL 内部视角下则包括了:表、表空间、索引、B+tree、页、行、列等。在 MySQL 8.0 中,默认情况下一个表独占一个表空间,所以为了描述简单,本文后续内容对表和表空间不做区分。

回到主题,MySQL中的并发访问控制也是基于MySQL内部的数据结构来进行设计的,具体包括了:

1. 表级别的并发访问控制,包括 Server 层和 Engine 层上的表;

2. 页级别的并发访问控制,包括 Index 和 Page 上的并发访问;

3. 行级别的并发访问控制;

后续内容将围绕以上三个部分展开,本篇将重点介绍“表级别的并发访问控制”。

表级别的并发访问控制

我的DDL会锁表吗?

在使用数据库的过程中,一个绕不开的操作就是 DDL,特别是在线上运行的库上直接进行 DDL 操作。MySQL 的用户经常会疑惑的一个问题就是:“我这个 DDL 会不会锁表啊?别把业务搞挂了。”之所以会有这样的疑问,是因为在早期的 MySQL 版本中(5.6 之前),DDL 期间是无法进行 DML 操作的,这就导致如果是对一个大表进行 DDL 操作的话,业务会长期无法进行数据写入。为了减少 DDL 期间对业务的应用,衍生出很多三方的 DDL 功能,其中使用最多的一个是 pt-online-schema-change。

实际上,从 MySQL 5.6 版本开始,MySQL 已经支持 Online DDL 操作;到 5.7 版本,Online DDL 的支持范围进一步扩大,到了 8.0 版本,MySQL 官方进一步支持了 Instant DDL 功能,在 MySQL 上执行 DDL 基本上不会造成业务影响。

关于 Online DDL 的详细介绍,可以直接阅读官方文档[1],想直接看精简版的同学,可以参考笔者之前整理的一篇文章[2]。

[1] 官方文档:https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html

[2] http://mysql.taobao.org/monthly/2021/03/06/

MDL锁

DDL 是否会锁表其实就是表级别并发访问控制中最重要的一个问题。MySQL 中实现 DDL、DML、DQL 并发访问最重要的结构就是 MDL 锁。先看一个简单的例子:

CREATE TABLE `t1` (  `id` int NOT NULL,  `c1` int DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1, 10);INSERT INTO t1 VALUES (2, 20);INSERT INTO t1 VALUES (3, 30);

图片

在上述例子中:

  1. session 1 上模拟了一个慢查询;

  2. session 2 上执行了一个添加的 DDL,因为查询没有结束,所以 DDL 被阻塞;

  3. session 3 上继续进行了查询,查询也会被阻塞,用户觉得“锁表”了;

为什么会出现上述的情况?这里结合 performance_schema 下的 metadata_locks 表可以很清楚的看到等待关系:

图片

可以看到:

  1. session 1(THREAD_ID = 57)持有了表上的 SHARED_READ 锁;

  2. session 2(THREAD_ID = 58)持有了表上的 SHARED_UPGRADABLE 锁,需要申请表上的 EXCLUSIVE 锁,被阻塞;

  3. session 3(THREAD_ID = 59)需要申请表上的 SHARED_READ 锁,被阻塞;

从代码路径上,MDL 的加锁逻辑在打开表的过程中,具体的入口函数为open_and_process_table,具体的函数堆栈如下:

|--> open_and_process_table|    |--> open_table|    |    |--> mdl_request.is_write_lock_request|    |    |--> thd->mdl_context.acquire_lock // 请求 global MDL 锁|    |    ||    |    |--> open_table_get_mdl_lock|    |    |    |--> thd->mdl_context.acquire_lock // 请求 table MDL 锁

DDL 过程中升级 MDL 锁逻辑的入口函数为mysql_alter_table,具体的函数堆栈如下:

|--> mysql_alter_table|    |--> mysql_inplace_alter_table|    |    |--> wait_while_table_is_used|    |    |    |--> thd->mdl_context.upgrade_shared_lock // 升级 MDL 锁|    |    |    |    |--> acquire_lock // 请求 table MDL EXCLUSIVE 锁

通过上面一个简单的例子,我们知道了 MDL 锁的基本概念,也知道了所谓的 DDL 导致“锁表”的原因,严格的说,MDL 锁并不是表锁,而是元数据锁,关于 MDL 更深入的介绍,可以参考这篇文章,本文不再过多展开。MySQL 在 5.6 版本中引入了 MDL 锁,那么是不是有了 MDL 锁之后,其他的表锁就不需要了?

Server层的表锁

回答上面的问题前,先看一下 MySQL Server 层处理表锁的基本过程。MySQL 中任意表上的操作都需要加表锁,具体的入口函数为lock_tables,具体的函数堆栈如下:

|--> lock_tables|    |--> mysql_lock_tables|    |    |--> lock_tables_check  // 判断是否需要加锁|    |    |--> get_lock_data  // 计算有多少张表需要加锁,初始化 MYSQL_LOCK 结构|    |    |    |--> file->lock_count|    |    ||    |    |--> lock_external|    |    |    |--> ha_external_lock  // 调用 engine handler 接口|    |    ||    |    |--> thr_multi_lock|    |    |    |--> sort_locks|    |    |    |--> // 遍历加锁|    |    |    |--> thr_lock  // 加锁 or 等待|    |    |    |    |--> wait_for_lock // 锁等待,Waiting for table level lock

通过上面的堆栈可以看到,整个加锁的过程包括了以下步骤:

  1. 加锁前需要先判断对应的表是否需要加锁;

  2. 加锁时,需要先调用 Engine 层的 hanlder 接口加锁;

  3. 如果需要,再在 Server 层进行加锁;

对于 InnoDB 引擎,lock_count接口直接返回 0,表示 InnoDB 引擎的表不需要 Server 层后续再加表锁,直接在external_lock接口中完成所有的处理,这部分后面展开。对于其他引擎,以 CSV 引擎为例,lock_count接口返回 1,所以需要进入到后续的thr_lock加锁逻辑中。关于thr_lock加锁的类型,以及不同类型锁的冲突关系,此处不再做展开。

狭义上来说,thr_lock接口加的锁就是 Server 层的表锁,具体的加锁逻辑、锁类型的互斥关系、锁等待的逻辑此处不再展开,有兴趣的同学可以自己结合代码进行查看。

InnoDB中的表锁

前面提到,Server 层的 lock_tables 接口会调用 Engine 层的 Handler 接口,具体的会调用 external_lock 接口,那么 InnoDB 在该接口内会去加表锁吗?先看一下函数调用堆栈:

|    |--> // lock_type == F_WRLCK|    |--> m_prebuilt->select_lock_type = LOCK_X|    ||    |--> // lock_type == F_RDLCK && trx->isolation_level == TRX_ISO_SERIALIZABLE |    |--> m_prebuilt->select_lock_type = LOCK_S|    ||    |--> // others|    |--> m_prebuilt->select_lock_type == LOCK_NONE
|--> row_search_mvcc|    |--> lock_table(..., prebuilt->select_lock_type == LOCK_S ? LOCK_IS : LOCK_IX, ...)

通过上面的堆栈可以看到,进入到 InnoDB 层的加锁逻辑时:

  1. 只会先设置后续查询需要的锁类型;

  2. 普通的查询操作设置为 LOCK_NONE,后续查询过程无需上锁;

  3. 更新操作设置为 LOCK_X,后续查询过程中需要加表上的 IX 锁;

关于 InnoDB 层表锁的具体类型,以及不同类型锁的冲突关系,此处不再做展开。Engine 层的表锁情况,可以在 performance_schema 下的 data_locks 表中进行查看:

图片

LOCK TABLES操作

前面已经介绍了 MySQL 中的 MDL 锁以及 Server 层和 InnoDB 层的表锁,那么对应到 LOCK TABLES 操作上,到底加的是什么锁?先看一下 LOCK TABLES 操作的执行路径:

|--> mysql_execute_command|    |--> // switch (lex->sql_command)|    |--> // SQLCOM_LOCK_TABLES|    |--> trans_commit_implicit // 隐式提交之前的事务|    |--> thd->locked_tables_list.unlock_locked_tables // 释放之前的表锁|    |--> thd->mdl_context.release_transactional_locks // 释放之前的 MDL 锁|    ||    |--> lock_tables_precheck|    |--> lock_tables_open_and_lock_tables|    |    |--> open_tables|    |    |    |--> lock_table_names // 根据表名加锁(此时还没有打开表)|    |    |    |    |--> mdl_requests.push_front|    |    |    |    |--> thd->mdl_context.acquire_locks|    |    |    ||    |    |    |--> open_and_process_table|    |    ||    |    |--> lock_tables

从上面的堆栈可以看到,对于显式的 LOCK TABLES 操作:

  1. 会首先隐式提交之前的事务,并且释放掉之前所有的表锁和 MDL 锁;

  2. 在打开表之前,直接根据表名进行加锁(如果有其他事务未提交,可能会卡在这里);

  3. 然后进入到正常的打开表和加锁的逻辑;

用一个表格总结一下不同的 LOCK TABELS 操作的加锁情况(InnoDB 表):

图片

典型线上问题

关于 MySQL 中由于表锁导致的问题,举两个线上常见的案例:

1. DDL 操作导致的 MDL 锁等待。也就是前面在介绍 MDL 锁时举到的例子。其实这类是比较好发现的,直接执行 show processlist 就能看到大量的 MDL 锁等待,这里主要是说明一下如何处理此类问题。处理的方法主要有两种:

  • 借助performance_schema下的metadata_locks表,找到具体的MDL等待关系,然后进行处理(例如:kill 掉慢查询);

图片

  • 但是线上多数情况下并没有开启 performance_schema(担心有性能影响),所以也无法从 metadata_locks 表中查询到 MDL 等待关系。此时可以采用另一个方法:直接根据 Time 列进行排序(逆序),然后依次 kill 连接,直到锁等待关系解除。当然,也可以直接 kill 掉所有连接。

2. Server 层表锁导致的性能问题。典型的场景就是开启了 general_log,并且设置输出格式为 TABLE。由于 genelog_log 表是 CSV 引擎,所以需要通过 Server 层的表锁来控制并发插入,当写入量很大时,CSV 表的写入会出现性能瓶颈。从现象上看,就是大量的连接等待表锁“Waiting for table level lock”。CSV 表的写性能问题暂时没有好的优化方式,所以遇到之后最好的处理手段就是直接关闭 general_log。

表级别的加锁过程总结

以上就是表级别的加锁过程,做一个总结:

1. 最先加的是 MDL 锁,在打开表时(open_and_process_table接口)就需要根据操作的类型确定 MDL 的锁类型(实际上,大部分请求在词法解析阶段就已经完成了 MDL 请求的初始化);

2. 在实际的 SQL 操作时,会根据操作的类型,在不同的位置调用lock_tables接口加表锁,表锁又分为 Server 层的表锁和 Engine 层的表锁:

  1. 对于 InnoDB 引擎,直接调用 Engine 层的external_lock接口去加 Engine 层的表锁(通过前面的代码堆栈知道,其实只是确定后续需要加锁的类型,加锁动作是后置的),不需要再在 Server 层加表锁;

  2. 对于 CSV 引擎,Engine 层并没有实现external_lock接口,所以需要在 Server 层加表锁。

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

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

相关文章

yolov5-7转onnx并推理(包括缩放图推理与原始图片推理)

一、yolov5转onnx 先安装onnx, onnxruntime-gpu, ( pip install 就可以) 1. 静态模型: python export.py --weights yolov5s.pt --include onnx2.动态模型: python export.py --weights yolov5s.pt --include onnx --dynamic3.这里谈谈静态与动态的…

在虚幻引擎中实时显示帧率

引擎自带了显示帧率的功能 但是只能在编辑器中显示 , 在游戏发布后就没有了 , 所以我们要自己做一个 创建一个控件蓝图 创建画布和文本 , 修改文本 文本绑定函数 , 点击创建绑定 添加一个名为 FPS 的变量 格式化文本 用大括号把变量包起来 {FPS Int} FPS 然后转到事件图表…

【html】基础(一)

本专栏内容为:前端专栏 记录学习前端,分为若干个子专栏,html js css vue等 💓博主csdn个人主页:小小unicorn ⏩专栏分类:js专栏 🚚代码仓库:小小unicorn的代码仓库🚚 &am…

怎么录制游戏视频?精选5款游戏录屏软件

对于热爱游戏的你来说,记录游戏中的精彩瞬间并分享给朋友或粉丝,无疑是一种享受。然而,在众多录屏软件中,如何选择最适合你的那一款?今天,我们就为大家精选了五款游戏录屏软件,需要的朋友快来选…

【LeetCode:1014. 最佳观光组合 + 思维题】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

EfficientFormer实战:使用EfficientFormerV2实现图像分类任务(一)

摘要 EfficientFormerV2是一种通过重新思考ViT设计选择和引入细粒度联合搜索策略而开发出的新型移动视觉骨干网络。它结合了卷积和变换器的优势,通过一系列高效的设计改进和搜索方法,实现了在移动设备上既轻又快且保持高性能的目标。这一成果为在资源受…

【Python报错已解决】NameError: name ‘variable‘ is not defined

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 专栏介绍 在软件开发和日常使用中,BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

把任务管理器里面的vmware usb arbitrition停了,虚拟机一直识别不到手机设备了

在设备管理器--服务 里面找到VMware usb arbitrition服务,点击“启用”就好了。 参考大佬的文章: 吐血经验!!!解决虚拟机连不上USB!最全!_为什么vmware虚拟机不能连接上usb设备-CSDN博客

C语言 | Leetcode C语言题解之第432题全O(1)的数据结构

题目: 题解: //哈希队列 //哈希检查是否存在 //双编标维护次数顺序 #define MAXSIZE 769/* 选取一个质数即可 */ typedef struct hashNode {char* string; //字符串int count; //次数struct doubleListNode* dList;str…

箭头与数字识别系统源码分享

箭头与数字识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

JavaScript 安装库npm报错

今天在编写JavaScript代码时,缺少了包express。 const express require(express); const app express();app.get(/, (req, res) > {res.send(Hello, world!); });app.listen(3000, () > {console.log(Server is running on port 3000); });npm install exp…

【C++指南】C++中nullptr的深入解析

💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 引言 一、nullptr的引入背景 二、nullptr的特点 1.类型安全 2.明确的空指针表示 3.函数重载支…

从决策树到GBDT、随机森林

何为决策树 决策树(Decision Tree),它是一种以树形数据结构来展示决策规则和分类结果的模型,作为一种归纳学习算法,其重点是将看似无序、杂乱的已知数据,通过某种技术手段将它们转化成可以预测未知数据的树…

3D 模型GLTF、GLB格式文件介绍使用

一、介绍 GLTF(GL Transmission Format)和 GLB(GL Binary)是用于在 Web 和各种应用程序中传输和加载 3D 场景和模型的开放标准格式。它们由 Khronos Group 开发,旨在提供一种高效、可扩展且易于使用的 3D 内容格式。以…

Java项目中Linux跑起来

要让一个web项目跑起来首先需要tomat和jdk的包(Linux版本) 之后可以使用Xftp工具将包传到linux中 可以新建一个java包专门放这些文件 之后将其这些包解压出来 tar -xvf jdk-8u141-linux-x64.tar.gz //换自己的包名使用命令配置自己的环境变量 vim /et…

计算机专业选题推荐-基于python的协同过滤酒店推荐系统

精彩专栏推荐订阅:在下方主页👇🏻👇🏻👇🏻👇🏻 💖🔥作者主页:计算机毕设木哥🔥 💖 文章目录 一、协同过滤酒店…

【C++ 11多线程加速计算实操教程】

【C 11多线程加速计算实操教程】 1. 了解线程的基本概念2. 创建线程2.1 启动线程的基本示例:2.2 运行结果 3. 线程加速计算3.1 演示如何使用多个线程计算数组的和:3.2 运行结果3.3 结果分析3.4 拓展学习 4. 互斥量(Mutex)4.1 演示…

Qt中多语言的操作(以QtCreator为例)

1、首先,我们在代码中与文本相关的且需要支持多语言的地方,用tr来包含多语言key(多语言key是我们自己定义的),如下 //举例 QPushButton* btnnew QPushButton(this); btn->move(20,20); btn->resize(100,50); //…

vue.js 展示一个树形结构的数据视图,并禁用其中默认选中的节点

功能描述 展示树形结构&#xff1a; 使用 Element UI 的 <el-tree> 组件展示树形结构数据。数据由 content 数组提供&#xff0c;树形结构包含了嵌套的节点及其子节点。 默认选中节点&#xff1a; 使用 defaultCheckedKeys 属性指定默认选中的节点。这些节点在树形结构渲…

自动换行且带下划线的居中长标题的论文封面一种绘图实现

自动换行且带下划线的居中长标题的论文封面一种绘图实现 引言 在一些学位论文的封面上要求标题带有下划线&#xff0c;但长标题的情况下标题自动换行后下划线就会面临一些问题。 因此&#xff0c;往往需要一些特殊的处理。 在《如何制作自动换行且有定长下划线的论文封面模板…