解锁 Postgres 锁管理器。 布鲁斯·莫吉安

Bruce Momjian 2020 年演讲“解锁 Postgres 锁管理器”的文字记录。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

(注:幻灯片中的所有 SQL 查询均可从此链接获取: http://momjian.us/main/writings/pgsql/locking.sql)

你好! 很高兴再次来到俄罗斯。 很抱歉去年我没能来,但今年伊万和我有宏伟的计划。 我希望能更频繁地来这里。 我喜欢来俄罗斯。 我将访问 秋明, 特维尔 。 我很高兴能够访问这些城市。

我叫布鲁斯·莫吉安。 我在 EnterpriseDB 工作,并且使用 Postgres 已经超过 23 年了。 我住在美国费城。 我每年大约有 90 天旅行。 我参加了大约 40 个会议。 我的 网站,其中包含我现在向您展示的幻灯片。 因此,会后大家可以到我的个人网站下载。 它还包含大约 30 个演示文稿。 还有视频和大量博客文章,超过 500 个。这是一个信息量相当大的资源。 如果您对此材料感兴趣,那么我邀请您使用它。

在开始使用 Postgres 之前,我曾经是一名教师、教授。 我很高兴现在能够告诉你们我将要告诉你们的事情。 这是我最有趣的演讲之一。 本演示文稿包含 110 张幻灯片。 我们会从简单的事情开始讲,到最后报告会变得越来越复杂,变得相当复杂。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这是一次相当不愉快的谈话。 阻止并不是最流行的话题。 我们希望它在某个地方消失。 这就像去看牙医一样。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

  1. 对于很多从事数据库工作并且同时运行多个进程的人来说,锁定是一个问题。 他们需要阻止。 也就是今天我给大家科普一下阻塞的基础知识。
  2. 交易 ID。 这是演示中相当无聊的部分,但需要理解它们。
  3. 接下来我们将讨论阻塞的类型。 这是一个相当机械的部分。
  4. 下面我们将举一些阻塞的例子。 这将是相当难以理解的。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们来谈谈阻塞。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们的术语相当复杂。 有多少人知道这段话出自哪里? 两个人。 这是来自一款名为《巨大洞穴冒险》的游戏。 我认为这是 80 年代的一款基于文本的电脑游戏。 在那里你必须进入一个山洞,进入一个迷宫,文本发生了变化,但内容每次都大致相同。 这就是我对这场比赛的记忆。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

在这里我们看到了来自 Oracle 的锁的名称。 我们使用它们。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

在这里我们看到了令我困惑的术语。 例如,分享更新独家。 下一页 分享 RAW EXXLUSIVE。 说实话,这些名字都不是很清楚。 我们将尝试更详细地考虑它们。 有些带有“分享”一词,意思是分开。 有些含有“独家”一词。 有些包含这两个词。 我想从这些锁的工作原理开始。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

而“访问”这个词也很重要。 “row”这个词是一个字符串。 即访问分配、行分配。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

Postgres 中另一个需要理解的问题是 MVCC,遗憾的是我无法在演讲中讨论这个问题。 我的网站上有关于这个主题的单独演示。 如果你认为这个演示很难,MVCC 可能是我最难的。 如果您有兴趣,可以在网站上观看。 您可以观看视频。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们需要了解的另一件事是交易 ID。 如果没有唯一标识符,许多交易就无法进行。 在这里我们解释了什么是交易。 Postgres 有两个事务编号系统。 我知道这不是一个非常漂亮的解决方案。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

另请记住,幻灯片将很难理解,因此以红色突出显示的内容是您需要注意的内容。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

http://momjian.us/main/writings/pgsql/locking.sql

让我们来看看。 交易编号以红色突出显示。 此处显示了 SELECT pg_back 函数。 它返回我的交易和交易 ID。

还有一件事,如果您喜欢此演示文稿并希望在您的数据库上运行它,那么您可以转到此粉红色链接并下载此演示文稿的 SQL。 您只需在 PSQL 中运行它,整个演示文稿就会立即显示在您的屏幕上。 它不会包含鲜花,但至少我们可以看到它。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

在本例中,我们看到交易 ID。 这是我们分配给她的号码。 而Postgres中还有另一种类型的事务ID,称为虚拟事务ID

我们必须理解这一点。 这一点非常重要,否则我们将无法理解Postgres中的锁定。

虚拟事务ID是不包含持久值的事务ID。 例如,如果我运行 SELECT 命令,那么我很可能不会更改数据库,也不会锁定任何内容。 因此,当我们运行简单的 SELECT 时,我们不会为该事务提供持久 ID。 我们只给她一个虚拟身份证。

而这提高了Postgres的性能,提高了清理能力,因此虚拟事务ID由两个数字组成。 斜杠之前的第一个数字是后端 ID。 在右边我们只看到一个柜台。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

因此,如果我运行一个请求,它会说后端 ID 是 2。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我运行一系列此类事务,那么我们会看到每次运行查询时计数器都会增加。 例如,当我运行查询 2/10、2/11、2/12 等时。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

请记住,这里有两列。 在左侧我们看到虚拟交易 ID – 2/12。 在右边我们有一个永久的交易ID。 并且这个字段是空的。 并且这个事务不会修改数据库。 所以我没有给它一个永久的交易ID。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

一旦我运行分析命令 ((ANALYZE)),相同的查询就会给我一个永久的事务 ID。 看看这对我们来说有何改变。 我以前没有这个ID,但现在我有了。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这是另一个请求,另一个交易。 虚拟交易数量为2/13。 如果我请求持久事务 ID,那么当我运行查询时,我会得到它。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

那么,再来一次吧。 我们有一个虚拟事务ID和一个持久事务ID。 只要理解这一点就可以理解 Postgres 的行为。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们继续第三部分。 这里我们将简单介绍一下 Postgres 中不同类型的锁。 这不是很有趣。 最后一部分会更有趣。 但我们必须考虑基本的事情,否则我们将无法理解接下来会发生什么。

我们将浏览本节,我们将了解每种类型的锁。 我将向您展示它们如何安装、如何工作的示例,我将向您展示一些查询,您可以使用它们来了解锁定在 Postgres 中的工作原理。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

要创建查询并查看 Postgres 中发生的情况,我们需要在系统视图中发出查询。 在这种情况下,pg_lock 以红色突出显示。 pg_lock 是一个系统表,它告诉我们 Postgres 当前正在使用哪些锁。

然而,我很难向您展示 pg_lock 本身,因为它非常复杂。 所以我创建了一个显示 pg_locks 的视图。 它还为我做了一些工作,让我更好地理解。 也就是说,它排除了我的锁、我自己的会话等。它只是标准 SQL,它可以让您更好地向您展示发生了什么。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

另一个问题是这个视图非常宽,所以我必须创建第二个视图 - lockview2。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安 它向我显示了表中的更多列。 另一篇向我展示了其余的专栏。 这是相当复杂的,所以我试图尽可能简单地介绍它。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

因此我们创建了一个名为 Lockdemo 的表。 我们在那里创建了一条线。 这是我们的示例表。 我们将创建一些部分只是为了向您展示锁的示例。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

所以,一行,一列。 第一种类型的锁称为 ACCESS SHARE。 这是限制最少的阻止。 这意味着它实际上不会与其他锁冲突。

如果我们想显式定义锁,我们可以运行“lock table”命令。 它显然会阻塞,即在 ACCESS SHARE 模式下我们启动锁表。 如果我在后台运行 PSQL,那么我会以这种方式从第一个会话开始第二个会话。 也就是说,我在这里做什么? 我转到另一个会话并告诉它“显示此请求的锁定视图”。 在这里,我在这个表中有 AccessShareLock。 这正是我所要求的。 他说该区块已被分配。 很简单。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

此外,如果我们看第二列,那么那里什么也没有。 他们是空的。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我运行“SELECT”命令,那么这就是请求 AccessShareLock 的隐式(显式)方式。 因此,我释放表并运行查询,查询返回多行。 在其中一行中我们看到了 AccessShareLock。 因此,SELECT 对表调用 AccessShareLock。 而且它几乎不会与任何东西冲突,因为它是一个低级锁。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我运行 SELECT 并拥有三个不同的表怎么办? 以前我只运行一个表,现在我运行三个表:pg_class、pg_namespace 和 pg_attribute。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

现在,当我查看查询时,我在三个表中看到 9 个 AccessShareLock。 为什么? 三个表以蓝色突出显示:pg_attribute、pg_class、pg_namespace。 但您也可以看到,通过这些表定义的所有索引也都具有 AccessShareLock。

而且这是一把几乎不会与其他锁冲突的锁。 它所做的只是阻止我们在选择表时重置表。 这说得通。 也就是说,如果我们选择一个表,那一刻它就消失了,那么这是错误的,所以 AccessShare 是一个低级锁,它告诉我们“在我工作时不要删除此表”。 本质上,这就是她所做的一切。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

ROW SHARE - 这个锁有点不同。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

让我们举个例子。 单独锁定每一行的 SELECT ROW SHARE 方法。 这样,当我们观看它们时,没有人可以删除它们或更改它们。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安那么SHARE LOCK有什么作用呢? 我们看到 SELECT 的事务 ID 是 681。 这很有趣。 这里发生了什么? 我们第一次看到这个数字是在“锁定”字段中。 我们获取事务 ID,它表示它正在以独占模式阻止它。 它所做的只是说我有一行在技术上被锁定在表中的某个位置。 但他没有说具体在哪里。 稍后我们将更详细地讨论这一点。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这里说锁是我们自己用的。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

因此,独占锁明确表示它是独占的。 而且,如果您删除该表中的一行,那么将会发生这种情况,如您所见。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

SHARE EXCLUSIVE 是一个较长的锁。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这是将使用的 (ANALYZE) 分析器命令。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

共享锁 – 您可以在共享模式下显式锁定。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

您还可以创建唯一索引。 在那里你可以看到共享锁,它是它们的一部分。 它锁定表并在其上放置共享锁。

默认情况下,表上的 SHARE LOCK 意味着其他人可以读取该表,但没有人可以修改它。 这正是创建唯一索引时发生的情况。

如果我创建一个唯一的并发索引,那么我将拥有不同类型的锁定,因为如您所知,使用并发索引会减少锁定要求。 如果我使用普通锁、普通索引,那么我将在创建表索引时防止写入该表索引。 如果我使用并发索引,那么我需要使用不同类型的锁定。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

SHARE ROW EXCLUSIVE – 同样可以显式设置(显式)。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

或者我们可以创建一个规则,即采用将使用该规则的特定情况。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

独占锁定意味着没有其他人可以更改该表。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

在这里我们看到不同类型的锁。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

例如,ACCESS EXCLUSIVE 就是一个阻塞命令。 例如,如果你这样做 CLUSTER table,那么这将意味着没有人能够在那里书写。 它不仅锁定表本身,还锁定索引。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这是 ACCESS EXCLUSIVE 阻塞的第二页,我们可以在其中准确地看到它在表中阻塞的内容。 它锁定各个表行,这非常有趣。

这就是我想提供的所有基本信息。 我们讨论了锁、事务 ID、虚拟事务 ID、永久事务 ID。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

现在我们将介绍一些阻塞示例。 这是最有趣的部分。 我们将研究非常有趣的案例。 我在本次演示中的目标是让您更好地了解 Postgres 在尝试阻止某些事情时实际上在做什么。 我认为他非常擅长遮挡零件。

让我们看一些具体的例子。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们将从表格和表格中的一行开始。 当我插入某些内容时,表上会显示 ExclusiveLock、事务 ID 和 ExclusiveLock。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我再插入两行会发生什么? 现在我们的表有三行。 我插入一行并将其作为输出。 如果我再插入两行,有什么奇怪的? 这里有一件奇怪的事情,因为我已经向该表添加了三行,但锁定表中仍然有两行。 这本质上就是 Postgres 的基本行为。

许多人认为,如果在数据库中锁定 100 行,那么您将需要创建 100 个锁定条目。 如果我一次阻止 1 行,那么我将需要 000 个这样的查询。 如果我需要一百万或十亿来阻止。 但如果我们这样做的话,效果不会很好。 如果您使用的系统为每个单独的行创建阻塞条目,那么您会发现这很复杂。 因为你需要立即定义一个可以溢出的锁表,但 Postgres 不会这样做。

这张幻灯片的真正重要之处在于,它清楚地表明了在 MVCC 内部运行的另一个系统可以锁定各个行。 因此,当您锁定数十亿行时,Postgres 不会创建十亿个单独的锁定命令。 而这对生产力有着非常好的影响。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

更新怎么样? 我现在正在更新该行,您可以看到它同时执行了两个不同的操作。 它同时锁定了表,但也锁定了索引。 而且他需要锁定索引,因为这个表有唯一约束。 我们希望确保没有人改变它,所以我们阻止它。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我想更新两行会发生什么? 我们看到他也有同样的行为方式。 我们进行两倍的更新,但锁定行的数量完全相同。

如果您想知道 Postgres 如何做到这一点,您需要听我关于 MVCC 的演讲,了解 Postgres 如何在内部标记它更改的这些行。 Postgres 有一种方法可以做到这一点,但它不是在表锁定级别上执行的,而是在较低且更有效的级别上执行的。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我想删除某些内容怎么办? 例如,如果我删除一行,但仍然有两个阻塞输入,即使我想将它们全部删除,它们仍然存在。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

并且,例如,我想插入 1 行,然后删除或添加 000 行,然后我添加或更改的那些单独的行,它们不会记录在这里。 它们是在系列本身的较低级别编写的。 在 MVCC 演讲中我详细谈到了这一点。 但是,当您分析锁时,确保您在表级别进行锁定并且您看不到此处如何记录各个行,这一点非常重要。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

显式阻止怎么样?

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我单击刷新,则会锁定两行。 如果我全部选择并单击“到处更新”,那么我仍然有两条阻止记录。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们不会为每一行创建单独的记录。 因为这样生产力就会下降,所以可能会出现太多的情况。 我们可能会发现自己处于一种不愉快的境地。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

同样的事情,如果我们共享的话,我们可以做 30 次。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们恢复表,删除所有内容,然后再次插入一行。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

您在 Postgres 中看到的另一个众所周知且理想的行为是您可以执行更新或选择。 您可以同时执行此操作。 并且 select 不会阻止更新,并且相反的方向也是如此。 我们告诉读者不要屏蔽作者,而作者并没有屏蔽读者。

我将向您展示一个例子。 我现在就做出选择。 然后我们将进行插入。 然后您可以看到 - 694。您可以看到执行此插入的事务的 ID。 这就是它的工作原理。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我现在查看我的后端 ID,现在是 695。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我可以看到 695 出现在我的表格中。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我像这样在这里更新,那么我会得到不同的情况。 在这种情况下,695是排它锁,并且update具有相同的行为,但它们之间没有冲突,这是很不寻常的。

并且可以看到顶部是ShareLock,底部是ExclusiveLock。 两笔交易都成功了。

您需要听我在 MVCC 的演讲来了解这是如何发生的。 但这是一个可以同时执行的示例,即同时执行 SELECT 和 UPDATE。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

让我们重置并再做一次操作。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果您尝试在同一行上同时运行两个更新,它将被阻止。 请记住,我说过读者不会阻止写入者,写入者也不会阻止读者,但一个写入者会阻止另一个写入者。 也就是说,我们不能让两个人同时更新同一行。 你必须等到其中一个完成。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

为了说明这一点,我将查看 Lockdemo 表。 我们将看一行。 每笔交易 698。

我们已将其更新为 2。 699是第一次更新。 并且已成功或处于待处理交易状态,正在等待我们确认或取消。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

但看看别的东西 - 2/51 是我们的第一笔交易,我们的第一个会话。 3/112 是来自顶部的第二个请求,该请求将该值更改为 3。如果您注意到,顶部的请求锁定了自己,即 699。但是 3/112 没有授予锁定。 Lock_mode 列说明它正在等待什么。 它预计为 699。如果您查看 699 的位置,就会发现它更高。 第一次会议做了什么? 她对自己的交易 ID 创建了独占锁。 Postgres 就是这样做的。 它会阻止自己的交易 ID。 如果您想等待某人确认或取消,那么您需要在有待处​​理的交易时等待。 这就是为什么我们可以看到一条奇怪的线。

我们再看一下。 在左侧我们可以看到我们的处理 ID。 在第二列中我们看到虚拟事务 ID,在第三列中我们看到 lock_type。 这是什么意思? 本质上,它所说的是它正在阻止事务 ID。 但请注意,底部的所有行都表示关系。 所以表上有两种类型的锁。 有一个关系锁。 然后是 transactionid 阻塞,你自己阻塞,这正是第一行或最底部发生的情况,即 transactionid 所在的位置,我们等待 699 完成其操作。

我会看看这里会发生什么。 这里有两件事同时发生。 您正在查看第一行中锁定自身的事务 ID 锁。 她还封锁自己,让人们等待。

如果您查看第 6 行,它与第一行是相同的条目。 因此交易 699 被阻止。 700也是自锁的。 然后在底行中您将看到我们正在等待 699 完成其操作。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

在 lock_type 元组中您可以看到数字。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

您可以看到它是 0/10。 这是页码,也是该特定行的偏移量。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

当我们更新时你会看到它变成了 0/11。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

但实际上它是0/10,因为这个操作需要等待。 我们有机会看到这是我正在等待确认的系列。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

一旦我们确认并按下提交,当更新完成时,这就是我们再次得到的。 事务 700 是唯一的锁,它不会等待其他任何人,因为它已提交。 它只是等待事务完成。 一旦699用完,我们就不再等待。 现在事务 700 表示一切都很好,它在所有允许的表上拥有所需的所有锁。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

为了使整个事情变得更加复杂,我们创建了另一个视图,这一次将为我们提供层次结构。 我不指望你能理解这个请求。 但这将使我们更清楚地了解正在发生的事情。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这是一个递归视图,还有另一个部分。 然后它又将一切重新组合在一起。 我们就用这个吧。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我们同时进行三个更新并说该行现在是三个怎么办? 我们将把 3 改为 4。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

在这里我们看到 4。交易 ID 702。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

然后我会将 4 更改为 5,将 5 更改为 6,将 6 更改为 7。我将让许多人排队等待这一笔交易结束。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

一切都变得清晰起来。 第一行是什么? 这是702。这是最初设置该值的事务ID。 我的授予专栏写了什么? 我有标记 f。 这些是我的更新,(5) 无法获得批准,因为我们正在等待交易 ID 6 结束。 我们有事务 ID 阻塞。 这会导致 7 个事务 ID 锁。

如果你看看 704,在 705,那里还没有写任何东西,因为他们还不知道发生了什么。 他们只是写道,他们不知道发生了什么。 他们会去睡觉,因为他们在等待有人完成并在有机会换行时被叫醒。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这就是它的样子。 显然,他们都在等待12号线。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这就是我们在这里看到的。 这里是 0 点 12 分。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

因此,一旦第一笔交易获得批准,您就可以在此处查看层次结构的工作原理。 现在一切都变得清楚了。 他们都变得干净了。 而他们实际上还在等待。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这就是发生的事情。 702 提交。 现在703获得了这个行锁,然后704开始等待703提交。 705 也在等待着这一点。 当这一切完成后,他们就会清理自己。 我想指出的是,每个人都在排队。 而这与堵车时大家都在等待第一辆车的情况非常相似。 第一辆车停了下来,每个人都排起了长队。 然后它移动,然后下一辆车可以向前行驶并获得其阻挡,等等。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果这对您来说还不够复杂,那么我们现在将与您讨论死锁。 不知道你们谁遇到过。 这是数据库系统中相当常见的问题。 但死锁是指一个会话正在等待另一个会话做某事。 此时另一个会话正在等待第一个会话做某事。

举例来说,如果伊万说:“给我一些东西”,我说:“不,只有你给我别的东西,我才会给你。” 他说:“不,如果你不给我,我就不给你。” 我们最终陷入了僵局。 我确信伊万不会这样做,但你明白这意味着我们有两个人想要得到一些东西,并且他们不准备放弃它,直到另一个人给他们他们想要的东西。 并且没有解决办法。

从本质上讲,您的数据库需要检测到这一点。 然后您需要删除或关闭其中一个会话,因为否则它们将永远保留在那里。 我们在数据库中看到它,我们在操作系统中看到它。 在所有具有并行流程的地方,这种情况都可能发生。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

现在我们将安装两个死锁。 我们将输入 50 和 80。在第一行中,我将从 50 更新到 50。我将获得交易编号 710。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

然后我会将 80 更改为 81,将 50 更改为 51。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这就是它的样子。 因此710有一行被阻塞,711正在等待确认。 我们更新的时候就看到了这一点。 710是我们系列的主人。 并且711等待710完成交易。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

它甚至指出死锁发生在哪一行。 这就是事情开始变得奇怪的地方。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

现在我们将80更新为80。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这就是僵局开始的地方。 710在等待711的回应,711也在等待710。而这不会有好结果。 并且没有办法摆脱这种情况。 他们会期待彼此的回应。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

它只会开始推迟一切。 我们不希望这样。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

当这种情况发生时,Postgres 有办法注意到。 当发生这种情况时,您会收到此错误。 由此可以清楚地看出,某个进程正在等待另一个进程的共享锁,即被 711 进程阻塞。 该进程正在等待某个事务 ID 上的共享锁,并被某个进程阻塞。 因此,这里就出现了僵局的情况。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

是否存在三向僵局? 是否可以? 是的。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们将这些数字输入到表格中。 我们把40改成40,我们做阻塞。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们将 60 更改为 61,将 80 更改为 81。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

然后我们改变80然后繁荣!

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

而714现在正在等待715。716正在等待715。 对此我们无能为力。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这里已经不是两个人了,已经是三个人了。 我想要从你那里得到一些东西,这个人想要从第三者那里得到一些东西,第三个人想要从我这里得到一些东西。 我们最终会陷入三向等待,因为我们都在等待对方完成他们需要做的事情。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

Postgres 知道这发生在哪一行。 因此,它会给您以下消息,这表明您遇到了三个输入相互阻塞的问题。 这里没有任何限制。 这可能是 20 个条目互相阻塞的情况。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

下一个问题是可序列化的。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果有特殊的可序列化锁。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们回到719。它的输出很正常。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

您可以单击使事务可序列化。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

您意识到您现在拥有一种不同类型的 SA 锁 - 它意味着可序列化。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

因此,我们有一种名为 SARieadLock 的新型锁,它是一个序列锁,允许您输入序列。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

您还可以插入唯一索引。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

在这个表中我们有唯一索引。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

所以如果我在这里输入数字 2,那么我就有一个 2。但是在最顶部,我又输入了另一个 2。你可以看到 721 有一个独占锁。 但现在 722 正在等待 721 完成其操作,因为它无法插入 2,直到它知道 721 会发生什么。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我们进行子事务。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这里有 723 个。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

如果我们保存该点然后更新它,那么我们就会得到一个新的交易 ID。 这是您需要注意的另一种行为模式。 如果我们返回这个,那么交易 ID 就会消失。 724要走了。 但现在我们有 725 个。

那么我在这里想做什么? 我试图向您展示您可能会发现的不寻常锁的示例:无论是可序列化锁还是 SAVEPOINT,这些都是将出现在锁表中的不同类型的锁。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这是显式(explicit)锁的创建,其中有pg_advisory_lock。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

您会看到阻止类型被列为建议类型。 这里用红色写着“咨询”。 您可以同时使用 pg_advisory_unlock 进行阻止。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

最后,我想向您展示另一件令人兴奋的事情。 我将创建另一个视图。 但我会将 pg_locks 表与 pg_stat_activity 表连接起来。 我为什么要这样做? 因为这将允许我查看所有当前会话并准确了解它们正在等待哪种锁。 当我们将锁表和查询表放在一起时,这是非常有趣的。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这里我们创建pg_stat_view。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我们将这一行更新为一位。 在这里我们看到 724。然后我们将行更新为三。 你现在在这里看到什么? 这些是请求,即您会看到左栏中列出的完整请求列表。 然后在右侧您可以看到障碍物及其造成的情况。 而且它可以让您更加清楚,这样您就不必每次都返回每个会话并查看是否需要加入。 他们为我们做这件事。

另一个非常有用的功能是 pg_blocking_pids。 你可能从未听说过她。 她在做什么? 它使我们能够知道该会话 11740 正在等待哪些特定进程 ID。 您可以看到 11740 正在等待 724。724 位于最顶部。 11306 是你的进程 ID。 本质上,这个函数会遍历你的锁表。 我知道这有点复杂,但你能理解。 本质上,这个函数会遍历这个锁表,并尝试找到这个进程ID在哪里被赋予了它正在等待的锁。 它还尝试找出正在等待锁的进程具有哪个进程 ID。 这样你就可以运行这个函数了 pg_blocking_pids.

这非常有用。 我们是在 9.6 版本中才添加这个功能的,所以这个功能只有 5 年的历史,但是它非常非常有用。 这同样适用于第二个请求。 它准确地展示了我们需要看到的内容。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

这就是我想和你谈谈的。 正如我所料,我们用完了所有的时间,因为幻灯片太多了。 幻灯片可供下载。 我要感谢您来到这里。 我相信您会喜欢本次会议的其余部分,非常感谢!

问题:

例如,如果我尝试更新行,而第二个会话尝试删除整个表。 据我了解,应该有意图锁之类的东西。 Postgres中有这样的东西吗?

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

让我们回到最开始。 您可能还记得,当您执行任何操作时,例如执行 SELECT 操作时,我们都会发出 AccessShareLock。 这可以防止表被删除。 因此,例如,如果您想要更新表中的一行或删除一行,那么某人无法同时删除整个表,因为您在整个表和该行上持有此 AccessShareLock。 一旦完成,他们就可以将其删除。 但是,当您直接在那里更改某些内容时,他们将无法做到这一点。

让我们再来一次。 让我们继续讨论删除示例。 您会看到整个表上方的行上有一个排他锁。

这看起来像锁独占,对吧?

是的,看起来是这样。 我明白你在说什么。 你是说,如果我执行 SELECT 然后我有一个 ShareExclusive,然后我将其设置为 Row Exclusive,这会成为问题吗? 但令人惊讶的是,这并没有造成问题。 这看起来像是增加了锁定程度,但本质上我有一个防止删除的锁。 现在,当我使这个锁变得更强大时,它仍然可以防止删除。 所以我不会上去。 也就是说,当它处于较低级别时,它也阻止了它发生,因此当我提高其级别时,它仍然阻止表被删除。

我明白你在说什么。 这里不存在锁升级的情况,即您试图放弃一个锁以引入一个更强的锁。 这里它只是全面增加了这种预防,因此不会引起任何冲突。 但这是一个好问题。 非常感谢您提出这个问题!

当我们有很多会话、大量用户时,我们需要做什么才能避免死锁情况?

Postgres 自动注意到死锁情况。 并且它会自动删除其中一个会话。 避免死锁的唯一方法就是按照相同的顺序来阻止人。 因此,当您查看应用程序时,通常会发现死锁的原因......让我们想象一下我想阻止两个不同的事情。 一个应用程序锁定表 1,另一个应用程序锁定表 2,然后锁定表 1。避免死锁的最简单方法是查看您的应用程序,并尝试确保所有应用程序中锁定以相同的顺序发生。 这通常可以消除 80% 的问题,因为各种各样的人都在编写这些应用程序。 如果您以相同的顺序阻止它们,那么您就不会遇到死锁情况。

非常感谢您的表演! 您谈到了“vacuum full”,如果我理解正确的话,“vacuum full”会扭曲单独存储中的记录顺序,因此它们会保持当前记录不变。 为什么vacuum full需要独占锁访问以及为什么它与写操作冲突?

这是个好问题。 原因是真空已满占据桌子。 我们实际上是在创建该表的新版本。 而且桌子会是新的。 事实证明,这将是该表的全新版本。 问题是,当我们这样做时,我们不希望人们阅读它,因为我们需要他们看到新表。 这与上一个问题有关。 如果我们可以同时阅读,我们将无法移动它并将人们引导到新桌子。 我们需要等待每个人都读完这个表,所以这本质上是一个锁独占的情况。
我们只是说我们从一开始就锁定,因为我们知道在最后我们需要一个独占锁才能将每个人移动到新副本。 所以我们有可能解决这个问题。 我们通过同步索引来做到这一点。 但这要困难得多。 这与您之前关于锁独占的问题非常相关。

是否可以为 Postgres 添加锁定超时? 例如,在 Oracle 中,我可以编写“选择更新”并在更新之前等待 50 秒。 这对于申请来说是有好处的。 但在 Postgres 中,我要么需要立即执行而不等待,要么等到某个时间。

是的,您可以选择锁的超时时间。 您还可以发出 no way 命令,这将...如果您无法立即获得锁。 因此,要么锁定超时,要么采取其他措施来允许您执行此操作。 这不是在句法层面上完成的。 这是作为服务器上的变量完成的。 有时这不能使用。

你能打开第 75 张幻灯片吗?

是。

解锁 Postgres 锁管理器。 布鲁斯·莫吉安

我的问题如下。 为什么两个更新进程都期望 703?

这是一个很好的问题。 顺便说一句,我不明白 Postgres 为什么这样做。 但是当 703 创建时,它期待着 702。而当 704 和 705 出现时,它们似乎不知道自己在期待什么,因为那里什么也没有。 Postgres 是这样做的:当你无法获得锁时,它会写“处理你有什么意义?”,因为你已经在等待某人了。 所以我们就让它悬在空中,它根本不会更新。 但这里发生了什么? 一旦702完成该过程并且703收到其锁,系统就返回。 她说现在我们有两个人在等。 然后我们一起更新它们。 让我们表明双方都在期待。

我不知道 Postgres 为什么这样做。 但是有一个问题叫做f...。 在我看来,这不是俄语中的术语。 这是每个人都在等待一座城堡的时候,即使有20个权威在等待这座城堡。 突然他们都同时醒来。 每个人都开始尝试做出反应。 但是系统让大家都在等703。因为他们都在等,我们马上就让他们全部排队。 如果在此之后出现任何其他新的请求,例如707,那么将再次出现空。

在我看来,这样做是为了让我们可以说,在这个阶段702正在等待703,而所有在那之后的人都不会进入这个领域。 但是,一旦第一个服务员离开,所有在更新之前等待的人都会收到相同的令牌。 所以我认为这样做是为了我们可以按顺序进行处理,以便它们得到正确的排序。

我一直认为这是一个相当奇怪的现象。 因为在这里,例如,我们根本没有列出它们。 但在我看来,每次我们给出一个新的锁时,我们都会查看所有正在等待的人。 然后我们把它们全部排好。 然后,只有当下一个人完成处理后,任何新进来的人才会进入队列。 非常好的问题。 非常感谢您的提问!

在我看来,当 705 期望 704 时,这更符合逻辑。

但这里的问题如下。 从技术上讲,您可以唤醒其中之一。 所以我们会唤醒其中之一。 但系统中会发生什么? 你可以看到最上面的703如何屏蔽了他自己的交易ID。 这就是 Postgres 的工作原理。 而且703被它自己的事务ID阻塞了,所以如果有人想等待,那么他们就会等待703。而且,本质上,703完成了。 只有在其完成后,其中一个进程才会被唤醒。 我们不知道这个过程到底是什么。 然后我们逐渐处理一切。 但不清楚哪个进程首先被唤醒,因为它可能是这些进程中的任何一个。 本质上,我们有一个调度程序,它表示我们现在可以唤醒这些进程中的任何一个。 我们只是随机挑选一个。 所以两者都需要注意,因为我们可以唤醒其中任何一个。

问题是我们有 CP 无穷大。 因此,我们很可能能够唤醒后者。 例如,如果我们唤醒后一个,我们将等待刚刚收到块的那个,因此我们无法确定到底谁将首先被唤醒。 我们只要创造这样一个情况,系统就会以随机的顺序唤醒它们。

Egor Rogov 有关锁的文章。 瞧,它们也很有趣,也很有用。 当然,这个话题非常复杂。 非常感谢你,布鲁斯!

来源: habr.com

添加评论