MySQL 锁定机制与实践:做一名称职的 DBA
这是入门级的 MySQL 锁定机制与实践。主要介绍源码入口等东西,帮助大家以后更顺利的阅读源码。
首先介绍一下MySQL的两层架构。在所有MySQL数据库中,除了MySQL之外,没有其他数据库具有此功能。 MySQL的两层架构将服务层和存储引擎层一分为二。从层次上看,服务层集中了所有通用功能,例如网络通信和语法分析。
存储层做数据相关的存储。比如它使用内存存储,像赖老师介绍的memcache,就是内存存储,还有文件存储,像InnoDB。
它有什么好处?这使得这个数据库非常灵活。然而,MySQL的两层存储也存在其问题和缺点。既然是关系型数据库,那么也就涉及到事务。在处理交易的时候,我们使用了几个引擎,在使用的时候一定要注意这个问题。
下面我们就来看看吧。这是一个非常经典的MySQL架构。它列在这里。从客户端,我使用的东西,它发送的消息或请求,到MySQL服务器,它的语法分析或优化部分,以及这些东西,这在很多方面都是MySQL。如果我们把底层这个公共部分去掉,它就集中了这个部分,使得这个部分变得非常好。下面可以连接各种存储引擎,比如InnoDB、MEMORY等。它不支持交易。如果您共享两者,则应小心。
BLACKHOLE 是一个非常有趣的引擎。如果以后你想做的话可以介绍一下。它实际上并不插入数据。我们用它作为中继。 binlog存储在本地,然后传输到下一个节点。这体现了MySQL非常的灵活性。 。
我们来谈谈这两种类型的锁。 MDL 锁位于服务器层。当我们建表的时候,比如表和数据库元数据信息,都是受到服务器保护的。这些访问需要 MDL 锁。 InnoDB是一个存储引擎。它实现了数据库中最标准的隔离级别。这部分还实现了锁。 InnoDB在这一层实现了这个锁并处理并发数据操作。
我们来看看元数据锁和InnoDB锁都包含哪些内容。元数据锁,正如我刚才所说,锁定数据库对象。
所谓的数据库对象包含什么?首先,它有一个全局对象。这个对象并不经常遇到每个人。事实上,我们在维度标注过程中最常见的就是全局读锁。事实上,这就是全局锁存在的原因。
还有数据库对象、表对象、存储过程对象、函数对象和触发器对象。这些都是服务器层涵盖的概念。元数据锁与这些锁相对应,它保护我们不去存储每一行数据。 ,下面是表锁和行锁。表锁对于InnoDB来说非常罕见,很少遇到。这里主要介绍一下InnoDB中间隙的实现原理。
我们来看看,介绍一下MySQL的元数据锁信息。我想从这几个方面来谈谈元数据锁。第一个元数据锁之间存在关系,元数据锁的类型,元数据锁中每个对象的元数据锁都有一些子关系,而当这些元数据锁被请求和释放时,我们如何读取它们呢? ,我们还需要了解如何读取元数据锁的源码。源码是大家最感兴趣的切入点,我们也会结合案例进行分析,教大家如何阅读MySQL的源码。
以下列出了所有 MySQL 元数据锁类型。我们之前提到过全局。我们应该如何谈论它?上面它做的最高级别是全局锁,也就是实例级锁。
下面是表空间。对于MySQL的这个参数设置来说,TABLESPACE实际上就是每个表的一个表锁。它下面还有一个table表锁,实际上可以理解为TABLESPACE的物理锁,表锁。表对象上的锁。
还有一个表格。 Schema 是 MySQL 中的一个概念混乱。元数据锁对应于表单的对象。下面还有FUNCTION锁、PROCEDURE锁、TRIGGER锁。这三个锁,提交锁。当我提交提交时,它必须持有允许 COMMIT 被序列化的锁。它并不常见,但每次提交时都必须存在。与全局类似。
还有USER LEVEL LOOK,这是MySQL对外提供的。使用MySQL时,可以使用外部提供的锁和元数据锁。您可以在两种不同的锁之间进行选择。如果使用这个锁,就可以实现它们之间的同步。
还有开锁服务。实际上,还有更进一步,USER_LEVEL_LOCK。前面的 USER_LEVEL_LOCK 有两个与之相关的函数。您可以查看 MySQL 手册了解此功能。它明确提供了一个可以连接这两个功能的接口。 ,锁定同一个对象,两者之间可以同步。
类型的数据锁实在是太多了。您可以在这张图片中看到数据锁之间的情况。全局锁定下,它所谓的条件是什么?这里的问题是,在大多数情况下,如果我想在其中锁定一些元数据,它有一些预锁。
在我锁定这把锁之前,我锁定了其他对象。我称之为关系。看关系箭头指向什么,关系是从属的。
比如我锁表空间的时候,加了全局锁,所以看图就知道,底层操作的时候,需要很多锁。根据这一行,它需要很多锁,当我们执行以下操作时可能会发生这种情况。更多冲突。这并不意味着会发生更多的冲突,而是必须持有更多的锁。
我们来谈谈元数据。锁有申请和释放的过程。元数据锁应用程序有多种类型。当你申请锁的时候,它只在你使用的时候才适用。
但是如果释放的话,就会按照这三种类型来释放,而这三种类型表示当程序运行到某个点的时候,这些锁会在固定的点被释放。比如这里的语句锁是我们执行一组SQL操作时添加的。
当元数据被锁定时,它会锁定元数据操作。如果指定了 STATEMENT,则当 STATEMENT 退出时,它将执行计算。当它发现有 STATEMENT 锁时,它会在此时继续。释放了。
如果你看下面这段话就更清楚了。首先,有STATMENT级别、TRANSACTION级别、EXPLICIT三种类型,明确规定。
当你看这个过程时,你可能会更清楚。一开始我说过,你在执行时会获取这些锁。例如某个元数据锁之前设置了MDL STATEMENT锁。刚才说一批SQL过来了,这条信息设置的锁全部释放了。
比如操作这张表时,首先要设置MDL锁。执行完成后,释放锁。如果你不放弃它,那就很容易了。其他人如果继续下去肯定会被封禁。 STATMENT锁,和TRANS锁一样,一个事务批量执行后,所有的MDL锁都会被释放。事务完全提交后,所有锁将首先被释放。
我们再说一遍,MDL锁,它的信息量很大,我们在看源码的时候常常会被自己搞糊涂。现在你知道了,这其实只是一件小事。当你不知道的时候,你就想撒网看看。刚才我们介绍了源数据锁内核、其类型、释放点之间的关系。这些是最重要的地方。看到当我们看最重要的东西的时候,我们看源码就可以看到红色虚线部分。红线是MDL核心源代码。当我们研究MDL如何工作时,我们会深入研究MDL.h和MDL.cc。在定义之前提到了这几种类型的宏(这里假设您有C语言经验并且了解宏的概念)。
通过这些宏我们找到了相应的信息,红框对应的文件,加锁和解锁的操作,以及这里定义的函数。使用这些函数,此处使用以下函数。
核心,这是您查看代码并查看哪些功能最重要的地方。一般来说,在查看代码时有一些最重要的功能。也就是说,底层的功能更重要。每个人都排在最后。在这个例子中,无论是什么,最终都会落入函数中,一切都在其中。说明,无论是哪种锁,最终都是通过这个函数来实现锁的。
所以如果你在调试MySQL的时候,想要调整MDL相关的代码来设置断点,就需要在这个函数中设置断点。该函数是最底层的函数,但下面的并不完整。我只举几个例子。
比如lock方案,如果你做了这个操作,最终会通过这些落入到这个函数中,所以当我们想看这个操作是如何进行的时候,我们在这里设置一个断点,我们可以通过这个堆栈来理解一系列对话是什么样的。 。
这是关于元数据锁和 PFS 的。一旦元数据被锁定,我就会等待。我在等待什么样的锁?就是这样。当我们在 show processlist 或 Performance_schema 下查找metadata_locks 表时,我们会读取此信息。其实这个信息和我们的锁是一一对应的。
讲一个网络上出现的问题,如何查看源码查找原因。这是一个帮助大家强化MDL源代码概念的例子。
这是我们在网上遇到的一个现象。当我们重新备份时,大约有 160 个连接被阻止。情况如图所示。其中有一个时间。当你处理这个问题的时候,直接连接最长的时间,整个问题就解决了。当我们解决了问题之后,我们还需要寻找原因。了解了这个原因之后,我们就可以避免以后出现类似的情况。再次发生。
当我们读到这篇文章时,这是最可疑和最长的文章之一。抱歉,这里不包含它。当时对一张大表进行了很长的操作。理论上,这个操作不应该被锁定。这么多数据。
这里取出的部分是唯一的。当你执行维护操作的时候,只有这个东西才会锁住整个全局。在这种情况下,我们检查为什么前面的查询导致全局锁稍后出现。 ,当时分析的一些结果。
我们当时就调整了,因为我们已经知道用读锁刷新表会导致其他事务阻塞。我们来看看它在这里面做了哪些操作。我们之前看到的信息问题非常重要。等待全局读锁元数据锁会导致问题。
我们在这里设置断点。我们使用读锁执行刷新表来获得堆栈提升。在故障排除时,我们重点关注堆垛机。以后遇到问题向别人寻求帮助的时候,最好能抓住他们。像这样堆叠电梯。
如果你看阿里巴巴或者腾讯发表的文章,意思是说,当他们谈论某件事的时候,他们会拿出栈电梯来看看当时发生了什么。
当我们查看堆栈提升时,我们很兴奋地在这个函数中设置一个断点,然后调用它。再往上就是这个功能。此时,我们知道该函数中发生的情况是我们不希望它生成全局锁。
再看一下这段代码,通过堆栈找到代码。这段代码上面我们看到了什么?设置了读锁之后,后面就是这个了。设置锁后,会关闭缓存的表,我们知道flush table操作被锁了。加载完成后,它会创建一个关闭的缓存表。
对于我们之前的长时间操作,必须从头开始。在完成之前它不会释放这些。一旦执行,它会在这个位置等待,直到另一个位置完全执行,因此之后会等待全局保持。读锁,执行元数据操作并添加读锁来获取信息,然后一切都被锁定。
这样,在这个断点之后,一切前因后果都可以找到。通过这样的运维或者开发,当你提出非常要求的时候,只要进行了上面的运维操作,其他的可能就会卡住。通过这样做,将几千万行数据通过网络传输到本地。这种情况下,如果被别人杀了,就会影响到其他人。
这个东西分析完之后,你就可以明确的告诉开发者或者以后的人,这个东西不能这么做。这是源代码的东西,而且是合理的。
关于 MDL,能说的就这么多了。就像我之前说过的,一旦你知道了就很容易,但如果你不知道,你仍然不知道。这是大家之前注意到的。
我们来谈谈InnoDB锁。我将在这几个部分进行介绍。事实上,InnoDB锁非常复杂。 PPT只能介绍其中的一部分。本节仅用于在这种情况下实现间隙锁。 ,我也会介绍一些前提知识,事务隔离级别,在讲InnoDB的时候,我会讲锁级别,间隙锁,上面的MDL告诉了源代码主要的地方,重要的输入在哪里。我也说一下我在这方面对携程所做的源码修改。
首先说第一个前提知识,包括事务隔离级别的三种现象,脏读、不可重复读、幻读,第二个是事务隔离级别,即未提交读,阅读承诺及其状态。因为在我工作的过程中,其实有很多人对这一点理解得不够透彻。我个人认为理解不够透彻。我想介绍一下这个东西。也许很多人已经很明白了。我会更详细地解释它。一场演讲。
先说脏读。什么是脏读?我就以赖老师为例。我熟悉他是为了帮助大家直观的理解。赖老师做一件事,写一件事。我正好路过,看了一眼。当我看到赖老师写的东西时,我说赖老师写得很好,所以我就把它传给了他。但赖老师觉得不满足,就根据数据库回滚了,不再要了。如果它传播开来,这就是脏读。如果其他人读取回滚数据并使用它,这就是脏读。
只有在提交读确认的隔离级别时才会发生脏读。为了避免脏读,显示了读提交隔离级别。你只需要提交。现在,赖老师说写稿子。给我最后的手稿。我读完后,提交的内容是这样的。如果我把它拿出来使用它,它运行得很好。此提交已阅读。
但是阅读投稿的时候也存在一个问题。赖老师做了第一个东西,给别人用,但是赖老师又修改了一遍,变成了第二个版本。当我拿到初稿时会发生什么?当你把第一个版本的手稿拿给别人使用的时候,第二个版本其实已经出现了。至此,就变成了无法再次读取的现象。
操作过程中我看了两遍,也就是两次遇到了两个不同的版本。在这种情况下,它是可重复读取的隔离级别。很多数据库实现的时候,里面都有一个MVCC。这样,一个事务会读取我的事务之前的版本以及我的事务之前的最后一个版本,从而避免不可重复的读取。
但是在可重复读隔离级别以下还存在另一个问题。以一个问题条件为例,赖老师写了一篇稿子。这时候我只能送这一篇,也只能引用这篇稿子,但是又生成了第二篇。第二遍读完一篇稿子,发现赖老师的第二篇稿子出现了,成了幻读现象。这种幻读现象是什么?
我在一笔交易中的搜索结果是分层次的,引入了序列化。当你看到这种级别的事务隔离时,你可以思考一下前人是如何将数据库逐渐发展到今天的样子的。一开始没有人知道这件事。没有人知道事务隔离级别。它们都写在那里。这种情况逐渐显现出来。这也是软件的开发。的概念。我推荐一本关于软件开发的书,这是一个非常有趣的现象。
介绍完锁的基本原理,我们就可以深入了解InnoDB的细节了。 InnoDB有表锁、行锁和间隙锁。列锁是行锁类型。特殊操作和特殊标识符可以认为是Gap lock。
有意向共享锁、意向排它锁以及共享锁、排它锁、自动增量锁。自增锁是InnoDB本身实现的自增列锁。这是全局锁,排他性就是这样。是直接列矩阵,兼容性是这样的。一般情况下,不会遇到IS锁和X锁冲突的情况。 S锁和X锁放在表级锁上,IS锁和IX锁放在条目级锁上。
我们来说一下InnoDB的间隙锁原理。间隙锁主要用于可能读取不足的情况。事实上,MySQL的第三个隔离级别——可重复读级别,部分实现了序列化并保证了事务的结果。水平基本一致,是通过插入这个间隙锁来实现的。
我们先建一张表,插入这四行数据。我们如何理解这个间隙锁呢?如果我们开始; select * from t_lock where f2=16 用于更新上一个表中没有数据。在查询过程中,实际上是找到大于查询条件的最小记录。它首先找到该记录。找到这条记录后,给这条记录的行锁添加一个特殊的标签。当其他事务插入它时,插入时必须查询插入点。
比如我插入15的时候,通过前面的查询肯定会找到17。此时,当它插入时,发现在17处有一个行锁,并且这个行锁上添加了一个特殊的标识符。该标记成为间隙锁,此时将被锁定。
为什么会有这样的间隙锁?就达到了行级别不变的情况。例如,如果您插入另一个等于16的语句,则17将不会被锁定。
有人进来输入16,这次查询的结果可以进行第二次查询。提交后可查询结果级别。这就是间隙锁的作用。
每次查找一行记录,在这种情况下,每一行记录都被锁定。这是间隙锁的实现。这样就可以避免在结果级别重复查询时出现行级别的不一致。这是刚才提到的锁定事物的操作示例。
我们再来说一下间隙锁,看看内存中间隙锁打印的结果。 MySQL的锁结构很简单,就是打印整个锁,锁在哪个事务,锁在哪个索引上。这是C中的一种特殊语法结构,它告诉你之前的锁是表锁还是锁锁。这是一个特殊的标记,分为十六进制,是512。这是间隙锁打印在间隙锁存储器中的一个例子。
看核心源码和这些函数。如果有兴趣,就从这些 Hanshu、locko、lock.h、IS、IX 类型、行锁类型或表锁类型入手。这些类型都是在这些.h里面定义的。如何锁定位于 trx0trx.cc、trx0sys、trx0rec.cc 中。这是实现MySQL事务(例如行锁)非常重要的核心代码。
先说一下携程对相关交易锁的改造。这是我们遇到的最常见的现象。当发生死锁时,这是必须发生的。下面的红色框提供了一个死锁。此操作会产生死锁事务。 ,上面是它之前的一个事务,假设我们有一个循环,这个操作会导致死锁,直到后面产生一个闭环,这就是一个死锁。
打印此信息。这些信息的格式不够好,生成的内容不容易阅读。第二点是我们无法打印死锁涉及的所有交易。针对这种情况,我们确实做出了改变。我们希望将所有这些信息格式化并直观地显示出来。我们格式化输出并将其输出为这种格式,然后是一个数组来打印所有交易。
这里,之前我们看到图中打印了一条锁定信息,我们操作的时候,一笔交易中的所有锁定信息都被打印出来了。在可视化操作的过程中,提取出之前所有的输出信息后,就可以看到。有了这样的结果,这对于我们线上运维来说是相当有用的。然而,有一个问题你说不出来。这里打印的信息还不够完整。我们无法打印所有涉及锁的操作,即生成的语句。当我们改进它时,我们有一个支持项目来构建完整版本的 MySQL。 trx输出,其实运维可以通过这个网上的信息,拿出当时开发在做什么,定位开发问题。
InnoDB和锁的源码我就讲完了。我将介绍一下我个人学习源码的经历。希望对大家学习源码有所帮助。
首先要先了解数据库的大部分功能,然后再研究源码。否则,如果直接拿源码来看,通常会一头雾水。因为像MySQL这样好的源码写得很好,很多函数名和变量定义都可以和函数直接相关。如果你不熟悉该函数并直接查看源代码,通常不会发现它的相关性。
其次,观察别人的源码学习。如何观察?网上很多人提供了很多功能文件。当你得到这个文件时,你就会知道实现了哪些功能。当你得到这个文件时,你就会知道执行了哪些操作。这样你就可以推断出这个函数的作用以及这个函数是如何实现的。你想要一个直观的印象。 ,通过具体的功能点嵌入,源码的功能还是有点大海捞针,下一步可以更有针对性的研究一下。
还有最后一步。最后就是我们在研究源码的时候,可以拓展我们对整个数据库的理解。就像我之前介绍的MDL一样,其实我最早在没有研究这部分源码的时候,这些东西我都知道了。我不知道。通过最终的源码返回,我们可以加深对功能的理解。这些功能可以帮助我们做在线运维方面的其他事情。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。