事务的处理
事务块
从事务形态划分可分为隐式事务和显示事务。隐式事务是一个独立的SQL语句,执行完成后默认提交。显示事务需要显示声明一个事务,多个sql语句组合到一起称为一个事务块。
事务块通过begin
,begin transaction
,start transaction
开始
通过COMMIT
,END
或ABORT
,ROLLBACK
结束,其中COMMIT=END
,ABORT=ROLLBACK
BEGIN;
select * from lzl1 limit 1;
update lzl1 set a=2;
END;
如果事务块执行过程中一旦有报错,由于事务必须满足原子性,事务只能回退
lzldb=# begin;
BEGIN
lzldb=*# select * from lzl2;
ERROR: relation "lzl2" does not exist
LINE 1: select * from lzl2;
^
lzldb=!# commit;
ROLLBACK
事务处理函数
事务处理函数分为3个层次:顶层事务函数、中层事务函数、底层事务函数
顶层事务函数,处理事务块命令,比如BEGIN
, COMMIT
, ROLLBACK
, SAVEPOINT
等,有如下函数
BeginTransactionBlock | 启动事务块 |
---|---|
EndTransactionBlock | 结束事务块 |
UserAbortTransactionBlock | 用户显示结束事务块 |
DefineSavepoint | 生成保存点 |
RollbackToSavepoint | 回滚到某保存点 |
ReleaseSavepoint | 释放保存点 |
中层事务函数,每个sql在执行前后都会调用中层事务函数,包括检测到异常后,有如下函数
StartTransactionCommand | 启动事务命令 |
---|---|
CommitTransactionCommand | 完成事务命令,注意不是提交命令 |
AbortCurrentTransaction | 退出当前事务 |
底层事务函数,真正的事务处理函数,负责维护事务状态、事务资源分配和回收等,有如下函数
StartTransaction | 启动事务 |
---|---|
CommitTransaction | 提交事务 |
AbortTransaction | 回滚/中断事务 |
CleanupTransaction | 清理事务 |
StartSubTransaction | 启动子事务 |
CommitSubTransaction | 提交子事务 |
AbortSubTransaction | 回滚/中断子事务 |
CleanupSubTransaction | 清理子事务 |
其实这上面几个函数还是比较好分辨的。抛开几个特殊的函数(上层相关savepoint
,中层abort
函数),其实上、中、下三层事务层分成了:*Block(事务块函数),*Command(command函数),*Transaction(真正的事务处理函数)。然后把savepoint
子事务当做事务块函数(后面会介绍,子事务可以在事务块中回退,所以这里把子事务放在事务块一级理所当然),把abort
命令当做command级函数就可以了。
事务块状态
上层函数和中层函数同时控制事务块状态,底层函数控制事务状态
事务块状态和事务状态均在src/backend/access/transam/xact.c
typedef enum TBlockState
{
/* 不在事务块中的状态 */
TBLOCK_DEFAULT, /* 空闲状态,事务开始或结束后都处于此状态 */
TBLOCK_STARTED, /* 刚开始进入事务块时的状态,由TBLOCK_DEFAULT转换到此状态,此状态存在时间较短 */
/* 事务块状态 */
TBLOCK_BEGIN, /* 启动事务块,此时才启动数据块,进入数据块级状态 */
TBLOCK_INPROGRESS, /* 活跃的事务,BEGIN以后事务块一直处于此状态,直到事务结束 */
TBLOCK_IMPLICIT_INPROGRESS, /* 隐式BEGIN的活跃事务 */
TBLOCK_PARALLEL_INPROGRESS, /* 并行执行的活跃事务 */
TBLOCK_END, /* 收到COMMIT命令 */
TBLOCK_ABORT, /* 事务失败,等待ROLLBACK */
TBLOCK_ABORT_END, /* 事务失败,收到ROLLBACK */
TBLOCK_ABORT_PENDING, /* 活跃事务,收到ROLLBACK */
TBLOCK_PREPARE, /* 活跃事务,收到PREPARE(显式2PC) */
/* 子事务状态(仍然是事务块级状态) */
TBLOCK_SUBBEGIN, /* 启动子事务 */
TBLOCK_SUBINPROGRESS, /* 活跃的子事务 */
TBLOCK_SUBRELEASE, /* 收到RELEASE(释放保存点) */
TBLOCK_SUBCOMMIT, /* 当子事务还在运行的时候(SUBINPROGRESS),收到父事务COMMIT */
TBLOCK_SUBABORT, /* 失败子事务,等待rollback命令 */
TBLOCK_SUBABORT_END, /* 失败子事务,收到rollback命令 */
TBLOCK_SUBABORT_PENDING, /* 活跃子事务,收到rollback命令 */
TBLOCK_SUBRESTART, /* 活跃子事务,收到rollback to命令 */
TBLOCK_SUBABORT_RESTART /* 失败子事务,收到ROLLBACK TO命令 */
} TBlockState;
大部分状态是显而易见的。需要补充说明的是事务回滚(rollback)和事务失败(ABORT)两者后续行为是相似的,他们都要把清理事务资源和退出当前事务。但是pg把他们分为了两种行为,设置了两种状态TBLOCK_ABORT
,TBLOCK_ABORT_END
(子事务亦然),为什么会这样呢?
在src/backend/access/transam/README
中对此现象作了详细的说明:
场景 1 场景 2 1) 用户输入 BEGIN
1) 用户输入 BEGIN
2) 用户执行某些命令 2) 用户执行某些命令 3) 用户不喜欢她所看到的东西,输入 ABORT
3) 事务系统因为某些原因中断(语法错误等) 场景1中,我们想中断事务并把事务回退到default状态 。
场景2中,可能后续还会有更多的命令,这些命令也是当前事务块的一部分,我们不得不忽略这些命令,直到我们看见
COMMIT
orROLLBACK
。
AbortCurrentTransaction
处理事务内部中断,UserAbortTransactionBlock
处理事务用户中断。两个函数都依赖AbortTransaction
来处理所有真正的工作。唯一区别在于AbortTransaction
工作结束后我们进入了什么状态:* AbortCurrentTransaction leaves us in TBLOCK_ABORT
* UserAbortTransactionBlock leaves us in TBLOCK_ABORT_END(原文如此,不过用户输入结束应该进入TBLOCK_ABORT_PENDING状态)
底层事务中断处理分为两个阶段:
* 一旦我们意识到事务失败,就会执行
AbortTransaction
。这应该释放所有共享资源(锁等),以防不必要的增加其他backends的延迟。* 当我最终看到用户
COMMIT
或者ROLLBACK
时,执行CleanupTransaction
;该函数将清理资源并让我们完全跳出事务。特别是,在此之前我们不能破坏TopTransactionContext
。
事务状态
事务状态一目了然(注意跟事务块状态是不同的)
typedef enum TransState
{
TRANS_DEFAULT, /* 空闲 */
TRANS_START, /* 事务启动 */
TRANS_INPROGRESS, /* 活跃的事务 */
TRANS_COMMIT, /* 事务提交 */
TRANS_ABORT, /* 退出事务 */
TRANS_PREPARE /* prepare事务(2pc) */
} TransState;
事务状态流转
事务块中的一个个命令,调用事务函数,事务函数转变事务、事务块的状态
以一个最简单的事务块举例(参考readme)
1)BEGIN
2)SELECT * FROM foo
3)INSERT INTO foo VALUES (...)
4)COMMIT
命令调用关系:
/ StartTransactionCommand; --中层事务命令启动函数
/ StartTransaction; --底层真正处理启动事务函数
1)< ProcessUtility; --ProcessUtility处理BEGIN命
\ BeginTransactionBlock; --顶层事务块启动函数
\ CommitTransactionCommand; --中层完成命令函数
/ StartTransactionCommand; --中层事务命令启动函数
2) / PortalRunSelect; --SELECT语句执行
\ CommitTransactionCommand; --中层完成命令函数
\ CommandCounterIncrement; --中层完成命令函数
/ StartTransactionCommand; --中层事务命令启动函数
3) / ProcessQuery; --INSERT语句执行
\ CommitTransactionCommand; --中层完成命令函数
\ CommandCounterIncrement; --命令计数器计数+1
/ StartTransactionCommand; --中层事务命令启动函数
/ ProcessUtility; --ProcessUtility处理commit命令
4) < EndTransactionBlock; --调用顶层事务块结束函数
\ CommitTransactionCommand; --中层完成命令函数
\ CommitTransaction; --底层真正处理提交事务函数
- 事务块的每一个命令,都会以中层函数
StartTransactionCommand
和CommitTransactionCommand
开始和结束 - 在以上两个中层函数中间,可以看成真正执行的命令处理
2)SELECT和3)INSERT事务块状态都是TBLOCK_INPROGRESS
,BEGIN
和COMMIT
状态块转换流程如下:
事务函数参考
《postgresql技术内幕》
src/backend/access/transam/README