PostgreSQL-分布式事务之两阶段提交

什么是ACID

在日常操作中,对于一组相关操作,通常需要其全部成功或全部失败。

在关系型数据库中,将这组相关操作称为“事务”。

在一个事务中,多个插入、修改、删除操作要么全部成功,要么全部失败,这称为“原子性”,实际上一个事务还需要有其他三个特性,即“一致性”“隔离性”“持久性”,英文简称为“ACID”:

  • 原子性(Atomicity):事务必须以一个整体单元的形式进行工作,对于其数据的修改,要么全部执行,要么全都不执行。如果只执行事务中多个操作的前半部分就出现错误,那么必须回滚所有的操作,让数据在逻辑上回滚到原先的状态
  • 一致性(Consistency):在事务完成时,必须使所有的数据都保持在一致状态。(AB转账,A账户5000元,不论他怎么转给B,AB账户合计还是5000元,这就是一致性)
  • 隔离性(Isolation):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务是不会查看中间状态的数据的
  • 持久性(Durability):事务完成之后,它对于系统的影响是永久性的,即使今后出现致命的系统故障(如机器重启、断电),数据也将一直保持

在PG中,可以使用多版本并发控制(MVCC)来维护数据的一致性。相比于锁定模型,其主要优点是在MVCC下对检索(读)数据的锁请求与写数据的锁请求不冲突,读不会阻塞写,而写也不会阻塞读

在PG中提供了表和行级别的锁定语句,让应用能更方便地操作并发数据。

DDL事务

在PG中,与其他数据库最大的不同是,大多数DDL也是可以包含在一个事务中的,而且也是可以回滚的。该功能非常适合把PG作为Sharding的分布式数据系统的底层数据库。

比如在Sharding中常常需要在多个节点中建相同的表,此时可以考虑把建表语句放在同一个事务中,这样就可以在各个节点上先启动一个事务,然后再执行建表语句,如果某个节点执行失败,也可以回滚前面已执行建表成功的操作,自然就不会出现部分节点建表成功,部分节点建表失败的情况。

事务的使用方法

手动设置

set AUTOCOMMIT off

image

BEGIN语句

image

SAVEPOINT

PG支持保存点(SAVEPOINT)的功能,在一个大事务中,可以把操作过程分为几个部分,第一个部分执行成功后可以建一个保存点,若后面的部分执行失败,则回滚到此保存点,而不必回滚整个事务。

image

事务隔离级别

数据库的事务隔离级别有以下4种(不同的数据库可能不同):

  • READ UNCOMMITTED:读未提交
  • READ COMMITTED:读已提交
  • REPEATABLE READ:重复读
  • SERIALIZABLE:串行化

对于并发事务,我们不希望发生不一致的情况,这类情况的级别从高到低排序如下:

  • 脏读:一个事务读取了另一个未提交事务写入的数据。这是我们最不希望发生的,因为如果发生了脏读,则在并发控制上,应用程序会变得很复杂
  • 不可重复读:指一个事务重新读取前面读取过的数据时,发现该数据已经被另一个已提交事务修改了。在大多数情况下,这还是可以接受的,只是在少数情况下会出现问题
  • 幻读:一个事务开始后,需要根据数据库中现有的数据做一些更新,于是重新执行一个查询,返回一套符合查询条件的行,这时发现这些行因为其他最近提交的事务而发生了改变,此时现有的事务如果再进行下去的话,就可能会导致数据在逻辑上的一些错误。

不同事务隔离级别下的行为:

  1. 读未提交:脏读、不可重复读、幻读
  2. 读已提交:不可重复读、幻读
  3. 重复读:幻读
  4. 可串行化:不会发生以上行为

对幻读理解不清楚的,可以参考以下实例

幻读实例

数据准备

image

开启事务更新数据

image
更新操作执行完之后,新开启一个事务插入一条数据
image

更新操作事务提交后,发现有一条数据id=4未更新,像产生了幻觉一样,这就是幻读

PG事务隔离级别

  • 9.1版本之前只有读已提交和可串行化
  • 9.1版本之后增加了重复读

也就是说在PG中,如果你选择了读未提交的级别,实际上还是读已提交;如果选择可重复读级别,有可能实际上仍是可串行化。

PG中默认的隔离级别是读已提交。当一个事务运行于这个隔离级别时,执行SELECT查询(没有FOR UPDATE/SHARE子句)只能看到查询开始之前已提交的数据,而无法看到未提交或者查询期间其他事务已提交的数据;可以看到自身所在事务前面尚未提交的更新结果。实际上,SELECT查询看到的是查询开始运行瞬间的一个快照。

注意:同一个事务中两个相邻的SELECT命令可能看到不同的快照,因为可能有其他事务在第一个SELECT执行完之后,提交了更新结果

两阶段提交

PG数据库支持两阶段提交协议

在分布式系统中,事务中往往包含了多台数据库上的操作,虽然单台数据库的操作能够保证原子性,但多台数据库之间的原子性就需要通过两阶段提交来实现了,两阶段提交是实现分布式事务的关键

两阶段提交有如下5个步骤:

  1. 应用程序先调用各台数据库做一些操作,但不提交事务。然后应用程序调用事务协调器(该协调器可能也是由应用自己实现的)中的提交方法
  2. 事务协调器将联络事务中涉及的每台数据库,并通知它们准备提交事务,这是第一阶段的开始。PG中一般是调用PREPARE TRANSACTION命令
  3. 各台数据库接收到PREPARE TRANSACTION命令后,如果要返回成功,则数据库必须将自己置于如下状态:确保后续能在被要求提交(回滚)事务的时候提交(回滚)事务,所以PG会将已准备好提交的信息写入持久存储区中。如果数据库无法完成此事务,它会直接返回失败给事务协调器
  4. 事务协调器接收所有数据库的响应
  5. 在第二阶段,如果任何一个数据库在第一阶段返回失败,则事务协调器将会发一个回滚命令ROLLBACK PREPARED给各台数据库。如果所有数据库的响应都是成功的,则向各台数据库发送COMMIT PREPARED命令,通知各台数据库事务成功

两阶段提交实例

配置max_prepared_transactions

image
image

重启服务

image

二阶段提交

image

  • 第一阶段:PREPARE TRANSACTION ‘osdba_global_trans_0001’
  • 第二阶段:COMMIT PREPARED ‘osdba_global_trans_0001’

注意:第一阶段结束后,重启服务,模拟数据库宕机。服务重启后,第二阶段提交,数据可以正常查询。

上述命令osdba_global_trans_0001是两阶段提交中全局事务的ID,由事务协调器生成(事务协调器会持久化该ID)。PG数据库一旦成功执行这条命令,也会把事务持久化,即使数据库重启,此事务也不会回滚或丢失

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值