searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

PostgreSQL内核事务系统原理

2023-08-07 07:11:56
169
0

1. 事务系统简介

    PostgreSQL 中负责管理事务运行的模块称为事务管理器,各部分作用如下:

  1. 事务管理器是事务系统的中枢,由一个有限状态自动机实现,接收外部系统的命令或操作系统信息,根据当前事务的状态来决定事务的下一步状态。
  2. 锁管理器实现系统并发控制所需要的锁。
  3. 日志管理器用于记录事务执行的状态和数据的变化过程,包括事务提交日志(CLOG)和事务日志(XLOG)。其中事务提交日志CLOG只记录事务执行的结果状态,事务日志XLOG记录了数据的变化过程并保持了一定的冗余数据。

 

以下为简单查询语句的执行过程:

BEGIN;
SELECT * FROM TABLE_A;
END;
  1. Fork一个Postgres来处理上述命令。
  2. 调用底层事务处理函数启动一个事务,并返回上层。
  3. 读取“BEGIN"语句,改变事务块状态。(后续详解)
  4. 进入事务底层,执行器执行该语句。
  5. 读取”END“语句,提交事务并退出,结束事务块。

2. 事务系统上层

      在 PostgreSQL 的事务处理层次中,位于事务系统上层的是事务块。 PostgreSQL 执行一条 SQL 语 句前会调用 StartTransactionCommand 函数,执行结束时会调用 CommitTransactionCommand 函数。如果 命令执行失败,则会调用 AbortCurrentTransaction 函数。上述三个函数根据事务块的状态执行不同的 操作,并调用不同的底层事务执行函数。PostgreSQL 将事务系统分成上层(事务块)和底层(事务)两个层次,通过分层的设计,在处理上层业务的时候可以屏蔽具体细节。值得注意的是,上述三个函数只是进入事务系统上层的入口函数,并不处理具体事务。

2.1 事务块状态

      代码位置:/src/backend/access/transam/xact.c
      PostgreSQL 中的一个事务处理一条 SQL 语句。事务块的设置使得一个事务能够处理多条 SQL 语句。在每一个 Postgres 进程中,自始至终都只存在一个事务块,当用户开始一个新的 "事务"时并不进入新的事务块,而是修改事务块的状态使之重新开始记录用户"事务"的状态。
      事务块状态指的是整个事务系统的状态,它的变化反映了当前用户输入命令的变化过程和事务底层的执行状态的变化。比如,用户输入 BEGIN/ROLLBACK/END 语句会改变事务块状态,底层事务提交或者终止也会改变该状态。PostgreSQL 把事务块状态定义为一个枚举类型 TBlockstate,如下所示:
typedef enum TBlockState
{
    /* not-in-transaction-block states */
    TBLOCK_DEFAULT,                /* 缺省状态idle */
    TBLOCK_STARTED,                /* 执行简单查询事务running single-query transaction */

    /* transaction block states */
    TBLOCK_BEGIN,                /* 事务开始 starting transaction block */
    TBLOCK_INPROGRESS,            /* 正在处理live transaction */
    TBLOCK_PARALLEL_INPROGRESS, /* 正在并行处理live transaction inside parallel worker */
    TBLOCK_END,                    /* 结束,提交后设置COMMIT received */
    TBLOCK_ABORT,                /* 出错,等待 ROLLBACK failed xact, awaiting ROLLBACK */
    TBLOCK_ABORT_END,            /* 出错,已接收 ROLLBACK failed xact, ROLLBACK received */
    TBLOCK_ABORT_PENDING,        /* 处理中,已接收到 ROLLBACK live xact, ROLLBACK received */
    TBLOCK_PREPARE,                /* 处理中,已接受到 PREPARE live xact, PREPARE received */

    /* subtransaction states 子事务状态, 同上*/   
    TBLOCK_SUBBEGIN,            /* starting a subtransaction */
    TBLOCK_SUBINPROGRESS,        /* live subtransaction */
    TBLOCK_SUBRELEASE,            /* RELEASE received */
    TBLOCK_SUBCOMMIT,            /* COMMIT received while TBLOCK_SUBINPROGRESS */
    TBLOCK_SUBABORT,            /* failed subxact, awaiting ROLLBACK */
    TBLOCK_SUBABORT_END,        /* failed subxact, ROLLBACK received */
    TBLOCK_SUBABORT_PENDING,    /* live subxact, ROLLBACK received */
    TBLOCK_SUBRESTART,            /* live subxact, ROLLBACK TO received */
    TBLOCK_SUBABORT_RESTART        /* failed subxact, ROLLBACK TO received */
} TBlockState;

2.2 事务块操作

        在PostgreSQL中,任何语句的执行都是通过事务块入口函数进入并执行的,执行完毕后, 通过事务块出口函数退出,当系统在执行中遇到错误时,会通过事务块出错处理函数完成相关的出错处理。另外,用户也可以通过 BEGIN/END/ROLLBACK 等命令来改变事务块的状态, PostgreSQL 针对每一个能够改变事务块状态的命令提供了单独的函数。

2.2.1. 事务块基本操作

      基本操作包括StartTransactionCommand、CommitTransactionCommand 和 AbortCurrentTransaction。其中,StartTransactionCommand 在每条语句执行前调用, CommitTransactionCommand 在每条语句执行后调用, AbortCurrentTransaction 在系统遇到错误时调用。
(1)StartTransactionCommand函数
      在 PostgreSQL 中,每条语句执行前,都需要执行该函数,它的作用是通过判断当前事务块的状态进入不同的底层事务执行函数中。因此 ,它是进入事务处理模块的入口函数,直接在总控模块中被调用 。
该函数根据当前事务块的状态决定下 一 步事务操作的方向。例如,假设当前事务块的状态为 TBLOCK_DEFAULT ,那么调用该函数时说明当前系统中还不存在事务,于是调用底层事务操作函数启动一个事务,并设置事务块的状态为 TBLOCK_STARTED。
(2)CommitTransactionCommand函数
      每条 SQL 语句执行后,都需要执行该函数,和 StartTransactionCommand 一 样,也是根据当前事务块的状态决定下一步的事务操作,只不过它在每条语句执行结束后调用 。 假设当前事务块的状态为 TBLOCK_DEFAULT,调用该函数时说明此时系统没有任何事务信息,我们知道 PostgreSQL 中 的任何语句都是在事务中执行的,而该函数又在执行语句后调用,说明现在处于一个非法状态,于是打印错误信息并返回。
参考https://www.postgresql.org/docs/current/sql-commit.html。
(3)AbortCurrentTransaction函数
      当系统遇到错误时,会返回到调用点并执行该函数,其作用也是根据当前事 务块的状态决定下一步的事务操作。只不过该函数在系统遇到错误时调用,其中可能进行事务出错 退出操作或者事务清理操作。 假设当前事务块的状态为 TBLOCK_ DEFAULT,如果系统执 行遇到错误,那么会 返回到 Slg setjmp 函数,然后调用该函数进行出错处理。如果底层事务的状态为 TRANS_DEFAULT,那么说明此时系统中没有任何事务信息,不需要进行事务出错处理,直接返回即可;若当前底层事务状态为 TRANS_START,说明此时底层事务已经存在,现在退出事务块需要调用 AbortTransaction 函数,再调用 CleanupTransaction 函数进行事务清理操作。

2.2.2 事务块状态改变

      如下图所示,其状态改变规则是一个有限状态自动机:

      由上图可知,事务块状态改变函数主要包括BeginTransactionBlock、EndTransactionBlock和UserAbortTransactionBlock函数,主要功能是根据用户输入命令以及系统状态来改变事务块状态。PostgreSQL 提交的任何一个 SQL 语句都被系统默认为一个事务块。若要自定义一个事务块的边界,则要用 BEGIN 和 END 命令来实现。 BEGIN 和 END 命令类似于一个大括号,在这个大括号中的 SQL 语句被系统认为是处于同一个事务块中的语句。

(1) 执行BEGIN命令

      执行 BEGIN 命令后,系统会进入一个事务块,这里状态的改变是调用函数 BeginTransactionock 来完成的。该函数判断当前事务块状态,如果事务块状态为 TBLOCK _ STARTED ,说明当前事务还没有进入到真正的事务块中,那么把事务块状态置为 TBLOCK_BEGIN , 说明这是一个事务块的开始。如果当前事务块状态是 TBLOCK_INPROGRESS 或者 TBLOCK_SUBIN PROGRESS ,说明已经有一个事务块正在运行,这时系统会输出一个警告。对于BeginTransaction8Iock 函数来说, TBLOCK_END、 TBLOCK_ABORT_END 等状态是无效的,一旦该函数遇到这些无效状态, PostgreSQL 会抛出一个错误 。

(2) 执行END命令

      PostgreSQL 在接收到 END 命令时会调用函数 EndTransactionBlock ,该函数指示一个事务块的结束。由于一个事务块的结束可能由 COMMIT 造成,也可能由 ROLLBACK 造成,所以 EndTransactionBlock 将根据返回值来确定系统的动作,返回 TRUE 表示函数成功 COMMIT,返回 FALSE 表示 ROLLBACK。 该函数的操作过程如下:

      1 )判断当前事务块状态。 如果事务正在执行当中,即事务块状态为 TBLOCK_INPROGRESS, 则把事务块状态改为 TBLOCK_END ,同时把返回值置为 TRUE; 如果事务块状态为TBLOCK_SUBINPROGRESS,说明当前处于一个活动的子事务内,递归地把子事务块状态置为 TBLOCK_SUBEND, 最后把顶层事务块状态改为 TBLOCK_END ,同时把返回值置为 TRUE。

      2)如果当前事务失败,即事务块状态为 TBLOCK_ABORT ,则需要退出该事务块,把事务块状态置为 TBLOCK_ABORT_END ,同时返回 FALSE; 如果当前事务块状态是TBLOCK _ SUBABORT,即我们处于失败的子事务中,这时把整个事务看成是一个失败事务,递归地把子事务块状态置为 TBLOCK_SUBABORT_END ,最后把顶层事务块状态置为 TBLOCK_ABORT_END ,同时返回 FALSE。

(3) 执行 ROLLBACK 命令

      函数 UserAbortTransactionBlock 用于执行用户提交的一个 ROLLBACK 命令。如果当前事务块状态是 TBLOCK_ABORT_PENDING ,由于已经收到了用户发来的 ROLLBACK 命令,需要把事务块状态置为 TBLOCK_ABORT_PENDING 以退出该事务块。如果当前事务已经是一个失败事务,事务块状态为 TBLOCK_ABORT ,则只需把该状态置为 TBLOCK_ABORT_END 即可。

3. 事务系统底层

      事务进入到底层事务之后的具体处理过程 , 包括资源和锁的获取及释放 、信号的处理、日志记录等。
/*
 *  transaction states - transaction state from server perspective
 */
typedef enum TransState
{
    TRANS_DEFAULT,              /* idle */
    TRANS_START,                /* transaction starting */
    TRANS_INPROGRESS,           /* inside a valid transaction */
    TRANS_COMMIT,               /* commit in progress */
    TRANS_ABORT,                /* abort in progress */
    TRANS_PREPARE               /* prepare in progress */
} TransState;
TransState表示低层次的是事务状态。事务处理过程中的保存事务状态相关数据则是由TransactionStateData负责:
/*
 *  transaction state structure
 */
typedef struct TransactionStateData
{
    TransactionId transactionId;    /* my XID, or Invalid if none */
    SubTransactionId subTransactionId;  /* my subxact ID */
    char       *name;           /* savepoint name, if any */
    int         savepointLevel; /* savepoint level */
    TransState  state;          /* low-level state */
    TBlockState blockState;     /* high-level state */
    int         nestingLevel;   /* transaction nesting depth */
    int         gucNestLevel;   /* GUC context nesting depth */
    MemoryContext curTransactionContext;    /* my xact-lifetime context */
    ResourceOwner curTransactionOwner;  /* my query resources */
    TransactionId *childXids;   /* subcommitted child XIDs, in XID order */
    int         nChildXids;     /* # of subcommitted child XIDs */
    int         maxChildXids;   /* allocated size of childXids[] */
    Oid         prevUser;       /* previous CurrentUserId setting */
    int         prevSecContext; /* previous SecurityRestrictionContext */
    bool        prevXactReadOnly;   /* entry-time xact r/o state */
    bool        startedInRecovery;  /* did we start in recovery? */
    bool        didLogXid;      /* has xid been included in WAL record? */
    int         parallelModeLevel;  /* Enter/ExitParallelMode counter */
    struct TransactionStateData *parent;    /* back link to parent */
} TransactionStateData;

3.2 事务操作函数

      BeginTransactionBlock、EndTransactionBlock和UserAbortTransactionBlock并没有做实际的事务操作。实际的事务操作由StartTransaction、CommitTransaction、AbortTransaction和CleanupTransaction等函数完成。

3.2.1 启动事务

StartTransaction函数完成事务的初始化工作,如下图所示:
  1. 将全局变量CurrentTransactionState指向TopTransactionStateData,然后将底层事务状态设置为TRANS_START。
  2. 确保旧快照已经被释放,重置事务状态变量,并初始化事务内部计数器。
  3. 使用AtStart_Memory和AtStart_ResourceOwner函数分别完成内存上下文和资源跟踪器的初始化。

3.2.2 提交事务

由CommitTransaction完成,过程如下:
  1. 检查事务状态:确保当前事务状态为TRANS_INPROGRESS,检查完事务状态之后进入预提交过程 。
  2. 触发所有延迟的触发器。
  3. 关闭大对象
  4. 如果修改了pg_database 等系统表,则执行更新操作并通知 Postmaster 进程。这个过程由函数 AtEOXact_UpdatePasswordFile 完成,该函数是事务真正提交前的最后一步。如果执行完 AtEOXact_UpdatePasswordFile 之后系统出现错误异常中断本事务,Postmaster可以接收到这个消息并做善后处理。
  5. 提交事务:把当前事务状态置为 TRANS_COMMIT 了,并执行 RecordTransactionCommit 函数,用于将当前所提交的事务所产生的日志写 回磁盘。返回最后处理完的当前事务或子事务的XID,如果当前事务没有XlD,则返回 lnvalidTransactionld
  6. 清理操作。先释放其他后台进程可见的资源(如文件、缓冲区锁等) ,接着释放锁,再释放进程的本地资源 。
  7. 恢复初始状态:设置事务状态为缺省状态 TRANS_DEFAULT ,然后准备接受下一事务。

3.2.3 退出事务

退出事务由函数AbortTransaction 完成,该函数在系统遇到错误时调用,属于系统终止事务退出时的行为。其流程如下:
  1. 关中断,因为进行事务退出后的清理工作时不能被别的事件打断。
  2. 确保内存上下文和资源跟踪器有效。
  3. 释放所有拥有的轻量锁。
  4. 检查事务状态 。 确保当前事务状态为 TRANS_INPROGRESS 或 TRANS_PREPARE 。
  5. 终止执行事务。
  6. 记录日志。
  7. 清理资源。
  8. 恢复中断。

3.2.4 清理事务

由CleanupTransaction完成,它在系统终止时调用,主要功能是释放事务所 占用的内存资源。与 AbonTransaction 不同的是,该函数是终止事务退出时最后调用的函数,做最后的实际的清理工作。 AhonTransaction 函数并没有实际释放相关资源,只是切换一些资源的状态使其 能够被其他事务在得 。其执行流程如下:
  1. 判断当前事务状态是否为 TRANS_ABORT 并释放Portal内存。
  2. 释放 ResourceOwner、MemoryContext,并把事务状态恢复到默认情况。

3.3 查询事务执行过程实例

以下为简单查询语句的例子。

BEGIN;
SELECT * FROM TABLE_A;
END;

BEGIN

  1. 事务块状态为TBLOCK_DEFAULT。
  2. 调用 StartTransactionCommand 函数。由于当前事务块状态为 TBLOCK_DEFAULT。于是调用 StartTransaction 函数,首先设置事务状态为TRANS_START,然后完成有关内存、缓冲区、锁的处理后设置事务状态为 TRANS_INPROGRESS。最后设置事务块状态为 TBLOCK_STARTED。
  3. 调用 processUtility 函数处理 BEGlN语句.
  4. 遇到 " BEGlN" ,所以调用BeginTransactionBlock 函数处理,它将设置事务块状态为 TBLOCK_BEGlN。
  5. 最后调用 CommitTransactionCommand 函数,由于当前事务块状态为 TBLOCK_BEGIN,于是改变事务块状态为TBLOCK_lNPROGRESS,并准备读取下一条命令 。

SELECT

  1. 调用StartTransactionCommand函数,由于当前事务块状态为 TBLOGK_INPROGRESS,表明正在继续该事务的命令,所以直接返回。
  2. 调用ProcessQuery 函数进入执行器处理 SELECT 语句。
  3. 调用 EndTransactionBlock函数,返回并准备继续读下一个命令。

END

  1. 调用 StartTransactionCommand 函数,当前事务块状态为 TBLOCK_lNPROGRESS,表明正在继续该事务的命令,所以直接返回 。
  2. 调用ProcessUtility 函数处理 END 语句 。
  3. 调用 commitTransactionBlock 函数并设置事务块状态为 TBLOCK_END。
  4. 调用 CommitTransactionCommand 函数,由于当前事务块状态为 TBLOCK_END ,于是调用 CommitTransaction 函数提交事务,首先设置事务状态为 TRANS_COMMIT, 然后真正提交事务,并清理关资源.。等着一切结束后,设置事务状态为 TRANS_DEFAULT 并返回。最后设置事务块状态为 TBLOCK_DEFAULT,整个事务执行结束。

4. 事务保存点和子事务

事务保存点 (SAVEPOINT)用于回滚部分事务。SAVEPOINT可以创建一个保存点,可以用ROLLBACK TRANSACTION TO语句回滚到该保存点。
BEGIN Transantion S;
insert into table1 values(1);
SAVEPOINT P1;

insert into table1 values(2);
SAVEPOINT P2;

insert into table1 values(3);
SAVEPOINT P3;

ROLLBACK TO savepoint P1;
COMMIT;
// 执行结果:向表table1中插入数据1

4.1 保存点实现原理

PostgreSQL 在把 SAVEPOINT 认为是定义子事务的标记。事务状态数据结构 TransactionStaleData中有如下变量:

struct TransactionStateData*parent;  /*指向父事务的指针*/

parent 指针指向本事务的父事务,上例中事务的层次结构如下所示:

子事务的层次描述用一个链栈实现。栈顶元素拥有一个指向其父事务的指针。当启动一个新的子事务时,系统调用PushTransaction 函数把描述该子事务的 TransaclionStale 结构变量压入战中,这 个变量可以标识该事务。相应的. PopTransaclion 函数的功能是把钱顶事务弹出。 PushTransaclion 函数为子事务创建一个 TransactionState 并压人事务状态堆栈中。在函数执行过程中, CurrentTransactionState 会被切换到新创建的事务状态。PopTransaction 函数将当前事务状态弹出堆栈,把 CurrentTransactionState 切换到父事务的事务状态并转换资源所有者、以及事务内存上 下文。 保存点的作用主要就是实现事务的回滚,即从当前子事务回滚到该事务链中的某个祖先事务。 在定义一个保存点时,需要用户输入保存点名称(例如 P1) .从而标识当前子事务。在用户执行了一系列子事务之后回滚到该子事务时( ROLLBACK TO P1) .会调用 Rollback ToSavepoint 函数在事务链中从当前事务节点回溯查找该保存点名称,若找到,就直接把 CurrentTransactionState 指针移到查找到的事务节点处。由于 PostgreSQL中MemeoyContext 的存在,这里并不需要释放抛弃的子事务的相关内存资源,释放资源的操作在最后事务提交或者退出时通过 MemoryContext来实现 。

4.2 子事务

      PG 有一套与事务操作函数类似的子事务操作函数,包括 StartSubTransaction、CommitSubTransaction、CleanupSubTransaction 和AbortSubTransaction 等。主要作用是实现保存点,从而增强事务操作的灵活性。
      在执行 PushTransaction 后会把当前事务块状态置为 TBLOCK_SUBBEGIN ,在接下来为 SAVEPOINT 语句执行的函数 CommitTransactionCommand 中会调用StartSubTransaction 函数并把 blockstate 设为 TBLOCK_SUBINPROGRESS。在系统实现中,上层的事务在创建的过程中可以得到一个 XID ,用于标识自己,这在 VACUUM 的过程中会用到。如果不对数据库进行写操作,就没有必要单独在得事务或其子事务的 XID。 所以对于子事务,在它们调用GetCurrentTransactionId 函数的时候才给它们分配 XID。系统利用子事务 m 标识每个子事务,最上层的子事务的值为 1 ,其子事务为 2 ,依此类推,0用于表示无效的子事务 ID 。
0条评论
0 / 1000
a****n
3文章数
2粉丝数
a****n
3 文章 | 2 粉丝
a****n
3文章数
2粉丝数
a****n
3 文章 | 2 粉丝
原创

PostgreSQL内核事务系统原理

2023-08-07 07:11:56
169
0

1. 事务系统简介

    PostgreSQL 中负责管理事务运行的模块称为事务管理器,各部分作用如下:

  1. 事务管理器是事务系统的中枢,由一个有限状态自动机实现,接收外部系统的命令或操作系统信息,根据当前事务的状态来决定事务的下一步状态。
  2. 锁管理器实现系统并发控制所需要的锁。
  3. 日志管理器用于记录事务执行的状态和数据的变化过程,包括事务提交日志(CLOG)和事务日志(XLOG)。其中事务提交日志CLOG只记录事务执行的结果状态,事务日志XLOG记录了数据的变化过程并保持了一定的冗余数据。

 

以下为简单查询语句的执行过程:

BEGIN;
SELECT * FROM TABLE_A;
END;
  1. Fork一个Postgres来处理上述命令。
  2. 调用底层事务处理函数启动一个事务,并返回上层。
  3. 读取“BEGIN"语句,改变事务块状态。(后续详解)
  4. 进入事务底层,执行器执行该语句。
  5. 读取”END“语句,提交事务并退出,结束事务块。

2. 事务系统上层

      在 PostgreSQL 的事务处理层次中,位于事务系统上层的是事务块。 PostgreSQL 执行一条 SQL 语 句前会调用 StartTransactionCommand 函数,执行结束时会调用 CommitTransactionCommand 函数。如果 命令执行失败,则会调用 AbortCurrentTransaction 函数。上述三个函数根据事务块的状态执行不同的 操作,并调用不同的底层事务执行函数。PostgreSQL 将事务系统分成上层(事务块)和底层(事务)两个层次,通过分层的设计,在处理上层业务的时候可以屏蔽具体细节。值得注意的是,上述三个函数只是进入事务系统上层的入口函数,并不处理具体事务。

2.1 事务块状态

      代码位置:/src/backend/access/transam/xact.c
      PostgreSQL 中的一个事务处理一条 SQL 语句。事务块的设置使得一个事务能够处理多条 SQL 语句。在每一个 Postgres 进程中,自始至终都只存在一个事务块,当用户开始一个新的 "事务"时并不进入新的事务块,而是修改事务块的状态使之重新开始记录用户"事务"的状态。
      事务块状态指的是整个事务系统的状态,它的变化反映了当前用户输入命令的变化过程和事务底层的执行状态的变化。比如,用户输入 BEGIN/ROLLBACK/END 语句会改变事务块状态,底层事务提交或者终止也会改变该状态。PostgreSQL 把事务块状态定义为一个枚举类型 TBlockstate,如下所示:
typedef enum TBlockState
{
    /* not-in-transaction-block states */
    TBLOCK_DEFAULT,                /* 缺省状态idle */
    TBLOCK_STARTED,                /* 执行简单查询事务running single-query transaction */

    /* transaction block states */
    TBLOCK_BEGIN,                /* 事务开始 starting transaction block */
    TBLOCK_INPROGRESS,            /* 正在处理live transaction */
    TBLOCK_PARALLEL_INPROGRESS, /* 正在并行处理live transaction inside parallel worker */
    TBLOCK_END,                    /* 结束,提交后设置COMMIT received */
    TBLOCK_ABORT,                /* 出错,等待 ROLLBACK failed xact, awaiting ROLLBACK */
    TBLOCK_ABORT_END,            /* 出错,已接收 ROLLBACK failed xact, ROLLBACK received */
    TBLOCK_ABORT_PENDING,        /* 处理中,已接收到 ROLLBACK live xact, ROLLBACK received */
    TBLOCK_PREPARE,                /* 处理中,已接受到 PREPARE live xact, PREPARE received */

    /* subtransaction states 子事务状态, 同上*/   
    TBLOCK_SUBBEGIN,            /* starting a subtransaction */
    TBLOCK_SUBINPROGRESS,        /* live subtransaction */
    TBLOCK_SUBRELEASE,            /* RELEASE received */
    TBLOCK_SUBCOMMIT,            /* COMMIT received while TBLOCK_SUBINPROGRESS */
    TBLOCK_SUBABORT,            /* failed subxact, awaiting ROLLBACK */
    TBLOCK_SUBABORT_END,        /* failed subxact, ROLLBACK received */
    TBLOCK_SUBABORT_PENDING,    /* live subxact, ROLLBACK received */
    TBLOCK_SUBRESTART,            /* live subxact, ROLLBACK TO received */
    TBLOCK_SUBABORT_RESTART        /* failed subxact, ROLLBACK TO received */
} TBlockState;

2.2 事务块操作

        在PostgreSQL中,任何语句的执行都是通过事务块入口函数进入并执行的,执行完毕后, 通过事务块出口函数退出,当系统在执行中遇到错误时,会通过事务块出错处理函数完成相关的出错处理。另外,用户也可以通过 BEGIN/END/ROLLBACK 等命令来改变事务块的状态, PostgreSQL 针对每一个能够改变事务块状态的命令提供了单独的函数。

2.2.1. 事务块基本操作

      基本操作包括StartTransactionCommand、CommitTransactionCommand 和 AbortCurrentTransaction。其中,StartTransactionCommand 在每条语句执行前调用, CommitTransactionCommand 在每条语句执行后调用, AbortCurrentTransaction 在系统遇到错误时调用。
(1)StartTransactionCommand函数
      在 PostgreSQL 中,每条语句执行前,都需要执行该函数,它的作用是通过判断当前事务块的状态进入不同的底层事务执行函数中。因此 ,它是进入事务处理模块的入口函数,直接在总控模块中被调用 。
该函数根据当前事务块的状态决定下 一 步事务操作的方向。例如,假设当前事务块的状态为 TBLOCK_DEFAULT ,那么调用该函数时说明当前系统中还不存在事务,于是调用底层事务操作函数启动一个事务,并设置事务块的状态为 TBLOCK_STARTED。
(2)CommitTransactionCommand函数
      每条 SQL 语句执行后,都需要执行该函数,和 StartTransactionCommand 一 样,也是根据当前事务块的状态决定下一步的事务操作,只不过它在每条语句执行结束后调用 。 假设当前事务块的状态为 TBLOCK_DEFAULT,调用该函数时说明此时系统没有任何事务信息,我们知道 PostgreSQL 中 的任何语句都是在事务中执行的,而该函数又在执行语句后调用,说明现在处于一个非法状态,于是打印错误信息并返回。
参考https://www.postgresql.org/docs/current/sql-commit.html。
(3)AbortCurrentTransaction函数
      当系统遇到错误时,会返回到调用点并执行该函数,其作用也是根据当前事 务块的状态决定下一步的事务操作。只不过该函数在系统遇到错误时调用,其中可能进行事务出错 退出操作或者事务清理操作。 假设当前事务块的状态为 TBLOCK_ DEFAULT,如果系统执 行遇到错误,那么会 返回到 Slg setjmp 函数,然后调用该函数进行出错处理。如果底层事务的状态为 TRANS_DEFAULT,那么说明此时系统中没有任何事务信息,不需要进行事务出错处理,直接返回即可;若当前底层事务状态为 TRANS_START,说明此时底层事务已经存在,现在退出事务块需要调用 AbortTransaction 函数,再调用 CleanupTransaction 函数进行事务清理操作。

2.2.2 事务块状态改变

      如下图所示,其状态改变规则是一个有限状态自动机:

      由上图可知,事务块状态改变函数主要包括BeginTransactionBlock、EndTransactionBlock和UserAbortTransactionBlock函数,主要功能是根据用户输入命令以及系统状态来改变事务块状态。PostgreSQL 提交的任何一个 SQL 语句都被系统默认为一个事务块。若要自定义一个事务块的边界,则要用 BEGIN 和 END 命令来实现。 BEGIN 和 END 命令类似于一个大括号,在这个大括号中的 SQL 语句被系统认为是处于同一个事务块中的语句。

(1) 执行BEGIN命令

      执行 BEGIN 命令后,系统会进入一个事务块,这里状态的改变是调用函数 BeginTransactionock 来完成的。该函数判断当前事务块状态,如果事务块状态为 TBLOCK _ STARTED ,说明当前事务还没有进入到真正的事务块中,那么把事务块状态置为 TBLOCK_BEGIN , 说明这是一个事务块的开始。如果当前事务块状态是 TBLOCK_INPROGRESS 或者 TBLOCK_SUBIN PROGRESS ,说明已经有一个事务块正在运行,这时系统会输出一个警告。对于BeginTransaction8Iock 函数来说, TBLOCK_END、 TBLOCK_ABORT_END 等状态是无效的,一旦该函数遇到这些无效状态, PostgreSQL 会抛出一个错误 。

(2) 执行END命令

      PostgreSQL 在接收到 END 命令时会调用函数 EndTransactionBlock ,该函数指示一个事务块的结束。由于一个事务块的结束可能由 COMMIT 造成,也可能由 ROLLBACK 造成,所以 EndTransactionBlock 将根据返回值来确定系统的动作,返回 TRUE 表示函数成功 COMMIT,返回 FALSE 表示 ROLLBACK。 该函数的操作过程如下:

      1 )判断当前事务块状态。 如果事务正在执行当中,即事务块状态为 TBLOCK_INPROGRESS, 则把事务块状态改为 TBLOCK_END ,同时把返回值置为 TRUE; 如果事务块状态为TBLOCK_SUBINPROGRESS,说明当前处于一个活动的子事务内,递归地把子事务块状态置为 TBLOCK_SUBEND, 最后把顶层事务块状态改为 TBLOCK_END ,同时把返回值置为 TRUE。

      2)如果当前事务失败,即事务块状态为 TBLOCK_ABORT ,则需要退出该事务块,把事务块状态置为 TBLOCK_ABORT_END ,同时返回 FALSE; 如果当前事务块状态是TBLOCK _ SUBABORT,即我们处于失败的子事务中,这时把整个事务看成是一个失败事务,递归地把子事务块状态置为 TBLOCK_SUBABORT_END ,最后把顶层事务块状态置为 TBLOCK_ABORT_END ,同时返回 FALSE。

(3) 执行 ROLLBACK 命令

      函数 UserAbortTransactionBlock 用于执行用户提交的一个 ROLLBACK 命令。如果当前事务块状态是 TBLOCK_ABORT_PENDING ,由于已经收到了用户发来的 ROLLBACK 命令,需要把事务块状态置为 TBLOCK_ABORT_PENDING 以退出该事务块。如果当前事务已经是一个失败事务,事务块状态为 TBLOCK_ABORT ,则只需把该状态置为 TBLOCK_ABORT_END 即可。

3. 事务系统底层

      事务进入到底层事务之后的具体处理过程 , 包括资源和锁的获取及释放 、信号的处理、日志记录等。
/*
 *  transaction states - transaction state from server perspective
 */
typedef enum TransState
{
    TRANS_DEFAULT,              /* idle */
    TRANS_START,                /* transaction starting */
    TRANS_INPROGRESS,           /* inside a valid transaction */
    TRANS_COMMIT,               /* commit in progress */
    TRANS_ABORT,                /* abort in progress */
    TRANS_PREPARE               /* prepare in progress */
} TransState;
TransState表示低层次的是事务状态。事务处理过程中的保存事务状态相关数据则是由TransactionStateData负责:
/*
 *  transaction state structure
 */
typedef struct TransactionStateData
{
    TransactionId transactionId;    /* my XID, or Invalid if none */
    SubTransactionId subTransactionId;  /* my subxact ID */
    char       *name;           /* savepoint name, if any */
    int         savepointLevel; /* savepoint level */
    TransState  state;          /* low-level state */
    TBlockState blockState;     /* high-level state */
    int         nestingLevel;   /* transaction nesting depth */
    int         gucNestLevel;   /* GUC context nesting depth */
    MemoryContext curTransactionContext;    /* my xact-lifetime context */
    ResourceOwner curTransactionOwner;  /* my query resources */
    TransactionId *childXids;   /* subcommitted child XIDs, in XID order */
    int         nChildXids;     /* # of subcommitted child XIDs */
    int         maxChildXids;   /* allocated size of childXids[] */
    Oid         prevUser;       /* previous CurrentUserId setting */
    int         prevSecContext; /* previous SecurityRestrictionContext */
    bool        prevXactReadOnly;   /* entry-time xact r/o state */
    bool        startedInRecovery;  /* did we start in recovery? */
    bool        didLogXid;      /* has xid been included in WAL record? */
    int         parallelModeLevel;  /* Enter/ExitParallelMode counter */
    struct TransactionStateData *parent;    /* back link to parent */
} TransactionStateData;

3.2 事务操作函数

      BeginTransactionBlock、EndTransactionBlock和UserAbortTransactionBlock并没有做实际的事务操作。实际的事务操作由StartTransaction、CommitTransaction、AbortTransaction和CleanupTransaction等函数完成。

3.2.1 启动事务

StartTransaction函数完成事务的初始化工作,如下图所示:
  1. 将全局变量CurrentTransactionState指向TopTransactionStateData,然后将底层事务状态设置为TRANS_START。
  2. 确保旧快照已经被释放,重置事务状态变量,并初始化事务内部计数器。
  3. 使用AtStart_Memory和AtStart_ResourceOwner函数分别完成内存上下文和资源跟踪器的初始化。

3.2.2 提交事务

由CommitTransaction完成,过程如下:
  1. 检查事务状态:确保当前事务状态为TRANS_INPROGRESS,检查完事务状态之后进入预提交过程 。
  2. 触发所有延迟的触发器。
  3. 关闭大对象
  4. 如果修改了pg_database 等系统表,则执行更新操作并通知 Postmaster 进程。这个过程由函数 AtEOXact_UpdatePasswordFile 完成,该函数是事务真正提交前的最后一步。如果执行完 AtEOXact_UpdatePasswordFile 之后系统出现错误异常中断本事务,Postmaster可以接收到这个消息并做善后处理。
  5. 提交事务:把当前事务状态置为 TRANS_COMMIT 了,并执行 RecordTransactionCommit 函数,用于将当前所提交的事务所产生的日志写 回磁盘。返回最后处理完的当前事务或子事务的XID,如果当前事务没有XlD,则返回 lnvalidTransactionld
  6. 清理操作。先释放其他后台进程可见的资源(如文件、缓冲区锁等) ,接着释放锁,再释放进程的本地资源 。
  7. 恢复初始状态:设置事务状态为缺省状态 TRANS_DEFAULT ,然后准备接受下一事务。

3.2.3 退出事务

退出事务由函数AbortTransaction 完成,该函数在系统遇到错误时调用,属于系统终止事务退出时的行为。其流程如下:
  1. 关中断,因为进行事务退出后的清理工作时不能被别的事件打断。
  2. 确保内存上下文和资源跟踪器有效。
  3. 释放所有拥有的轻量锁。
  4. 检查事务状态 。 确保当前事务状态为 TRANS_INPROGRESS 或 TRANS_PREPARE 。
  5. 终止执行事务。
  6. 记录日志。
  7. 清理资源。
  8. 恢复中断。

3.2.4 清理事务

由CleanupTransaction完成,它在系统终止时调用,主要功能是释放事务所 占用的内存资源。与 AbonTransaction 不同的是,该函数是终止事务退出时最后调用的函数,做最后的实际的清理工作。 AhonTransaction 函数并没有实际释放相关资源,只是切换一些资源的状态使其 能够被其他事务在得 。其执行流程如下:
  1. 判断当前事务状态是否为 TRANS_ABORT 并释放Portal内存。
  2. 释放 ResourceOwner、MemoryContext,并把事务状态恢复到默认情况。

3.3 查询事务执行过程实例

以下为简单查询语句的例子。

BEGIN;
SELECT * FROM TABLE_A;
END;

BEGIN

  1. 事务块状态为TBLOCK_DEFAULT。
  2. 调用 StartTransactionCommand 函数。由于当前事务块状态为 TBLOCK_DEFAULT。于是调用 StartTransaction 函数,首先设置事务状态为TRANS_START,然后完成有关内存、缓冲区、锁的处理后设置事务状态为 TRANS_INPROGRESS。最后设置事务块状态为 TBLOCK_STARTED。
  3. 调用 processUtility 函数处理 BEGlN语句.
  4. 遇到 " BEGlN" ,所以调用BeginTransactionBlock 函数处理,它将设置事务块状态为 TBLOCK_BEGlN。
  5. 最后调用 CommitTransactionCommand 函数,由于当前事务块状态为 TBLOCK_BEGIN,于是改变事务块状态为TBLOCK_lNPROGRESS,并准备读取下一条命令 。

SELECT

  1. 调用StartTransactionCommand函数,由于当前事务块状态为 TBLOGK_INPROGRESS,表明正在继续该事务的命令,所以直接返回。
  2. 调用ProcessQuery 函数进入执行器处理 SELECT 语句。
  3. 调用 EndTransactionBlock函数,返回并准备继续读下一个命令。

END

  1. 调用 StartTransactionCommand 函数,当前事务块状态为 TBLOCK_lNPROGRESS,表明正在继续该事务的命令,所以直接返回 。
  2. 调用ProcessUtility 函数处理 END 语句 。
  3. 调用 commitTransactionBlock 函数并设置事务块状态为 TBLOCK_END。
  4. 调用 CommitTransactionCommand 函数,由于当前事务块状态为 TBLOCK_END ,于是调用 CommitTransaction 函数提交事务,首先设置事务状态为 TRANS_COMMIT, 然后真正提交事务,并清理关资源.。等着一切结束后,设置事务状态为 TRANS_DEFAULT 并返回。最后设置事务块状态为 TBLOCK_DEFAULT,整个事务执行结束。

4. 事务保存点和子事务

事务保存点 (SAVEPOINT)用于回滚部分事务。SAVEPOINT可以创建一个保存点,可以用ROLLBACK TRANSACTION TO语句回滚到该保存点。
BEGIN Transantion S;
insert into table1 values(1);
SAVEPOINT P1;

insert into table1 values(2);
SAVEPOINT P2;

insert into table1 values(3);
SAVEPOINT P3;

ROLLBACK TO savepoint P1;
COMMIT;
// 执行结果:向表table1中插入数据1

4.1 保存点实现原理

PostgreSQL 在把 SAVEPOINT 认为是定义子事务的标记。事务状态数据结构 TransactionStaleData中有如下变量:

struct TransactionStateData*parent;  /*指向父事务的指针*/

parent 指针指向本事务的父事务,上例中事务的层次结构如下所示:

子事务的层次描述用一个链栈实现。栈顶元素拥有一个指向其父事务的指针。当启动一个新的子事务时,系统调用PushTransaction 函数把描述该子事务的 TransaclionStale 结构变量压入战中,这 个变量可以标识该事务。相应的. PopTransaclion 函数的功能是把钱顶事务弹出。 PushTransaclion 函数为子事务创建一个 TransactionState 并压人事务状态堆栈中。在函数执行过程中, CurrentTransactionState 会被切换到新创建的事务状态。PopTransaction 函数将当前事务状态弹出堆栈,把 CurrentTransactionState 切换到父事务的事务状态并转换资源所有者、以及事务内存上 下文。 保存点的作用主要就是实现事务的回滚,即从当前子事务回滚到该事务链中的某个祖先事务。 在定义一个保存点时,需要用户输入保存点名称(例如 P1) .从而标识当前子事务。在用户执行了一系列子事务之后回滚到该子事务时( ROLLBACK TO P1) .会调用 Rollback ToSavepoint 函数在事务链中从当前事务节点回溯查找该保存点名称,若找到,就直接把 CurrentTransactionState 指针移到查找到的事务节点处。由于 PostgreSQL中MemeoyContext 的存在,这里并不需要释放抛弃的子事务的相关内存资源,释放资源的操作在最后事务提交或者退出时通过 MemoryContext来实现 。

4.2 子事务

      PG 有一套与事务操作函数类似的子事务操作函数,包括 StartSubTransaction、CommitSubTransaction、CleanupSubTransaction 和AbortSubTransaction 等。主要作用是实现保存点,从而增强事务操作的灵活性。
      在执行 PushTransaction 后会把当前事务块状态置为 TBLOCK_SUBBEGIN ,在接下来为 SAVEPOINT 语句执行的函数 CommitTransactionCommand 中会调用StartSubTransaction 函数并把 blockstate 设为 TBLOCK_SUBINPROGRESS。在系统实现中,上层的事务在创建的过程中可以得到一个 XID ,用于标识自己,这在 VACUUM 的过程中会用到。如果不对数据库进行写操作,就没有必要单独在得事务或其子事务的 XID。 所以对于子事务,在它们调用GetCurrentTransactionId 函数的时候才给它们分配 XID。系统利用子事务 m 标识每个子事务,最上层的子事务的值为 1 ,其子事务为 2 ,依此类推,0用于表示无效的子事务 ID 。
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0