PostgreSQL保存点/子事务(SAVEPOINT)你想要了解的那些事

本文深入探讨PostgreSQL的子事务(SAVEPOINT)功能,包括其用法、基础实现、可见性、事务日志及性能问题。文章介绍了如何定义、回滚和释放保存点,并对比了Greenplum和MySQL中的子事务实现。同时,讨论了子事务可能导致的XID膨胀和缓存溢出问题,以及优化方案和未来发展方向。
摘要由CSDN通过智能技术生成

概述

PostgreSQL是当今最广泛应用的数据库系统(DBMS)之一。除了由于其具有优秀的性能、良好的兼容性之外,其完全开源的特性、完整的事务能力也是其中重要的原因。PostgreSQL支持完整的ACID特性,支持RC/RR/SSI等隔离级别。

PostgreSQL除了基础的事务能力之外,还提供了子事务的能力,也就是保存点(SAVEPOINT)的功能。保存点功能能够支持在发生错误时自动回滚到上一保存点,而无需回滚整个事务;或者在任意时刻回滚到某一特定保存点的状态,而放弃事务中的部分修改。

由于PostgreSQL保存点/子事务的强大能力,其被广泛应用与PLSQL/UDF/Function、Webservice(django),数据同步服务保证exactly-once等诸多场景。

本文主要面向数据库内核开发人员、高级DBA群体、Web开发工程师群体,会首先介绍下保存点的基础用法;然后结合源码介绍PostgreSQL中子事务的基础实现,并着重介绍其可见性、事务日志相关的实现;并且讨论Postgresql 子事务使用过程中可能会出现的性能问题,还会介绍Greenplum中的两阶段事务与子事务的结合,之后会简要介绍MySQL中的子事务做简单对比,最后会对本文内容做个总结并介绍下PostgreSQL的未来方向。

子事务怎么用

PostgreSQL在基础的事务能力之外,提供了子事务的能力。具体来说,PG支持创建子事务(SAVEPOINT)、回滚子事务(ROLLBACK TO SAVEPOINT)、释放子事务(RELEASE SAVEPOINT)等操作。PostgreSQL能够提供在发生错误时自动回滚到上一保存点,而无需回滚整个事务;或者在任意时刻回滚到某一特定保存点的状态,而放弃事务中的部分修改的能力。

SAVEPOINT语法可以定义一个保存点;

ROLLBACK TO SAVEPOINT语法支持回滚到指定保存点;

RELEASE SAVEPOINT语法支持释放之前定义的保存点,但是保存点被释放,并不会导致中间做的修改失效。

详见例子:

BEGIN;
    INSERT INTO table1 VALUES (1);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (2);
    ROLLBACK TO SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (3);
COMMIT;
上面的事务将插入值 1 和 3,但不会插入 2。


要建立并且稍后销毁一个保存点:
BEGIN;
    INSERT INTO table1 VALUES (3);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (4);
    RELEASE SAVEPOINT my_savepoint;
COMMIT;
上面的事务将插入 3 和 4。

值得注意的是子事务与主事务在COMMIT和ABORT时与主事务的区别和联系。

0)子事务一定需要在父事务中定义,无法不定义父事务而单独定义某个子事务;

1)无法单独COMMIT某个子事务/保存点,父事务提交时会自动提交其中定义的所有子事务;

2)在没有子事务的场景下,内部错误或者外部ROLLBACK命令都一定必须ABORT整个事务;

3)接受外部ROLLBACK指令而产生的ROLLBACK,会ABORT当前父事务和其中定义的所有的子事务;

4)内部错误产生的ROLLBACK会ROLLBACK当前子事务,而不影响之前定义的子事务的状态。

CREATE TABLE table1(a int);

postgres=# BEGIN;
BEGIN
postgres=#     INSERT INTO table1 VALUES (1);
INSERT 0 1
postgres=#     INSERT INTO table1 VALUES (1, 2);
ERROR:  INSERT has more expressions than target columns
LINE 1: INSERT INTO table1 VALUES (1, 2);
                                      ^
postgres=# COMMIT;
ROLLBACK
上面的COMMIT将自动执行ROLLBACK,任何值都不会被插入;事务一定出错,必须ABORT整个事务。


BEGIN;
    INSERT INTO table1 VALUES (1);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (2);
ROLLBACK;
上面的事务将不会插入任何值;

BEGIN;
    INSERT INTO table1 VALUES (1);
    SAVEPOINT my_savepoint;
    INSERT INTO table1 VALUES (1,2);
    ROLLBACK TO SAVEPOINT my_savepoint;
COMMIT;
上面的事务会成功插入1;

再次总结,PG通过子事务提供:

0)发生错误时自动回滚到上一保存点,而无需回滚整个事务;

1)或者在任意时刻回滚到某一特定保存点的状态,而放弃事务中的部分修改;

的能力。

子事务实现——基础实现

在前面的章节里面,我们介绍了PG中子事务/保存点的使用方法,实际上PG生态的大多数数据库,比如Greenplum,GaussDB对于子事务的使用都是一致的。这一章节我们主要结合源码(PG9.4)介绍PG关于子事务的实现原理。

本章节主要面向数据库内核开发者和高级DBA,推荐阅读本章节之前,需要对PG的基于状态机的事务模型有一些基本了解;这一方面的资料推荐阅读笔者的前一篇关于PG事务与分布式事务的文章。

总体来说,PG的子事务是栈模式,每次新创建子事务就压栈进去。而如果当前事务中如果有多个子事务,则前一个子事务是它后面一个子事务的父事务(parent);通过不断地压栈和出栈,修改事务块状态实现子事务的定义、回滚、提交。后续章节会结合子事务的基本操作来讲述PG的子事务基础实现。

DEFINE SAVEPOINT

定义保存点是子事务的基本使用方法:

SAVEPOINT savepoint_name;

执行SAVEPOINT的时候会调用DefineSavepoint()函数,主要是调用PushTransaction()进行如下操作:

1、检查当前事务的子事务数目(currentSavepointTotal)是否超过总数限制,如果超限则打出WARN;

2、新建一个TransactionState,关联当前子事务;

3、为当前子事务分配SubTransactionId,如果超过了2^32-1个子事务,则打出ERROR;

4、将当前子事务的TransactionState的parent指针指向当前事务的父事务CurrentTransactionState,填入subTransactionId和nestingLevel;

5、将当前的全局事务CurrentTransactionState置为新分配的子事务;

在这个Command对应的CommitTransactionCommand中,还会对新建的子事务调用StartSubTransaction,进一步完成一些事务的初始化。

而PG中子事务的TransactionId的分配是lazy模式,在实际需要事务ID(比如具体的DML)的时候才会分配。

其实PG的子事务是栈模式,每次新创建子事务就压栈进去。而如果当前事务中如果有多个子事务,则前一个子事务是它后面一个子事务的父事务(parent);

ROLLBACK TO SAVEPOINT

这个命令将当前事务ROLLBACK到一个指定的SAVEPOINT点,后台具体实现为:

1、从当前事务CurrentTransactionState开始,逐层向上遍历,寻找要rollback的savepoint对应的事务;

2、修改最新事务到被rollback到的指定事务之间的事务的状态为TBLOCK_SUBABORT_PENDING TBLOCK_SUBABORT_END;

3、将找到的事务块的状态置为TBLOC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值