postgres中的并行控制知识整理

postgres中的并行控制

前言

上一篇写了数据库的索引,这一篇主要是整理数据库关于事务与锁的相关内容。

一、postgres的并行控制思想,

PostgreSQL的数据一致性通过使用一种多版本模型来维护,这就意味着每个 SQL 语句看到的都只是一小段时间之前的数据快照。
好处
1.保护语句不会看到可能由其他在相同数据行上执行更新的并发事务造成的不一致数据,为每一个数据库会话提供事务隔离。
2.MVCC避免了传统的数据库系统的锁定方法,将锁争夺最小化来允许多用户环境中的合理性能,
更进一步说,对查询(读)数据的锁请求与写数据的锁请求不冲突,所以读不会阻塞写,而写也从不阻塞读。

二、数据的隔离级别

2.1.四种隔离级别

读未提交:
一个事务读取了另一个并行未提交事务写入的数据,这现象叫脏读。
不过这个不会发生在PG数据库中,因为PG的是基于MVCC看到的数据都是某一刻的一个快照。

读已提交:
多个事务对同一份数据操作,一个事务重新读取之前读取过的数据,发现前后两次数据不同,这现象叫不可重复度。

可重复读:
一个事务重新执行一个返回符合一个搜索条件的行集合的查询, 发现满足条件的行集合因为另一个最近提交的事务而发生了改变,这现象叫幻读。

可序列化:
成功提交一组事务的结果与这些事务所有可能的串行执行结果都不一致,这种现象叫序列化异常。

postgres不会有实现读未提交的隔离级别,
SQL 标准允许更严格的行为:四种隔离级别只定义了哪种现像不能发生,但是没有定义哪种现像必须发生

2.2读已提交隔离级别

(1)读已提交是PostgreSQL中的默认隔离级别。
(2)当一个事务运行使用这个隔离级别时, 一个查询只能看到查询开始之前已经被提交的数据, 而无法看到未提交的数据或在查询执行期间其它事务提交的数据,这是因为SELECT查询看到的是一个在查询开始运行的瞬间该数据库的一个快照。

值得注意:不过SELECT可以看见在它自身事务中之前执行的更新的效果,即使它们还没有被提交。还要注意的是,即使在同一个事务里两个相邻的SELECT命令可能看到不同的数据, 因为其它事务可能会在第一个SELECT开始和第二个SELECT开始之间提交。

在读已提交模式中,每个命令都是从一个新的快照开始的,而这个快照包含在该时刻已提交的事务, 因此同一事务中的后续命令将看到任何已提交的并行事务的效果。以上的焦点在于单个命令是否看到数据库的绝对一致的视图。

读已提交模式提供的部分事务隔离对于许多应用而言是足够的,并且这个模式速度快并且使用简单。 不过,它不是对于所有情况都够用。做复杂查询和更新的应用可能需要比读已提交模式提供的更严格一致的数据库视图。

2.3 可重复读隔离级别

可重复读隔离级别只看到在事务开始之前被提交的数据;它从来看不到未提交的数据或者并行事务在本事务执行期间提交的修改(不过,查询能够看见在它的事务中之前执行的更新,即使它们还没有被提交)。

与读已提交不同之处在于,一个可重复读事务中的查询可以看见在事务中第一个非事务控制语句开始时的一个快照,而不是事务中当前语句开始时的快照。

使用这个级别的应用必须准备好由于序列化失败而重试事务,如果前一个事务操作同一份数据回滚了,那么后者的事务是可以用当前快照版本进行继续操作,如果前一个事务对数据更新了那么后者的事务是无法修改的,只能重试整个事务。

2.4 可序列化隔离级别

可序列化隔离级别提供了最严格的事务隔离。这个级别为所有已提交事务模拟序列事务执行;就好像事务被按照序列一个接着另一个被执行,而不是并行地被执行。但是,和可重复读级别相似,使用这个级别的应用必须准备好因为序列化失败而重试事务。
这个级别会引入监控,监控不会引入超出可重复读之外的阻塞,但是监控会产生一些负荷,并且对那些可能导致序列化异常的条件的检测将触发一次序列化失败。

要保证真正的可序列化,PostgreSQL使用了谓词锁,这意味着它会保持锁,这些锁让它能够判断在它先运行的情况下,什么时候一个写操作会对一个并发事务中之前读取的结果产生影响。在PostgreSQL中,这些锁并不导致任何阻塞,并且因此不会导致一个死锁它们被用来标识和标志并发可序列化事务之间的依赖性,这些事务的组合可能导致序列化异常。

当依赖可序列化事务进行并发控制时,为了最佳性能应该考虑一下问题:

1.在可能时声明事务为READ ONLY。

2.控制活动连接的数量,如果需要使用一个连接池。这总是一个重要的性能考虑,但是在一个使用可序列化事务的繁忙系统中这尤为重要。

3.只在一个单一事务中放完整性目的所需要的东西。

4.不要让连接不必要地“闲置在事务中”。配置参数idle_in_transaction_session_timeout可以被用来自动断开拖延会话的连接。

5.在那些由于使用可序列化事务自动提供的保护的地方消除不再需要的显式锁、SELECT FOR UPDATE和SELECT FOR SHARE。

6.当系统因为谓词锁表内存短缺而被强制结合多个页面级谓词锁为一个单一的关系级谓词锁时,序列化失败的比例可能会上升。你可以通过增加max_pred_locks_per_transaction、max_pred_locks_per_relation或max_pred_locks_per_page来避免这种情况。

7.一次顺序扫描将总是需要一个关系级谓词锁。这可能导致序列化失败的比例上升。通过缩减random_page_cost和/或增加cpu_tuple_cost来鼓励使用索引扫描将有助于此。一定要在事务回滚和重启数目的任何减少与查询执行时间的任何全面改变之间进行权衡。

三.显式锁定

3.1 为什么需要锁

锁模式是用于控制对表中数据的并发访问,这些模式可以用于在MVCC无法给出期望行为的情境中由应用控制的锁。

3.2表锁

两个事务在同一时刻不能在同一个表上持有属于相互冲突模式的锁(但是,一个事务决不会和自身冲突。例如,它可以在同一个表上获得ACCESS EXCLUSIVE锁然后接着获取ACCESS SHARE锁)。非冲突锁模式可以由许多事务同时持有。 请特别注意有些锁模式是自冲突的(例如,在一个时刻ACCESS EXCLUSIVE锁不能被多于一个事务持有)而其他锁模式不是自冲突的(例如,ACCESS SHARE锁可以被多个事务持有)。

一旦被获取,一个锁通常将被持有直到事务结束。 但是如果在建立保存点之后才获得锁,那么在回滚到这个保存点的时候将立即释放该锁。 这与ROLLBACK取消保存点之后所有的影响的原则保持一致。 同样的原则也适用于在PL/pgSQL异常块中获得的锁:一个跳出块的错误将释放在块中获得的锁。

在这里插入图片描述

3.3 行锁

除了表级锁以外,还有行级锁,在下文列出了行级锁以及在哪些情境下PostgreSQL会自动使用它们。行级锁的完整冲突表请见Table 。注意:一个事务可能会在相同的行上保持冲突的锁,甚至是在不同的子事务中。但是除此之外,两个事务永远不可能在相同的行上持有冲突的锁。行级锁不影响数据查询,它们只阻塞对同一行的写入者和加锁者。

PostgreSQL不会在内存里保存任何关于已修改行的信息,因此对一次锁定的行数没有限制。 不过,锁住一行会导致一次磁盘写,例如, SELECT FOR UPDATE将修改选中的行以标记它们被锁住,并且因此会导致磁盘写入。

在这里插入图片描述

3.4 页级锁

除了表级别和行级别的锁以外,页面级别的共享/排他锁被用来控制对共享缓冲池中表页面的读/写。 这些锁在行被抓取或者更新后马上被释放。应用开发者通常不需要关心页级锁,我们在这里提到它们只是为了完整。

3.5 死锁

显式锁定的使用可能会增加死锁的可能性,死锁是指两个(或多个)事务相互持有对方想要的锁。

3.6 咨询锁

PostgreSQL提供了一种方法创建由应用定义其含义的锁。这种锁被称为咨询锁,因为系统并不强迫其使用 — 而是由应用来保证其正确的使用。咨询锁可用于 MVCC 模型不适用的锁定策略。
有两种方法在PostgreSQL中获取一个咨询锁:在会话级别或在事务级别。一旦在会话级别获得了咨询锁,它将被保持直到被显式释放或会话结束。

咨询锁和普通锁都被存储在一个共享内存池中,它的尺寸由max_locks_per_transaction和max_connections配置变量定义。 必须当心不要耗尽这些内存,否则服务器将不能再授予任何锁。这对服务器可以授予的咨询锁数量设置了一个上限,根据服务器的配置不同,这个限制通常是数万到数十万。

四.应用级别的数据完整性检查

对于使用读已提交事务的数据完整性强制业务规则非常困难,因为对每一个语句数据视图都在变化,并且如果一个写冲突发生即使一个单一语句也不能把它自己限制到该语句的快照。

虽然一个可重复读事务在其执行期间有一个稳定的数据视图,在使用MVCC快照进行数据一致性检查时也有一个小问题,它涉及到被称为读/写冲突的东西。如果一个事务写数据并且一个并发事务尝试读相同的数据(不管是在写之前还是之后),它不能看到其他事务的工作。读取事务看起来是第一个执行的,不管哪个是第一个启动或者哪个是第一个提交。如果就到此为止,则没有问题,但是如果读取者也写入被一个并发事务读取的数据,现在有一个事务好像是已经在前面提到的任何一个事务之前运行。如果看起来最后执行的事务实际上第一个提交,在这些事务的执行顺序图中很容易出现一个环。当这样一个环出现时,完整性检查在没有任何帮助的情况下将不会正确地工作。

4.1 用可序列化事务来强制一致性

如果可序列化事务隔离级别被用于所有需要一个一致数据视图的写入和读取,不需要其他的工作来保证一致性。在PostgreSQL中,来自于其他环境的被编写成使用可序列化事务来保证一致性的软件应该“只工作”在这一点上。

当使用这种技术时,如果应用软件通过一个框架来自动重试由于序列化错误而回滚的事务,它将避免为应用程序员带来不必要的负担。把default_transaction_isolation设置为serializable可能是个好主意。通过触发器中的事务隔离级别检查来采取某些动作来保证没有其他事务隔离级别被使用(由于疏忽或者为了破坏完整性检查)也是明智的。

性能建议见Section 13.2.3。

Warning
这个级别的使用可序列化事务的完整性保护还没有扩展到热备份模式(Section 26.5)。由于这个原因,那些使用热备份的系统可能想要在主控机上使用可重复读和显式锁定。

4.2 使用显式锁定强制一致性

当可以使用非可序列化写时,要保证一行的当前有效性并保护它不受并发更新的影响,我们必须使用SELECT FOR UPDATE、SELECT FOR SHARE或一个合适的LOCK TABLE 语句(SELECT FOR UPDATE和SELECT FOR SHARE锁只针对并发更新返回行,而LOCK TABLE会锁住整个表)。当从其他环境移植应用到PostgreSQL时需要考虑这些。

关于这些来自其他环境的转换还需要注意的是SELECT FOR UPDATE不保证一个并发事务将不会更新或删除一个被选中的行。要在PostgreSQL中这样做,你必须真正地更新该行,即便没有值需要被改变。SELECT FOR UPDATE 临时阻塞其他事务,让它们不能获取该相同的锁或者执行一个会影响被锁定行的UPDATE或DELETE,但是一旦正持有该所锁的事务提交或回滚,一个被阻塞的事务将继续执行冲突操作,除非当锁被持有时一个该行的实际UPDATE被执行。

在非可序列化MVCC环境下,全局有效性检查需要一些额外的考虑。例如,一个银行应用可能会希望检查一个表中的所有扣款总和等于另外一个表中的收款总和,同时两个表还会被更新。比较两个连续的在读已提交模式下不会可靠工作的SELECT sum(…)命令, 因为第二个查询很可能会包含没有被第一个查询考虑的事务提交的结果。在一个单一的可重复读事务里进行两个求和则给出在可串行化事务开始之前提交的所有事务产生的准确结果 — 但有人可能会合理地置疑在结果被递交的时候,它们是否仍然相关。 如果可重复读事务本身在尝试做一致性检查之前应用了某些变更,那么检查的有用性就更加值得讨论了, 因为现在它包含了一些(但不是全部)事务开始后的变化。 在这种情况下,一个小心的人可能希望锁住所有需要检查的表,这样才能获得一个无可置疑的当前现状的图像。 一个SHARE模式(或者更高)的锁保证在被锁定表中除了当前事务所作的更改之外,没有未提交的更改。

还要注意如果某人正在依赖显式锁定来避免并发更改,那么他应该使用读已提交模式, 或者是在可重复读模式里在执行命令之前小心地获取锁。 在可重复读事务里获取的锁保证了不会有其它修改该表的事务正在运行,但是如果事务看到的快照在获取锁之前, 那么它可能早于表中一些现在已经提交的更改。 一个可重复读事务的快照实际上是在它的第一个查询或者数据修改命令(SELECT、INSERT、UPDATE或DELETE)开始的时候冻结的,因此我们可以在快照冻结之前显式地获取锁。

五.使用例子

看上面理论这么多了,就该上代码测试测试这些知识点。

5.1事务的测试

5.1.1读取已提交隔离级别的事务测试(默认级别)

1.先插入三条数据到表里,当前会话窗口查看数据入下图
在这里插入图片描述
2.打开第二个窗口逐条执行事务的语句,执行到中间的select语句,如下图
在这里插入图片描述
3.打开第三个会话窗口执行select语句,只执行到第一条语句。如下图
在这里插入图片描述
小结:
从上面的结果可以看到,每一个会话开启了事务,都好像拥有了自己的一章表一样,两个会话的各个事务没有提交前,双方都看不到对方正在做的操作,如果有其中一个事务commit数据,则另一个事务在下一时刻查询该表还是看到该表最新的更新的数据。
所以这个事务正是验证了上面的说默认支持,都已提交的隔离级别。
然后存在不可重复读的现象,是因为如果有第三个事务在这一刻将该表的数据更新,则前面的两个事务重新查询就出现这个现象了,如下图看见的结果一样。
在这里插入图片描述

5.1.2 可重复读取的隔离级别的事务测试

1.在一个会话中开始可重复读取隔离级别事务,先查看当前快照里的数据如下
在这里插入图片描述
2.另一个会话里的事务更新了该表的数据
id=2的数据更新了。
在这里插入图片描述
3.回到另一个可重复读取的事务执行查看表数据。
不楠看出如文档说的一样,这个快照在再次查询的时候同步更新表里的最新数据,所以是可重复级别的隔离。
这说明当前级别的事务的快照的保留起点是开启事务的那一刻的表里的数据,完后不再同步表里的最新数据。
在这里插入图片描述
4.最后它也是允许并行操作表的。

5.1.2 可序列化的隔离级别事务测试(通俗点就是串行的事务,不允许并行操作)

1.一个会话开启了串行事务级别,并执行更行了表数据
在这里插入图片描述
2.另一个会话也开启了串行事务并尝试更新同一张表的数据,并提交事务。
在这里插入图片描述
3.然后前者就报错了。
在这里插入图片描述

5.2数据库默认实现行锁的测试

  1. 第二个会话窗口里的事务对id=1的那条数据进行更新如下图。
    在这里插入图片描述
    2.第三个会话窗口也对id=1的那条数据进行更新如下图,如果再往下执行select的语句会看到运行的三角符号变灰色了,说明当前的更新语句已经堵塞住了,这是因为上面的事务已经获取到了锁了
    在这里插入图片描述
    查看一下事务ID,第二个会话有两个锁,一个独占锁一个共享锁,这是因为第二个会话的事务里,既有更新语句又有插入语句,插入语句是一个独占锁,而更新语句是共享锁。然而独占锁并不会堵着另一个事务,但共享锁就会堵住另一个事务。当只有第二个事务commit之后,第三个事务才能执行下去。
    注意 这里不就引出了死锁的概念了,当个多个事务操作多张表,事务彼此又需要对方释放锁才能向下执行否则就会导致死锁的现象出现。
    在这里插入图片描述
    如果是其他的事务更新不同的行数据,是不会出现堵塞的。
    在这里插入图片描述

5.2制造个死锁的测试。

当三个事务,当彼此需要对方释放锁才能向下执行,否则就会僵持住,然后这个死锁还是被检测出然后报错了,所以只能回滚结束后者的事务。
在这里插入图片描述

5.3显性使用锁的测试。

1.开启锁的方式

LOCK [ TABLE ] name IN  lock_mode

2.没开启事务,并显示使用锁的情况下
在这里插入图片描述
2.开启事务并显示执行锁。
在这里插入图片描述
在这里插入图片描述
所以开始事务显性上锁后,有两个锁的模式,开启事务默认上了独享锁
3.同一个事务里是可以显性使用多个锁的。
在这里插入图片描述
4.一旦这个表锁被某个事务用了,另一个事务再执行某些操作涉及用到锁的问题,就得等到前者的事务释放锁操作。
在这里插入图片描述

重要:
LOCK TABLE … IN ACCESS SHARE MODE需要SELECT对目标表的特权。LOCK TABLE … IN ROW EXCLUSIVE MODE要求INSERT,UPDATE,DELETE,或者TRUNCATE对目标表的权限。所有其他形式的LOCK需要表级UPDATE,DELETE或TRUNCATE特权。

对视图执行锁定的用户必须对视图具有相应的权限。此外,视图的所有者必须对底层基关系具有相关权限,但执行锁定的用户不需要对底层基关系的任何权限。

LOCK TABLE在事务块之外是无用的:锁会一直保持到语句完成。因此,如果在事务块之外使用PostgreSQL,则会报告错误LOCK。使用BEGIN和COMMIT(或ROLLBACK)来定义一个事务块。

LOCK TABLE只处理表级锁,所以涉及的模式名称ROW都是用词不当。通常应将这些模式名称解读为表明用户打算在锁定表中获取行级锁。此外,ROW EXCLUSIVE模式是一个可共享的表锁。请记住,LOCK TABLE就相关而言,所有锁定模式都具有相同的语义,仅在哪些模式与哪些模式冲突的规则上有所不同。有关如何获取实际的行级锁的信息,请参阅第13.3.2节和锁定条款中选择文档。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值