InnoDB的数据锁–第1部分“简介”

2020-09-28 16:12:43 浏览数 (1)

作者:Kuba Łopuszański 译:徐轶韬

在这个博客系列中,我想向您简要介绍我最近两年从事的工作内容,改善InnoDB锁(表和行)的方式。 我希望从简单的情况和挑战的角度出发,逐步引入越来越多的现实世界元素,希望这种表达方式最终能够更容易理解。 如果您在本系列的开始部分发现内容并非完全正确,请原谅我–并邀请您在评论中讨论细节,或阅读本系列的后续部分。

这篇文章的摘要。在本文中,我将介绍以下概念,如果您已经熟悉它们,请跳到下一篇文章:

  • 数据库,表,行(它们的关系如同共享驱动器上的文件,文件中的电子表格以及电子表格中的行)
  • 事务的可序列化(用一个关于并行操作的故事来解释随时间观察到的状态
  • Amdahl定律(对于必须一次完成一个任务的并行度的限制)
  • 超时(用于行为不当的锁所有者,以解决死锁)
  • 读/写锁(共享/独占访问权限)
  • 互斥等待(读取不断涌入,写入等待)
  • 队列(FIFO或优先级)
  • 读取视图(只读快照,允许同时进行陈旧读取和新的写入)
  • 挂钟时间,向外通信及其可能与序列化顺序不一致
  • 读,写,读写事务(为什么SELECT FOR SHARE与常规SELECT产生不同的结果)
  • 访问权限提升(请求已拥有权限的访问权限)
  • 升级导致的僵局
  • 锁的粒度(对所有内容的访问权与仅所需资源的访问权)
  • 粒度导致的死锁,以及通过锁排序克服死锁的方法
  • 活锁

我尽可能用通俗易懂的方式回答这些是做什么的: 您还记得Excel吗?您知道列和行,也许还知道多个选项卡,您可能有多个文件。您可能不会有意识到这是一个很好的特性,无论您在单元格中放入什么,当您保存它,它就会保留在那里。也许有时您需要添加一个新行,或删除它以期简单地工作,即使有一些公式,例如涉及这些行的SUM(A1:A100),对吗? 您是否曾经不得不与另一个人在单个电子表格上进行协作?如何不覆盖彼此的工作?您是否使用一些共享服务器,一次只能允许一个人编辑文件?还是用邮件来回发送新的更改?也许你有一个“标识物”作为写入权限的标识在办公室里传递?

接下来是Google Spreadsheets,现在多个人可以通过某种方式在一个电子表格上进行合作。您甚至可以按“撤消”来撤消您的更改,但不能撤消其他人的更改。当单元格E13中已经有一个游标,并且有人同时在其上方插入一行时,系统会以某种方式知道您的值应该进入E14。

现在,假设有1000个人,不断地在多个文件的多个选项卡中添加,删除,修改单元格,期望它“将正常工作”,并且他们的SUM(A1:A100)公式将一直无故障地更新。

这就是我要面对的情况:有成千上万的客户端计算机,它们执行INSERT,DELETE,UPDATE,SELECT或更长的此类操作序列(称为事务),而编写它们的程序员通常会将这些处理事务的服务器想象为黑匣子,它只是一个接一个地应用它们,“一个人一次”。但是实际情况是,这太慢了,因此服务器会尝试并行执行这些更改,只要它可以假装向客户端说明它是按顺序进行的。也就是说,如果有人进行询问,服务器需要能够提出一个令人信服的关于时间轴的故事,该故事与观察到的数据库状态一致,就好像它一个接一个地处理客户端一样,即使实际上交错或同时执行其查询的某些部分。为什么?因为程序员如此假设,以使他们的生活更轻松。LAMP (Linux Apache MySQL PHP)堆栈隐藏大部分来自(PHP)的开发人员并行的复杂性,给他们事实的单一来源或“真相”。

但是服务器是如何实现的呢?如何制造一个令人信服的“谎言”?怎样才能更快地“撒谎”而不被发现?

作为第一个真正的坏主意,它可以一次将所有“ Excel文件”(数据库)的访问权限授予一个事务,等到事务完成后再向队列中的另一个授予权限。这与一次处理一个客户端没有区别,因为……它确实在一次处理一个客户端–多个并行连接在大多数时间里都在等待处理。没有真正的加速,没有兑现并行性的承诺。

这已经引入了第一个现实世界的问题:如果有人打开文件之后去度假却忘记了关闭文件怎么办?

为了解决这个问题,我们可以引入超时。制定一些明智的策略,例如:每个事务应在50秒内完成,如果没有,那么我们认为它已超时,然后回滚–撤消其所有工作并释放锁。清理是必要的,因为我们不想将未完成的工作暴露给他人,而且我们也不知道如何完成。麻烦制造者一旦休假回来,将得到有关该事件的通知,然后将有机会再试一次。

提高速度的一种方法是认识到某些人只想阅读,而不修改文件。甚至大多数人都属于此类,一般而言,创建内容要比消费内容更困难。所以,我们可以有两种接入方式:读取写入写入访问必须是排他性的,以防止其他写入的干扰,防止读取到正在进行中未完成的工作。但是,我们可以在大量的并发读取之间共享读取访问权限!这显示出极大的提高速度的潜力–如果写入读取所需的处理时间之比为1:9,则这9个可以并行进行,从而使时间从1 9减少到1 1 –五倍!

但是请记住,如果假设有人问,服务器必须能够呈现一个与所有读取和写入的观察一致的连续时间轴。 在我们的例子中,这很简单–数据库状态仅由写入更改,并且没有两个写入可以同时具有写入访问权限,因此我们可以使用写入的顺序作为时间线的起点–只需将它们放在时间线以相同的顺序(明确定义)获得其写入访问权限。然后,我们可以将读取的事务置于空缺中–必须注意将每个读取者置于他们看到的写入和他们没有看到的写入之间,这是可以做到的,因为我们知道读取者无法具有读取权限的同时还具有写入权限,因此对于每对读取和写入,都有一个明确的答案,即哪一个是第一(高级读者可能对我在存在时如何轻松逃避时间相对性问题表示嘲笑)多个服务器,复制和中子星等。请暂时忽略这些)。 请注意,尽管写入之间以及读取与写入之间存在确定的顺序,服务器可以完全自由地在写入标记的边界内安排读取。有n个阶乘可能性可供选择。也就是说,服务器可以说谎,没有人可以注意到!

这种 / 方法看起来很不错,直到您意识到一个细微的问题:如果读取不断来来往往,即使他们全都遵循“每人最多50秒!策略,它们这样做的模式,总是至少有一个读取者具有权限,因此没有写入者可以做任何工作?我们说读取者“饿死了”写入者。该怎么办?

解决此问题的一种方法是先进先出队列(FIFO)。我们引入了一条新规则:它不能看到被授予写入权限的人开始读取。它应该首先检查队列中是否有人已经在它前面等候,如果有人,则把自己排到最后,等待轮到它。因此,如果已经有一个写入在等待已经存在的读取完成并释放其读取访问权限,那么新读取者就必须加入该写入者的对列,而不是“只是因为可以”而加入。最终,(在我们这个简单的世界里只用了50秒),活动的读取者将完成(或被迫因超时回滚),等待写入者将获得珍贵的写入锁。请注意,这还避免了写入者可能会饿死读取者的对称问题(尽管在现实世界中不太常见)。

我们还可以使用优先级队列代替FIFO:由于某些原因,某些事务可能更为重要,并且可能会跳过队列。 在本系列的后面讨论感知拥塞的事务调度 (Congestion Aware Transaciton Scheduling,CATS)时,我们将重新讨论这个思想。特别是要考虑授予谁访问权限的一个因素可能是他们已经在系统中等待的时间,这是InnoDB使用先前实现(VATS)的基本思想。

您不仅可以提出新的队列排序规则,还可以更具创造力!记住你可以“撒谎”!如果不时(例如,每分钟)备份了所有文件,该怎么办?当读取者出现时,您可以为他们提供最新备份的版本。他们不必等待任何活动或未完成的写入者,他们可以立即开始读取旧版本的文件,这称为“读取视图”。每个读取者都有一个用于整个事务的读取视图。也许大多数读取者共享相同的读取视图,并发读取者可能只在少数几个不同的读取视图上进行操作-这是一个实现细节,只要我们能够摆脱现实的这种扭曲即可。 如果有人询问时间轴,服务器总是可以假装该读取“发生在”该备份之后的第一个写入之前。这确实很狡猾–虚构的时间轴仍然在与原位置相同的位置进行写操作,但是与进行备份的时间相对应,值得注意的是,读取者的事务被及时转移到不同的位置。他们怎么知道呢?嗯,它们只能判断是否有一些写入操作(从某个来源获知)在它们读取之前“发生”,但是在读取过程中,它们看不到写入操作的效果。比如说,我从一个同事Alice那里得知,她肯定把新的数字放到了报告中,但我却看不见它们! 或者,也许是我放的,我记得当时按了CTRL S。 或者,也许是我把它们放进去的,我记得这样做了,然后按CTRL S。

然而,除了在读取操作的同时进行写入操作之外,没有“正式”的方法可以“从某些来源了解”——这就是“事务”的含义。如果读取和写入不是同一“事务”的一部分,那么服务器始终可以通过说“ 嗯,也许,从她的角度来看,她认为自己输入了新的数字,但您知道,时空在这里如此奇怪地弯曲,以至于您也许仍然看不到效果,因为您仍在她的过去吗?”。您回答“ 但是我听到了她的声音!”。服务器说“抱歉,D“语音”不是SQL协议的一部分,也不是我们的服务水平协议(SLA)的一部分。我只是保证事务可以按某种顺序进行序列化,而不是我无法访问的向外通信也同意该顺序。如果您希望我考虑到您的通信,请在下一次通过XA PROTOCOL告诉我

如果我们真的很邪恶,我们可以每年进行一次备份,甚至在数据库清空时也可以进行一次备份,以免给我们在存储和提供真实数据时遇到麻烦!好的,这很烦人,但是这些只是可序列化性的规则。 通常,就像在这个虚构的故事中看到的那样,人们想要更多东西,例如,看到自己的作品,以及在要求新结果时至少与挂钟时间达成“某种”协议,并由他人以某种方式可以完成作品证明他们开始读取之前“发生过”。

通过在每次成功(“提交”)事务后进行文件的新备份(“所有数据库的快照”),并始终将最新的读取视图分发给传入的读取者,可以实现大多数这些愿望。可以确保读取视图包含特定的读取者对“已知要提交的事务”的更改。 (从技术上讲,如果在读取时,我听不到同事成功地将最新更改推送到文件中,仍然会存在一些问题,但这似乎与我在读取视图中看到的东西相矛盾,特别是与我的读取应该“之前发生过的错觉相矛盾”。但同样,服务器只会简单地回复:“您听到的谣言与它无关,如果您将来听到一些消息,那么也许您不应该相信自己的耳朵,而应该专注于屏幕上看到的内容”)。这种方法的好处是我们甚至不再需要读取权限——读取者只要喜欢就可以进行读取。另一方面,写入仍然需要排队以避免干扰。

具有读/ 写访问权限的想法基于这样的假设,即人们可以被整齐地划分为读取者和写入者。我们默默地假设写入者实际上也可以进行读取,这是合理的假设。我们还假设,写入者从其事务的请求中知道他们将要写入,因此需要尽早请求“ 写入”访问权限。实际上,我们中的许多人从阅读文档开始,然后才决定更改其中的某些内容。这需要从读取写入的权限提升。当读取视图使用快照时,看起来不是很明显–当他们开始编辑文件时,他们是否应该以某种方式看到文件更改为最新版本?现在,让我们假设如果事务执行写入操作,会弹出一个带有最新版本的新窗口-事务可以继续在旧窗口中读取旧的读取视图,或者在新窗口中使用最新的可编辑版本。 (顺便说一句:实际上,这就是为什么REPEATABLE READ隔离级别为SELECT * FROM t和SELECT * FROM t FOR SHARE提供不同的结果;在同一事务中–它可以访问两个“窗口”:这两个查询中的第一个使用读取视图,第二个使用最新版本的数据行)

让我们将读取视图搁置一会儿,然后回到具有读取写入权限的简单FIFO方法中,以了解 “访问权限提升”的工作原理。 假设一个事务已经具有读取访问权限,但是现在它想写入。 因此,它请求“ 写入”访问权限,这在到目前为止描述的算法中意味着它必须排队并等待所有读取者释放其“ 读取”访问权限。但是,它又是其中拥有读取访问权的人之一!为了避免在这里出现循环,我们需要引入一个简单的规则:寻找冲突的访问权限时,我们可以忽略自己。 让我们仔细分析这是否正确。特别是:具有这种新规则的服务器是否仍能提出令人信服的关于及时进行事务排序的谎言? 将这种“提升的”事务与时间轴上的点相关联,这是合理的,这一点与它被授予“ 写入”访问权限的时间相对应。那可以解释为什么其他并发的读取者没有看到它的行为,以及为什么它没有看到它背后的写入者的影响。到目前为止,一切都很好。

但是,如果将请求写入访问权的请求加入队列时,队列中已经有另一个正在等待的写入的程序,那会发生什么呢?我们可以跳过队列吗?还是我们应该等待?如果我们跳过队列,那么这似乎与时间线保持一致,在该时间线中,我们的事务在等待的写入者之前进行。但是,我们已经引入了一个漏洞:如果很多人开始像我们那样行事,那么可怜的写入者将无奈地等待着,因为人们假装只需要读取某些内容,然后提升了写入的权限。因此,新人们即使假装只需要读取权限,也不得不在写入者后面排队,只有已经在写入者前面的有限数量的读取者才能在轮到等待的写入者之前升级其访问权限。

如果我们不跳过队列,那么我们将陷入僵局–我们正在等待写入者,写入者正在等待读取者完成,但是我们就在其中。 (InnoDB的当前实现使用此后一种方法,因为它使用的代码更简单,并且死锁相对较少,可以通过提早请求写入访问来缓解这种情况。我们今后将尝试使用第一种方法。)

到目前为止,我们使用一个读、写锁来保护对所有数据库的访问。

但是我们可以找出一个更精细的方案!除了锁定所有数据库,我们还可以利用以下事实:如果一个事务只需要访问一个数据库,那么它可以与另一个在另一个数据库上工作的事务并行进行。这个想法类似于SharePoint锁定文件直到完成编辑,而另一个人无需等待即可处理另一个文件。希望我们可以找到许多这样的并行化机会,从而使更多的客户可以并行执行事务!

服务器能证明这一点吗? 当然,如果这两个事务没有任何共同点,那么它们中的哪一个在时间轴的前面就不重要了。只有同时查看两个文件的人才能了解它们的相对顺序,如果有人需要访问多个文件,我们还没有指定具体会发生什么。假设我需要将在Assortment.odt文件(简称文件A)中找到的一些数字复制到Balance.odt文件(简称文件B)。我会首先需要请求访问文件A,然后访问文件B .假设Alice使用写访问把数字写入A, Basil读取两个文件,先读 B,假设我的名字叫做Abe。

  • Alice写入文件A,
  • Basil读取文件B之后读取A文件,
  • Abe在B之前打开文件A

不管我们对操作进行交织有多疯狂,我们的锁方案是否都能使结果与Alice,Basil和ABe的顺序一致?如果您想知道潜在的困难是什么,请观察一下,如果根本没有锁规则,则可能会发生这种情况:Alice发誓将Assortment.odt文件中的Apples数字从0更改为10,我记得我看到了10个Apples并通过将“ Balance”从0 更改为10 在Balance.odt文件中进行保存,但Basil坚持认为他看到“ Balance”为10,然后Apples为“ 0”,表明我不能保存比实际更多的Apples。希望我们的锁方案不会导致这样的悲剧!

让我们看看遵循锁协议的服务器如何根据授予锁的顺序证明各种结果的合理性。首先,我请求对A的读取访问权限,而这种访问权限与Alice所需的写入访问权限不兼容,因此我或她首先获得了访问权限。因此,让我们在这里拆分故事(如您所见,这很快就会变得很复杂,因此,一旦您感觉到“分析所有可能的历史记录”的基本概念,就可以跳过本节)

  1. 现实1: 在这个故事中,Alice首先拥有“ 写”访问权限,因此在授予Abe“ 读”访问权限之前,我不得不等她完成。这意味着,当我查看A文件时,我看到了她的更改,即10个Apples,然后我尝试将其写入文件B,该文件需要具有与Basil所需的访问权限不兼容的访问权限。因此,无论是我还是他先进行,故事又再次分裂了。
    1. 现实1.1: 在这种情况下,我是第一个获得访问权限的用户,而Basil必须等待。仅在我将Budget = 10放入并单击保存并释放所有访问权限后,Basil才获得对文件B的读取访问权限,看到我的Budget = 10,然后对文件A的读取访问权限看到Alice的Apples = 10,满意并满足了我们的所有条件, 如果提出询问,服务器可以报告顺序为:Alice,Abe,Basil。
    2. 现实1.2: 在这个变体中,Basil 在我之前查看了B文件。因此,他看到Budget = 0。然后,他尝试获得对A的读取访问权限,并且鉴于Alice已经完成,并且我只需要读取访问权限,他可以毫无问题地查看它,看到Apples = 10。Basil认为Abe很懒惰,但在其他方面很有能力,不会给任何人加薪,但至少不会大声喊叫。他完成后,我将通过更新文件B来完成我的工作。 服务器可以报告的顺序是:Alice,Basil,Abe。 注意,这是一个微妙的谎言:实际上,Abe和Basil的工作是并行完成的,但是我们谁也说不清。
  2. 现实2: 在这个版本中,Alice迟到了一分钟,因此我必须使用Apples = 0来读取文件A的旧版本。然后,我请求对文件B进行写访问,并且故事再次发生在这里,这是Abe与Basil的比赛。

a.现实2.1:我在Basil之前就对文件B有了写权限。打开后,我看到Budget=0,所以决定不更改它,除了把字体颜色改为红色,以表示这是多么的悲哀。在我释放了访问权限之后,Basil对B文件的读取请求被批准了。他看到红色的Budget=0,挠挠头,决定查看文件A。故事在这里再次分裂,因为不清楚Alice是否已经设法更新了文件。Alice需要的写访问权限和Basil需要的读访问权限是互斥的,所以其中一个必须先进行

A.现实2.1.1:仅在Alice释放其访问权限后,Basil才具有访问权限。也就是说,在Basil查看文件之前,Alice已将Apples设置为10。然后Basil看到Apples = 10,再加上以前看到的Budget = 0,这意味着我很懒,但是至少我并没有凭空减少Apples。 服务器可以报告顺序是:我,Alice,Basil。 如果Alice 在Basil忙于检查文件B中的Budget时实际上更新了文件A,那可能是个小谎言,但谁在乎呢。请注意,即使我没有使用Budget = 0单元格的背景色,该故事也不会与顺序保持一致:Alice,Basil,我,因为我有能力,不会让Budget = 0看到Alice的10个苹果!

B.现实2.1.2:Alice今天来晚了,Basil在Alice必须写入之前获得了读取权限。因此,Basil认为Apples= 0,与Budget= 0一致。然后,Alice获得了对文件A的“ 写”访问权,将Apples修改为10,这一天结束了。 服务器坚持认为故事是这样的:我,Basil,爱Alice。 如果我不使用背景色,那也将与Basil 我,Alice 保持一致,因此服务器将有更多的摆动空间。

b.现实:2.2 Basil得到读取访问文件B之前,我有一个访问它。因此,Basil看到其中的旧Budget= 0。然后Basil尝试读取文件A。由于此访问权限与Alice 对同一文件的请求不兼容,因此其中一个必须是第一个要求访问权限的人。

A.现实2.2.1:Alice写入是在Basil读取之前。这个看起来很有趣,因为我们也知道在这两个现实的分支中,我的读取在Alice之前。所以只要看一下文件A的访问次数,我们就能发现顺序一定是我, Alice, Basil。这似乎与现实2.2的前提相矛盾。我写文件B是在Basil读了之后。我们该怎么做呢?我们能不能宣布2.2.1不是真实的情况,因为它不符合我们喜欢的谎言?不,那是作弊。我们必须证明这个场景是不可能的,只使用FIFO队列的访问权限语义和参与者所观察到的以及我们到目前为止所假设的内容。那就是:

(前提2.2.*)在我获得对B文件的写访问权之前,Basil获得了对B文件的读访问权。

代码语言:javascript复制
Basil.read(B) << ABe.write(B)

(前提2.*)在Alice获得对文件A的写访问权之前,我获得了对文件A的读访问权。

代码语言:javascript复制
ABe.read(A) << Alice.write(A)

(前提2.2.1)Alice的写入先于Basil在A文件队列中读取。

代码语言:javascript复制
Alice.write(A) << Basil.read(A)

我们还知道,Basil在读了B之后尝试读A

把这些合起来,我们得到:

代码语言:javascript复制
ABe.read(A) << Alice.write(A) << Basil.read(A) << Basil.read(B) << ABe.write(B)

有什么问题吗?首先,看起来Alice对资源A有写访问权,而我对资源A有读访问权,我只能在完成对资源B的写操作后才能释放它,这就是我们需要的矛盾!

现实2.2.2:Alice的写入是在Basil读取之后。Basil看到的是原来的0个Apples。Basil在两个文件里都看到了0。服务器可以报告的顺序是:Basil、我、Alice。

我希望您从上面得到的是几点看法。 首先,服务器可以根据每个文件的FIFO队列已知的部分顺序来拼接事务的最终顺序,或者至少将其用作起点。此外,还必须考虑这些部分顺序,因为它们(可能)被客户端(至少具有读取的部分可能会看到先前的写入)观察到。 其次,仅仅指定每个事务计划做什么还不足以描述它们在后台相互交错的所有各种方式。但在最后,服务器可以假装发生的事情相当于3!可能的排列: 1.1 Alice, 我, Basil 1.2 Alice, Basil, 我 2.1.1. 我, Alice, Basil 2.1.2. 我, Basil, Alice 2.2.1. 不可能. 2.2.2 Basil, 我, Alice 最后,要证明这一领域的一切,通常很困难,而且我甚至不确定这次是否做对了。例如,令我感到困惑的是,没有对应于Basil,Alice和我的现实世界情况……显然,由于Basil超过了Alice,我忘记了将案例1.2分为两个子案例。您看到它有多复杂吗?

当尝试模拟服务器并弄清楚它应该提供的时间表时,我发现一个有用的技巧是想象一个类似俄罗斯方块的游戏,其中每一列的块代表一个资源(一个文件),一个请求访问权限的事务对于给定的文件,从相应列的顶部放一个1×1块,其颜色对应于此事务。如果它落在地面上,则授予访问权限。如果不是,则事务必须在其下面的块的所有者之后的队列中等待(因此,列就像FIFO队列!):

上图显示了三个事务:trxR已经在等待trxG拥有的资源B,并且trxG正在请求访问资源C,并且将不得不等待当前拥有它的trxB。 当事务完成时,所有的块都消失了,并且其上方的块掉了下来(它们在队列中向前移动),而现在落到实处的那些块则被授予访问权限:

这个俄罗斯方块类比仅对独占访问权限有意义,因此,假设我们仅处理写入

现在,如果您有录制的游戏玩法,则可以重播它,但有一点点变化:完成的事务量不会消失。最后,您将获得一个静态图像,如下所示:

在上图中,事务创建了色彩斑斓的图层,其中每种颜色代表一个事务。 当要求服务器提供与已观察到的顺序一致的顺序时,它可以使用彩色层的(部分)顺序:如果一个事务在另一个之上,则必须是这样一个情况:在前一个事务释放权限之后获得权限,所以有机会在现实中看到前一个事务的结果,因此,事务必须按照虚构的顺序出现。对于上图中的示例,trxV必须位于trxY之后和trxO之后。 只要服务器提供的顺序与这些色彩丰富的层的顺序一致,就不会有人撒谎!

但是这些层是否真的部分有序?会不会有周期? 是的,很遗憾,可能会出现一个循环! 回到我们的示例,假设Basil想要写入文件A,而不仅仅是读取它。 可能发生的情况是我已经打开文件A进行读取,Basil已经打开文件B进行读取,然后我在等待获取对仍由Basil打开的文件B的访问权,而Basil想写入文件A,但我仍然在读取模式下保持打开状态。这是死锁周期的一种经典形式,在我们的俄罗斯方块世界中,它看起来像这样:

请注意,任何一层都不在另一层之上。 蛇的头在彼此的尾巴上!

我们如何发现这种情况?我们已经介绍了一种解决方案:超时。由于除了等待之外,我们没人能采取任何行动,因此我们当中至少有一个最终会超时。 然后其他人就可以完成。 请注意,超时涉及回滚事务引入的所有更改,这意味着继续进行的事务不会看到另一事务的更改,这是一件好事。就像方块从俄罗斯方块世界历史中完全消失一样,好像它们从来没有在那里一样。 更糟糕的情况是,我们俩都超时了,我们俩都重试并再次团结一致。然后再次。这被称为“活锁”,可以通过随机化两次重新尝试之间的退避时间来避免。

通过遵循一个简单的规则,也可以完全避免死锁:始终按字母顺序打开文件。如果我和Basil遵循这个规则,那么我有他需要的东西就不可能发生,反之亦然,因为我们每个人总是在字典上想要的东西比已经拥有的要晚。在俄罗斯方块世界中,这意味着每笔事务都只会将一个新块放到先前放置的块的右侧的一列中,因此这些层是从左到右构建的,这种情况是“一条蛇的头顶在另一条蛇的尾巴上,反之亦然”是不可能的,因为它要求其中一个向左移动。顺便说一句。这是LOCK TABLES所使用的策略–它在幕后对您提供给它的表的列表进行排序,并以升序获取锁。这也是为什么它要求您预先指定所有表的原因,

您可能已经猜到了,顺序在字面上按字母顺序并不重要–可以是任何顺序,只要它对于整个应用程序都是固定的即可。

但是,实际上,即使可以灵活选择自己的顺序,将应用程序构造为始终按给定的顺序获取锁也并非易事。因为InnoDB的内部代码使用这种排序技术来避免InnoDB本身所采取的低级锁之间的死锁,并且在团队,插件,层,模块和并行分支之间很难维护此规则。

因此,僵局是现实的一部分,并且会一直存在。我们如何检测到它们?这将是第3部分的主题。但是首先,我们将看到我们在这里讨论的抽象概念如何映射到InnoDB的Lock System对象:锁,这将是第2部分

感谢您使用MySQL!

0 人点赞