当前位置: 首页 > 科技观察

超详细的PG事务隔离级别总结,值得收藏

时间:2023-03-18 22:15:12 科技观察

今天主要介绍一下PG事务隔离。事务隔离和锁机制是密切相关的。我希望你能掌握两者。我们先来看看SQL标准的四种隔离级别。四个隔离级别SQL标准定义了四个隔离级别。最严格的是可串行化,它由标准中的一整段定义,它说一组可串行事务的任何并发执行都保证执行以及按某种顺序逐个执行它们。其他三个层次是利用并发事务之间相互作用产生的现象来定义的,每个层次都要求一种现象一定不能发生。请注意,由于可序列化的定义,在这个级别上这些现象都不可能发生。各级禁止的现象有:脏读:一个事务读取另一个并行未提交事务写入的数据。不可重复读:一个事务重新读取之前读取的数据,发现数据已经被另一个事务修改(初次读取后提交)。幻读:一个事务重新执行一个查询,该查询返回一组匹配搜索条件的行,发现匹配该条件的行集因为另一个最近提交的事务而发生了变化。SerializationException:成功提交一组事务的结果与一次运行这些事务的所有可能顺序不一致。SQL标准和PostgreSQL实现的事务隔离级别如下:事务隔离级别在PostgreSQL中,可以请求四种标准事务隔离级别中的任何一种。但在内部,只实现了三种不同的隔离级别,即:PostgreSQL的read-uncommitted模式表现得像read-committed。这是因为这是将标准隔离级别映射到PostgreSQL的多版本并发控制体系结构的唯一合理方法。要设置一个事务的事务隔离级别,使用SETTRANSACTION命令SETTRANSACTIONtransaction_mode[,...]SETTRANSACTIONSNAPSHOTsnapshot_idSETSESSIONCHARACTERISTICSASTRASACTION_idSETSESSIONCHARACTERISTICSASTRASACTIONe[,...ISOLATIONLEVEL{SERIALIZABLE|REPEATABLEREAD|READCOMMITTED|READUNCOMMITTED}READWRITE|READONLY[NOT]DEFERRABLE1。读已提交隔离级别ReadCommitted是PostgreSQL中的默认隔离级别。当使用此隔离级别运行事务时,查询(不带FORUPDATE/SHARE子句)只能看到在查询开始之前已经提交的数据,而看不到未提交的数据或查询执行期间其他事务提交的数据。(假设SQL查询10s,这10s中间插入的新数据是看不到的)其实SELECT查询看到的是查询开始运行那一刻的数据库快照。但是,SELECT可以看到先前在其自己的事务中执行的更新的影响,即使它们尚未提交。另请注意,即使在同一事务中,两个相邻的SELECT命令也可能会看到不同的数据,因为其他事务可能已在第一个SELECT开始和第二个SELECT开始之间提交。UPDATE、DELETE、SELECTFORUPDATE和SELECTFORSHARE命令在搜索目标行时的行为与SELECT相同:它们将仅查找命令启动时已提交的行。带有ONCONFLICTDOUPDATE子句的INSERT行为类似。在读取提交模式下,建议插入的每一行都将被插入或更新。除非有不相关的错误,否则这两个结果之一是有保证的。如果冲突是由另一个事务引起的,其效果对INSERT不可见,则UPDATE子句将用于该行,即使可能没有该命令通常可见的行版本。带有ONCONFLICTDONOTHING子句的INSERT可能不会插入行,因为它会影响另一个对INSERT快照不可见的事务的结果。同样,这只是在读提交模式下的情况。由于上述规则,更新命令可能会看到不一致的快照:它们可以看到并发更新命令对其尝试更新的同一行的影响,但看不到这些命令对数据库中其他行的影响。这种行为使得读取提交模式不适合涉及复杂搜索条件的命令。但是,它对于更简单的情况是正确的。例如,考虑使用如下命令更新银行余额:BEGIN;UPDATEaccountsSETbalancebalance=balance+100.00WHEREacctnum=12345;UPDATEaccountsSETbalancebalance=balance-100.00WHEREacctnum=7534;COMMIT;显然期望第二个事务从帐户行的更新版本开始工作。(这里,考虑到第二次更新期间accounts表数据的变化)由于每个命令只影响一个已经确定的行,让它看到该行的更新版本不会导致任何麻烦的不一致。因为在读提交模式下,每个命令都从包含当时提交的事务的新快照开始,同一事务中的后续命令将看到任何已提交的并行事务的影响。上面的重点是单个命令是否看到了数据库的绝对一致视图。读提交模式提供的部分事务隔离对于很多应用来说已经足够了,而且这种模式快速且易于使用。然而,这并不足以应对所有情况。执行复杂查询和更新的应用程序可能需要比读取提交模式提供的数据库视图更严格一致的视图。2.可重复读隔离级别可重复读隔离级别只看到事务开始前提交的数据;它永远不会看到未提交的数据或在此事务执行期间由并行事务提交的修改(但是,查询可以看到之前在其事务中执行的更新,即使它们尚未提交)。这是比SQL标准对此隔离级别所要求的更强的保证,SQL标准仅描述了每个隔离级别必须提供的最低保护。此级别与已提交读的不同之处在于,可重复读取事务中的查询在事务中第一个非事务控制语句的开头看到快照,而不是事务中当前语句开头的快照。因此,后续单个事务中的SELECT命令看到的是相同的数据(也就是我前面提到的一个事务中两次更新的情况),即看不到事务启动后其他事务提交的修改。由于序列化失败,使用此级别的应用程序必须准备好重试事务。UPDATE、DELETE、SELECTFORUPDATE和SELECTFORSHARE命令在搜索目标行时的行为与SELECT相同:它们只会查找在事务开始时已提交的行。但是,这些目标行在被发现时可能已被其他并发事务更新(或删除或锁定)。在这种情况下,可重复读取事务将等待第一个更新事务提交或回滚(如果它仍在进行中)。如果第一个更新事务回滚,则忽略它的影响,可重复读取事务可以继续更新最初找到的行。但是如果第一个更新事务提交(并且实际上更新或删除行,而不是仅仅锁定它),可重复读取事务将回滚并显示消息错误:由于并发更新无法序列化访问,因为可重复读取事务无法修改或者在可重复读取事务开始后锁定由其他事务更改的行。当应用程序收到此错误消息时,它应该中止当前事务并从头开始重试整个事务。在第二次执行中,事务会将先前提交的更改视为其数据库初始视图的一部分,因此使用新版本的行作为新事务更新的起点没有逻辑冲突。请注意,只有更新事务可能需要重试;只读事务永远不会有序列化冲突。可重复读模式提供了严格的保证,每个事务都可以看到一个完全稳定的数据库视图。但是,此视图并不总是需要与同一级别上并发事务的某些序列化(一次一个)执行保持一致。例如,即使是此级别的只读事务也可能会看到正在更新的控制记录,这表明批处理已完成但看不到作为批处理逻辑部分的详细记录,因为它读取NULL的早期版本记录。3.可序列化隔离级别可序列化隔离级别提供最严格的事务隔离。该级别模拟所有已提交事务的顺序事务执行;就好像交易是一个接一个地顺序执行,而不是并行执行。但是,与可重复读取级别一样,使用此级别的应用程序必须准备好因序列化失败而重试事务。事实上,此功率级别的工作方式与可重复读取完全相同,只是它监视可能导致可序列化事务的并发集合的执行表现得好像这些事务可能是可序列化的(一次)不一致实现的条件。这种监控不会引入repeatablereads之外的blocking,但是监控会产生一些负载,检测到可能导致序列化异常的情况会触发序列化失败。例如,考虑一个表mytab,它最初包含:class|value---+-------1|101|202|1002|200假设可序列化事务A计算:SELECTSUM(value)FROMmytabWHEREclass=1;然后将结果(3)作为类=2的新行的值插入。同时,可序列化事务B计算:SELECTSUM(value)FROMmytabWHEREclass=2;并获得结果300,并将其插入到class=1的新行中。然后两个事务都尝试提交。如果其中一个事务运行在可重复读取隔离级别,则两个事务都被允许提交;但是由于没有序列化的执行顺序在结果上是一致的,使用可序列化事务将允许一个事务提交并回滚另一个事务并伴随此消息:错误:couldnotserializeaccessduetoread/writedependenciesamongtransactions这是因为,如果A在B之前执行,B将计算330而不是300的总和,类似地,另一个序列将导致A计算不同的总和。当依赖可序列化事务来防止异常时,重要的是要注意,在读取它的事务成功提交之前,从持久用户表中读取的任何内容都不会被视为有效。即使对于只读事务也是如此,除了在可延迟只读事务中读取的数据在读取后立即有效,因为这样的事务会等待直到它可以得到一个快照保证,从而避免了这个问题。在所有其他情况下,应用程序不能依赖于稍后中断的事务中的读取结果;相反,他们应该重试交易直到成功。为确保真正的可串行性,PostgreSQL使用谓词锁,这意味着它持有的锁允许它确定写入操作何时会影响并发事务中的先前读取(如果它首先运行)。结果有影响。在PostgreSQL中,这些锁不会导致任何阻塞,因此不会导致死锁。它们用于识别和标记并发可序列化事务之间的依赖关系,这些事务的组合可能会导致序列化异常。反之,一个read-committed或repeatable-read事务想要保证数据的一致性,可能需要对整个表进行锁定,这可能会阻塞其他用户试图使用该表,或者它可能不仅使用阻塞其他用户的事务还导致SELECTFORUPDATE或SELECTFORSHARE进行磁盘访问。与大多数其他数据库系统一样,PostgreSQL中的谓词锁基于事务实际访问的数据。这些谓词锁将以SIReadLock模式显示在pg_locks系统视图中。查询执行期间获取的特定锁将取决于查询使用的计划,许多细粒度锁(如元组锁)可能与少量粗粒度锁(如页锁)组合使用在交易过程中。结合使用以防止用完用于跟踪锁的内存。如果READONLY事务检测到不会发生会导致序列化异常的冲突,它可以在完成之前释放其SIRead锁。事实上,READONLY事务通常能够在启动时确定这一事实并避免使用任何谓词锁。如果您显式请求SERIALIZABLEREADONLYDEFERRABLE事务,它将阻塞直到它可以确定该事实(这是可序列化事务阻塞但可重复读取事务不阻塞的唯一情况)。另一方面,SIRead锁往往需要在事务提交后才持有,直到重叠的读写事务完成。一致使用可序列化事务可以简化开发。保证任何并发可序列化事务的集合将与一次运行一个事务具有相同的效果意味着如果你能证明单个事务在其自身运行时做正确的事情,你可以相信它序列化的事务也可以做正确的事情,即使它不知道其他交易的作用。重要的是,使用此技术的环境具有处理序列化失败的通用方法(它将始终返回“40001”的SQLSTATE值),因为很难准确预测哪些事务可能正在为读/写依赖项执行贡献和需要回滚以防止序列化异常。监控读/写依赖会产生开销,例如重启因序列化失败而中止的事务,但作为这种开销与显式锁和SELECTFORUPDATE或SELECTFORSHARE导致的阻塞之间的平衡,序列化规范化事务是最佳性能选择在某些环境中。