postgresql select for update 多行加锁顺序_PostgreSQL和Mysql的MVCC实现机制的差异对比

任何数据库的主要要求之一就是实现可伸缩性。只有将争用(锁定)最小化(如果不能一起删除),才可以实现。由于读/写/更新/删除是数据库中发生的一些主要的频繁操作,因此对于这些操作并发进行而不被阻塞非常重要。为了实现这一目标,大多数主要数据库都采用了一种称为多版本并发控制的并发模型该模型将争用降低到最低限度。

什么是MVCC

多版本并发控制(以下简称MVCC)是一种算法,可通过维护同一对象的多个版本来提供精细的并发控制,以使READ和WRITE操作不会冲突。这里的WRITE表示UPDATE和DELETE,因为无论如何新插入的记录都将按照隔离级别受到保护。每个WRITE操作都会生成对象的新版本,并且每个并发读取操作都会根据隔离级别读取对象的不同版本。由于读取和写入操作均在同一对象的不同版本上进行,因此这些操作都不需要完全锁定,因此两者都可以并发操作。争用仍然存在的唯一情况是当两个并发事务尝试写入同一记录时。

当前大多数主要数据库都支持MVCC。该算法的目的是维护同一对象的多个版本,因此MVCC的实现因数据库而异,仅在创建和维护多个版本方面有所不同。因此,相应的数据库操作和数据存储发生了变化。

公认的实现MVCC的方法是PostgreSQL和Firebird / Interbase使用的一种,而InnoDB和Oracle使用的另一种。在随后的章节中,我们将详细讨论如何在PostgreSQL和InnoDB中实现它。

PostgreSQL中的MVCC

为了支持多个版本,PostgreSQL维护每个对象的其他字段(PostgreSQL术语为Tuple),如下所述:

  1. xmin –插入或更新元组的交易的交易ID。如果是UPDATE,则将使用此事务ID分配更新版本的元组。
  2. xmax –删除或更新元组的交易的交易ID。如果是UPDATE,则为元组的当前现有版本分配此事务ID。在新创建的元组上,此字段的默认值为null。

PostgreSQL将所有数据存储在称为HEAP的主存储中(页面的默认大小为8KB)。所有新元组都将xmin作为创建它的事务进行处理,而旧版本元组(已更新或删除)将分配给xmax。从旧版本元组到新版本始终存在链接。在隔离的情况下,较旧版本的元组可用于在回滚的情况下重新创建元组,并可通过READ语句读取较旧版本的元组。

考虑到表有两个元组,T1(值1)和T2(值2),可以在下面的3个步骤中演示新行的创建:

787ba7e661dddb51aa2e1f1216371ec3.png

MVCC:在PostgreSQL中存储多个版本

如图所示,数据库中最初有两个元组,其值分别为1和2。

然后,在第二步中,将值2的行T2更新为值3。这时,将使用新值创建一个新版本,并将其存储为与现有元组相邻并将其存储在同一存储区域中。在此之前,较旧的版本将分配给xmax并指向最新版本的元组。

类似地,在第三步中,当删除具有值1的行T1时,将在同一位置虚拟删除现有行(即,它为当前事务分配了xmax)。没有为此创建新版本。

接下来,让我们看一下每个操作如何创建多个版本,以及如何保持事务隔离级别而不用一些实际示例进行锁定。在下面的所有示例中,默认情况下使用“ READ COMMITTED”隔离。

插入

每次插入记录时,都会创建一个新的元组,并将其添加到属于相应表的页面之一中。

e44d4725c6fc00ac178929aa3dfb48d0.png

PostgreSQL并发INSERT操作

正如我们在这里看到的逐步:

  1. 会话A启动事务并获取事务ID 495。
  2. 会话B启动事务并获取事务ID 496。
  3. 会话A插入一个新的元组(获取存储在HEAP中)
  4. 现在,添加了将xmin设置为当前事务ID 495的新元组。
  5. 但是从会话B中看不到相同的内容,因为xmin(即495)仍未提交。
  6. 一旦提交。
  7. 数据对两个会话均可见。

更新

PostgreSQL UPDATE不是“ IN-PLACE”更新,即它不会使用所需的新值来修改现有对象。相反,它将创建该对象的新版本。因此,UPDATE大致涉及以下步骤:

  1. 它将当前对象标记为已删除。
  2. 然后,它添加该对象的新版本。
  3. 将对象的旧版本重定向到新版本。

因此,即使许多记录保持不变,HEAP也会占用空间,就好像插入了多条记录一样。

931596b5bb5fb4ea967da64bd5a11b86.png

PostgreSQL并发INSERT操作

正如我们在这里看到的逐步:

  1. 会话A启动事务并获取事务ID 497。
  2. 会话B启动事务并获取事务ID 498。
  3. 会话A更新现有记录。
  4. 在这里,会话A看到一个版本的元组(更新的元组),而会话B看到另一个版本(旧元组,但xmax设置为497)。两个元组版本都存储在HEAP存储中(甚至同一页面也取决于空间可用性)
  5. 一旦会话A提交了事务,则由于提交了旧元组的xmax,旧元组将过期。
  6. 现在,两个会话都看到相同版本的记录。

删除

删除几乎与UPDATE操作类似,只是它不必添加新版本。只是将当前对象标记为DELETED,如UPDATE情况中所述。

81d0f616126464376e3446314aa1d369.png

PostgreSQL并发DELETE操作

  1. 会话A启动事务并获取事务ID 499。
  2. 会话B启动事务并获取事务ID 500。
  3. 会话A删除现有记录。
  4. 在这里,会话A没有看到任何从当前事务中删除的元组。而Session-B看到该元组的较旧版本(xmax为499;删除该记录的事务)。
  5. 一旦会话A提交了事务,则由于提交了旧元组的xmax,旧元组将过期。
  6. 现在,两个会话都看不到已删除的元组。

如我们所见,没有任何操作可以直接删除对象的现有版本,并且在需要的地方都可以添加对象的其他版本。

现在,让我们看一下如何在具有多个版本的元组上执行SELECT查询:SELECT需要读取所有版本的元组,直到根据隔离级别找到合适的元组为止。假设有元组T1,它已更新并创建了新版本T1',并在更新时又创建了T1'':

  1. SELECT操作将通过此表的堆存储并首先检查T1。如果提交了T1 xmax事务,则它将移至该元组的下一个版本。
  2. 假设现在也提交了T1'元组xmax,然后再次移动到该元组的下一个版本。
  3. 最后,它找到T1''并看到xmax未提交(或为null),并且根据隔离级别,T1''xmin对于当前事务可见。最后,它将读取T1''元组。

如我们所见,它需要遍历元组的所有3个版本,以便找到合适的可见元组,直到过期的元组被垃圾收集器(VACUUM)删除为止。

InnoDB中的MVCC

为了支持多个版本,InnoDB为每行维护其他字段,如下所述:

  1. DB_TRX_ID:插入或更新该行的事务的事务ID。
  2. DB_ROLL_PTR:它也称为回滚指针,它指向写入回滚段的撤消日志记录(在下一个更多内容中)。

与PostgreSQL一样,InnoDB在所有操作中也会创建该行的多个版本,但是旧版本的存储有所不同。

对于InnoDB,更改后的行的旧版本保存在单独的表空间/存储中(称为undo段)。因此,与PostgreSQL不同,InnoDB在主存储区中仅保留行的最新版本,而在undo段中保留较旧的行。还原段中的行版本用于回滚时的撤消操作,并根据隔离级别通过READ语句读取旧版本的行。

考虑到表有两行,T1(值1)和T2(值2),可以通过以下3个步骤来演示新行的创建:

253c54c4c313cfce3204495b69fb9e3c.png

MVCC:在InnoDB中存储多个版本

从图中可以看出,数据库中最初有两行,其值分别为1和2。

然后在第二阶段中,将值2的行T2更新为值3。这时,将使用新值创建新版本,并替换旧版本。在此之前,较旧的版本将存储在撤消段中(请注意,UNDO段版本仅具有增量值)。另外,请注意,回滚段中有一个从新版本到旧版本的指针。因此,与PostgreSQL不同,InnoDB更新是“ IN-PLACE”。

类似地,在第三步中,当删除具有值1的行T1时,则在主存储区域中虚拟删除了现有行(即,它只是在行中标记了一个特殊位),并在其中添加了与之对应的新版本。撤消段。同样,从主存储器到撤消段只有一个滚动指针。

从外部看,所有操作的行为均与PostgreSQL相同。只是多个版本的内部存储有所不同。

MVCC:PostgreSQL与InnoDB

现在,让我们分析PostgreSQL和InnoDB在MVCC实现方面的主要区别是什么:

  1. 旧版本的大小
    PostgreSQL只是在元组的较旧版本上更新xmax,因此较旧版本的大小与相应的插入记录相同。这意味着,如果您有3个版本的旧元组,则所有版本都将具有相同的大小(除非每次更新时实际数据大小有所不同)。
    而对于InnoDB,存储在Undo段中的对象版本通常小于相应的插入记录。这是因为仅将更改的值(即差分)写入UNDO日志。
  2. INSERT操作
    即使对于INSERT,InnoDB也需要在UNDO段中写入一条额外的记录,而PostgreSQL仅在UPDATE的情况下才创建新版本。
  3. 在回滚的情况下还原旧版本
    PostgreSQL不需要任何特定的东西就可以在回滚的情况下恢复旧版本。请记住,旧版本的xmax等于更新此元组的事务。因此,在提交该事务ID之前,对于并发快照,它被视为活动元组。事务回滚后,所有事务都会自动将相应的事务视为活动事务,因为这将是中止的事务。
    而对于InnoDB,则明确要求在回滚发生后重建对象的旧版本。
  4. 回收旧版本占用的空间
    对于PostgreSQL,仅当没有并行快照读取该版本时,才可以将较早版本占用的空间视为已耗尽。旧版本失效后,VACUUM操作可以回收它们所占用的空间。VACUUM可以手动触发,也可以作为后台任务触发,具体取决于配置。
    InnoDB UNDO日志主要分为INSERT UNDO和UPDATE UNDO。相应的事务提交后,第一个将被丢弃。第二个需要保留,直到与任何其他快照平行为止。InnoDB没有显式的VACUUM操作,但是在类似的行上,它具有异步的PURGE来丢弃作为后台任务运行的UNDO日志。
  5. 延迟真空的影响
    如前所述,在PostgreSQL的情况下延迟真空会产生巨大的影响。即使不断删除记录,它也会导致表开始膨胀并导致存储空间增加。它也可能达到需要完全抽真空的地步,这是非常昂贵的操作。
  6. 如果表格过大,则顺序扫描
    PostgreSQL顺序扫描必须遍历对象的所有旧版本,即使所有旧版本都已失效(直到使用真空将其删除为止)。这是PostgreSQL中最典型且讨论最多的问题。请记住,PostgreSQL将所有版本的元组存储在同一存储中。
    而对于InnoDB,除非需要,否则不需要读取撤消记录。如果所有撤消记录都已失效,则仅足以读取对象的所有最新版本。
  7. 指数
    PostgreSQL将索引存储在单独的存储中,该存储与HEAP中的实际数据保持一个链接。因此,即使INDEX不变,PostgreSQL也必须更新INDEX部分。尽管稍后通过实现HOT(仅堆元组)更新解决了此问题,但仍然存在一个局限,即如果无法在同一页面中容纳新的堆元组,则它将回退到常规UPDATE。
    InnoDB没有问题,因为它们使用聚集索引。

结论

PostgreSQL MVCC没有什么缺点,特别是在工作负载频繁更新/删除的情况下,存储空间过大。因此,如果您决定使用PostgreSQL,则应该非常小心地配置VACUUM。

PostgreSQL社区也承认这是一个主要问题,他们已经开始研究基于UNDO的MVCC方法(临时名称为ZHEAP),我们可能会在以后的版本中看到同样的情况。

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

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

相关文章

WireShark抓DNS请求和回复数据报的分析

1 DNS简单理解 我们简单理解DNS功能是把域名转成IP地址,我们先发送一个NDS请求数据包到本地域名服务器去找,找不到我们就去根域名服务器去找,根域名找不到我们再把顶级域名服务器地址回复给本地域名服务器,然后本地域名服务器到顶级域名服务器去查询,如果依然找不到,同理…

12款白帽子用于黑客渗透测试的操作系统

想知道什么样的操作系统的是白帽子黑客的最爱吗?本文我们将推荐12个操作系统,包括一些Linux发行版,如Kali Linux,Parrot安全操作系统,BlackArch等。这些以安全为重点的操作系统,可以帮助白帽子黑客进行渗透测试&#…

jqurey操作radio总结

2019独角兽企业重金招聘Python工程师标准>>> 在我们前端的项目中&#xff0c;会经常用到radio单选按钮&#xff0c;下面给出个例子总结jquery对radio的各种操作&#xff1a; 示例如下&#xff1a; <html> <head><meta charset"UTF-8">&…

android中文离线api_比林肯法球Linken sphere浏览器更多更新指纹的国产防关联软件-VMLogin中文版浏览器...

林肯法球&#xff08;Linken Sphere&#xff09;是俄罗斯人开发的一个功能强大的浏览器&#xff0c;该工具最早出现在2017年7月&#xff0c;此外还具有“离线记录模式”功能&#xff0c;能够简化会话之间导入和导出cookie的过程。使用Linken Sphere工具开启浏览器会话时&#x…

【C语言简单说】十七:数组

** ( ิ∀ิ ) 还有两个小节就基本上简单的过了一遍C语言了&#xff0c;现在我们来讲诉什么是数组。** 字面意思&#xff1a;数组&#xff0c;数组的一个组&#xff1f;应该说一堆数值的一个集合&#xff1f;我不知道大家的年龄段在于什么阶段&#xff0c;如果学过集合的话&am…

Blazor University (12)组件 — 组件生命周期

原文链接&#xff1a;https://blazor-university.com/components/component-lifecycles/组件生命周期源代码[1]Blazor 组件具有许多我们可以重写以影响应用程序行为的虚拟方法。这些方法在组件生命周期的不同时间执行。下图概述了这些生命周期方法的流程。组件生命周期图SetPar…

如何通过css控制内容显示顺序 第二行的内容优先显示

我们有时进行网页设计时为了想让用户感兴趣的内容优先显示在前&#xff0c;又不想改动代码的先后顺序&#xff0c;要怎么操作呢&#xff1f;&#xff08;或者换种说法&#xff1a;源代码中要先看到A再看到B&#xff0c;而视觉上是先B再A&#xff09;举个简单的例子&#xff0c;…

【C语言简单说】十七:数组(补)

上一节 我们所说的数组是整数类型的对吧&#xff1f;那么我们还有其他类型 的数组&#xff0c;在这里用字符数组举例。 如下代码&#xff1a; #include<stdio.h> #include<stdlib.h> int main() {char a[5]{a,b,c,d,e};int i;for(i0;i<5;i){printf("a[%d…

WireShark之抓包过滤链接部分

1 问题 我们打开WireShark&#xff0c;开始抓包&#xff0c;然后浏览器输入http链接地址&#xff0c;那我们怎么快速在WireShark里面找到 2 解决办法 1&#xff09;在WireShark里面输入http 2 ) Ctrl F,然后选择字符串&#xff0c;然后在字符串的右边输入 我们要过滤的部分…

最通俗易懂的依赖注入之生命周期

这篇文章是 ASP.NET 6 依赖注入系列文章的第二篇&#xff0c;点击上方蓝字可以阅读整个系列。在上一篇文章中&#xff0c;我们讨论了什么是依赖注入和控制反转&#xff0c;以及它的作用是什么。在这篇文章中&#xff0c;我们先演示一下依赖注入的基本用法&#xff0c; 然后再讨…

Cnblogs自定义皮肤css样式-星空观测者

不知不觉来Cnblogs也这么久了&#xff0c;然而Blogs提供的主题还是依旧那么复古&#xff0c;总觉得阅读起来难免枯燥&#xff0c;虽然我认为做技术不可以太过浮躁&#xff0c;但是一个美观的主题终究是吸引人眼的第一要素。 毕竟这么久了&#xff0c;在博客园还没有发现一个比较…

我的世界java版forge怎么用_我的世界电脑版MOD怎么用 我的世界pc版forge怎么安装...

我的世界由游戏本体以及启动器两部分组成&#xff0c;要玩游戏就要下载好本体再用启动器启动&#xff0c;单有游戏或者单有启动器都是玩不成的&#xff0c;想知道我的世界电脑版怎么开始&#xff0c;我的世界pc版启动器怎么用就来看看吧&#xff01;▍MOD怎么用1.安装MOD前要先…

【C语言简单说】十八:二维数组

这里可能会让大家脑袋迷糊&#xff0c;不过没事&#xff0c;多动动脑。 这一节我们来说二维数组&#xff0c;啥叫二维数组&#xff1f;之前我们那个是一维数组&#xff0c;好了&#xff0c;我们接下来大家就会慢慢的搞懂的。 我们的一维数组就像 一列排得整整齐齐的队伍&…

应用系统日志采集解决方案

概述 基于Flume MongoDB&#xff0c;对现有的多个应用系统进行日志采集。特点 采集范围每一次用户请求的请求信息。数据量大尽量减少现有系统的改动数据流图 说明&#xff1a;首先考虑的结构体系&#xff0c;是直接在应用系统中&#xff0c;将日志数据写到Flume&#xff1b;但…

每个程序员都可能犯过的10个错误

1. 面向编译器写代码&#xff0c;而不是面向用户 当人们使用编译器创建自己的 app 时&#xff0c;在把自己的想法诉诸于机器代码的过程中&#xff0c;常常会将那些可以使得编程更为简单却又冗长的语法遗忘于脑后。 无论你使用的是单字母的标识符还是更易于人脑理解的标识符&…

Xamarin效果第二十二篇之录音效果

在前面文章中简单玩了玩GIS的基本操作、Mark相关、AR、测距、加载三维白模和可扩展浮动操作;今天抽空再来分享一下录音效果;啥也不说了都在效果里:1、首次尝试了开源的Plugin.AudioRecorder结果发现没效果,也可能是我的姿势不对:https://github.com/NateRickard/Plugin.AudioRe…

从零开始来看一下Java泛型的设计

引言 泛型是Java中一个非常重要的知识点&#xff0c;在Java集合类框架中泛型被广泛应用。本文我们将从零开始来看一下Java泛型的设计&#xff0c;将会涉及到通配符处理&#xff0c;以及让人苦恼的类型擦除。 泛型基础 泛型类 我们首先定义一个简单的Box类&#xff1a; public c…

php json error,PHP 7.3 中的 JSON 错误处理

PHP 7.3 为 json_encode() 和 json_decode() 函数增加的一个新特性使其更好的处理错误。这个特性「 RFC 」以 23 比 0 的投票结果被一致接受。让我们看一看在 PHP 7.2 及一下版本中是如何处理 JSON 错误的&#xff0c;以及 PHP 7.3 中新的改进。背景当前在 PHP7.2 版本中&#…

【C语言简单说】十九:二维数组循环嵌套(2)

这节直接用循环嵌套来输出二维数组了&#xff1a; 注&#xff1a;我说的队和列并不是一般说法&#xff0c;我用此比喻好让新手更好理解。 #include<stdio.h> #include<stdlib.h> int main() {int array[2][3]{1,2,3,4,5,6};//第一句 int i,j;//第二句 for(i0;i&l…

lia人是什么意思_狗狗喜欢舔人到底什么意思?毛孩的心思主人你要懂

很多人都喜欢养狗&#xff0c;因为它们忠诚、淘气、可爱。同时&#xff0c;狗狗也有很多奇怪的习惯&#xff0c;例如&#xff1a;喜欢舔人&#xff0c;喜欢追逐活动的东西等等。不过大多数狗主人通常都会有一个最想知道的问题&#xff1a;为什么狗狗总喜欢舔人&#xff0c;它们…