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

前言

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

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

本系列文章以 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.xdnf.cn/news/148826.html

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

相关文章

通过 Flink 的火焰图定位反压

在 Apache Flink 中,Web UI 提供了丰富的监控工具来帮助用户分析和解决作业性能问题,其中火焰图(Flame Graph)是用于分析反压问题的一个强有力的工具。反压可能是由于作业中某些算子处理速度过慢,或者资源耗尽导致的。…

【解密 Kotlin 扩展函数】扩展函数的底层原理(十八)

导读大纲 1.1.1 从 Java 调用扩展函数1.1.2 扩展函数无法重载 1.1.1 从 Java 调用扩展函数 在编译器底层下,扩展函数是一种静态方法,它接受接收器对象作为第一个参数 调用它不涉及创建适配器对象或任何其他运行时开销这使得从 Java 使用扩展函数变得非常简单 调用静态方法并传…

使用k8s部署RainLoop-Webmail

说明 * rainloop最新源码官方下载地址:https://www.rainloop.net/downloads/ * 系统要求:https://www.rainloop.net/docs/system-requirements/ * 安装文档:https://www.rainloop.net/docs/installation/ * 更多详细资料请查看官方文档 * do…

HDL coder使用手册

💡 由于本科毕设女朋友准备使用FPGA完成,因此写这篇文章帮助她快速上手HDL coder的使用,降低前期入门的难度。 支持生成HDL代码的simulink库 名字中含有HDL的库中的模块一般都可以用来生成HDL代码。直接搜索模块名称,比如搜索fir&…

管道检测与识别系统源码分享

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

C++进阶学习——模版进阶

1. 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成…

寄大件快递用什么物流更便宜,寄20-200公斤大件价格对比

大件货物,大件行李,大件电器用什么物流快递更便宜呢? 新生入学,放寒暑假,新单位入职,搬家换工作的时候,都会遇到大件行李货物要邮寄的情况。这些都属于物流中的寄大件服务,在快递费…

隐私计算相关知识

WOE( Weight of Evidence)编码 一种在数据分析,尤其是信用评分和欺诈检测等领域中常用的特征编码方法。它的主要目的是将分类变量转换为数值变量,从而使得模型能够更好地理解类别与目标变量之间的关系 IV( Informatio…

大数据毕业设计选题推荐-网络电视剧收视率分析系统-Hive-Hadoop-Spark

✨作者主页:IT毕设梦工厂✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、PHP、.NET、Node.js、GO、微信小程序、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇…

如何在平板电脑上用谷歌浏览器观看高清视频

在数字时代,使用平板电脑观看高清视频已成为一种流行的娱乐方式。Google Chrome浏览器因其快速、简洁和兼容性强的特点,成为许多用户的首选。本文将指导您如何在平板电脑上设置和使用Chrome浏览器来享受高清视频内容,同时融入一些提升浏览体验…

沃尔玛、亚马逊、Temu提升产品曝光度的实用技巧:测评补单

在当今竞争激烈的市场环境中,对于一家新开店铺或新上市产品而言,快速实现销量增长往往是一项艰巨的挑战。由于缺乏初始的市场认可,潜在消费者通常会对新品牌或产品产生犹豫。因此,提升店铺和产品的曝光率是实现快速出单的首要任务…

文档加密,如何设置?加密文档的10个小妙招值得参考!(电脑文件安全加密)

文档加密,如何设置? 是不是经常担心电脑里的重要文件被人偷看?别担心,学会这几招加密小技巧,就能给文件穿上"隐形衣"。不管是个人隐私还是公司机密,都能得到妥善保护。 接下来,咱们…

数组组成的最小数字 - 华为OD统一考试(E卷)

2024华为OD机试(E卷D卷C卷)最新题库【超值优惠】Java/Python/C合集 题目描述 给定一个整型数组,请从该数组中选择3个元素组成最小数字并输出(如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 一行用半角逗号…

2024年陕西省安全员B证证模拟考试题库及陕西省安全员B证理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年陕西省安全员B证证模拟考试题库及陕西省安全员B证理论考试试题是由安全生产模拟考试一点通提供,陕西省安全员B证证模拟考试题库是根据陕西省安全员B证最新版教材,陕西省安全员B证大纲整理…

中国可观测日「成都站」圆满落幕

在数字化转型的大潮中,企业对于系统的稳定性和可靠性提出了更高的要求,而可观测性平台正是确保业务连续性的关键技术。9月20日,中国可观测日成都站的活动圆满落幕,为技术专家们提供了一个宝贵的平台,深入探讨了可观测性…

【BetterBench博士】2024年华为杯E题:高速公路应急车道紧急启用模型 Python代码实现

题目 【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析 【BetterBench博士】2024年中国研究生数学建模竞赛 E题:高速公路应急车道紧急启用模型 问题分析 【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动…

【Transformers基础入门篇4】基础组件之Model

文章目录 一、Model简介1.1 Transformer1.2 注意力机制1.3 模型类型 二、Model Head2.1 什么是 Model Head2.2 Transformers中的Model Head 三、Model基本使用方法3.0 模型下载-浏览器下载3.1 模型加载与保存3.2 配置加载参数3.3 加载config文件3.2 模型调用3.2.1 带ModelHead的…

*C++:string

一.STL简介 1.STL STL(standard template libaray- 标准模板库 ) : 是 C 标准库的重要组成部分 ,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架 。 2.STL六大组件 二.标准库里的string类 标准string库网址&#xff1…

朴世龙团队《Global Change Biology 》研究成果!揭示生物累积效应对秋季叶片衰老的重要调节作用!

本文首发于“生态学者”微信公众号! 在全球气候变化的背景下,生态系统的季节性变化,尤其是植物的春季叶片展开和秋季叶片衰老(EOS),对碳循环和区域气候调节起着至关重要的作用。然而,关于秋季叶…

腾讯云点播及声音上传

文章目录 1、开通腾讯云点播2、获取腾讯云API密钥3、完成声音上传3.1、引入依赖3.2、参考:接入点地域3.3、参考:任务流设置3.4、首先修改配置:3.4.1、 3.5、TrackInfoApiController --》 uploadTrack()3.6、VodServiceImpl --》 uploadTrack(…