什么是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
BEGIN语句
SAVEPOINT
PG支持保存点(SAVEPOINT)的功能,在一个大事务中,可以把操作过程分为几个部分,第一个部分执行成功后可以建一个保存点,若后面的部分执行失败,则回滚到此保存点,而不必回滚整个事务。
事务隔离级别
数据库的事务隔离级别有以下4种(不同的数据库可能不同):
- READ UNCOMMITTED:读未提交
- READ COMMITTED:读已提交
- REPEATABLE READ:重复读
- SERIALIZABLE:串行化
对于并发事务,我们不希望发生不一致的情况,这类情况的级别从高到低排序如下:
- 脏读:一个事务读取了另一个未提交事务写入的数据。这是我们最不希望发生的,因为如果发生了脏读,则在并发控制上,应用程序会变得很复杂
- 不可重复读:指一个事务重新读取前面读取过的数据时,发现该数据已经被另一个已提交事务修改了。在大多数情况下,这还是可以接受的,只是在少数情况下会出现问题
- 幻读:一个事务开始后,需要根据数据库中现有的数据做一些更新,于是重新执行一个查询,返回一套符合查询条件的行,这时发现这些行因为其他最近提交的事务而发生了改变,此时现有的事务如果再进行下去的话,就可能会导致数据在逻辑上的一些错误。
不同事务隔离级别下的行为:
- 读未提交:脏读、不可重复读、幻读
- 读已提交:不可重复读、幻读
- 重复读:幻读
- 可串行化:不会发生以上行为
对幻读理解不清楚的,可以参考以下实例:
幻读实例
数据准备
开启事务更新数据
更新操作执行完之后,新开启一个事务插入一条数据
更新操作事务提交后,发现有一条数据id=4未更新,像产生了幻觉一样,这就是幻读
PG事务隔离级别
- 9.1版本之前只有读已提交和可串行化
- 9.1版本之后增加了重复读
也就是说在PG中,如果你选择了读未提交的级别,实际上还是读已提交;如果选择可重复读级别,有可能实际上仍是可串行化。
PG中默认的隔离级别是读已提交。当一个事务运行于这个隔离级别时,执行SELECT查询(没有FOR UPDATE/SHARE子句)只能看到查询开始之前已提交的数据,而无法看到未提交或者查询期间其他事务已提交的数据;可以看到自身所在事务前面尚未提交的更新结果。实际上,SELECT查询看到的是查询开始运行瞬间的一个快照。
注意:同一个事务中两个相邻的SELECT命令可能看到不同的快照,因为可能有其他事务在第一个SELECT执行完之后,提交了更新结果。
两阶段提交
PG数据库支持两阶段提交协议。
在分布式系统中,事务中往往包含了多台数据库上的操作,虽然单台数据库的操作能够保证原子性,但多台数据库之间的原子性就需要通过两阶段提交来实现了,两阶段提交是实现分布式事务的关键。
两阶段提交有如下5个步骤:
- 应用程序先调用各台数据库做一些操作,但不提交事务。然后应用程序调用事务协调器(该协调器可能也是由应用自己实现的)中的提交方法
- 事务协调器将联络事务中涉及的每台数据库,并通知它们准备提交事务,这是第一阶段的开始。PG中一般是调用PREPARE TRANSACTION命令
- 各台数据库接收到PREPARE TRANSACTION命令后,如果要返回成功,则数据库必须将自己置于如下状态:确保后续能在被要求提交(回滚)事务的时候提交(回滚)事务,所以PG会将已准备好提交的信息写入持久存储区中。如果数据库无法完成此事务,它会直接返回失败给事务协调器
- 事务协调器接收所有数据库的响应
- 在第二阶段,如果任何一个数据库在第一阶段返回失败,则事务协调器将会发一个回滚命令ROLLBACK PREPARED给各台数据库。如果所有数据库的响应都是成功的,则向各台数据库发送COMMIT PREPARED命令,通知各台数据库事务成功
两阶段提交实例
配置max_prepared_transactions
重启服务
二阶段提交
- 第一阶段:PREPARE TRANSACTION ‘osdba_global_trans_0001’
- 第二阶段:COMMIT PREPARED ‘osdba_global_trans_0001’
注意:第一阶段结束后,重启服务,模拟数据库宕机。服务重启后,第二阶段提交,数据可以正常查询。文章来源:https://www.toymoban.com/news/detail-443308.html
上述命令osdba_global_trans_0001是两阶段提交中全局事务的ID,由事务协调器生成(事务协调器会持久化该ID)。PG数据库一旦成功执行这条命令,也会把事务持久化,即使数据库重启,此事务也不会回滚或丢失。文章来源地址https://www.toymoban.com/news/detail-443308.html
到了这里,关于PostgreSQL-分布式事务之两阶段提交的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!