首页 > 数据库 >POSTGRESQL 事务控制(一) (写着费力,看着费劲系列)

POSTGRESQL 事务控制(一) (写着费力,看着费劲系列)

时间:2023-06-22 13:03:37浏览次数:36  
标签:事务 oldest xid database 费力 POSTGRESQL TransactionId 费劲 ID


POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_面试

最近发现一个问题, 最近写的关于感性的文字如 DBA 职业迷茫何去何从, 和另外一篇都是较高的用户读取量, 而反观到技术性的文字,基本上都不太高, 能到400以上就属于"上帝帮助" 了

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_面试_02

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_python_03

原因我是明白的, 大众化的东西受众必然很多, 反而纯技术性的文字实撰写困难,首先自己要理解, 然后在转化. 很难掺杂个人感情,耗费精力大. 纵然如此个人还是喜欢去搞技术性的东西,主要有两点  1 要靠这个吃饭, 2 个人兴趣. 

这边准备一个深入系列,其实这也是费力不讨好, 自己写的费劲, 大家看的也比较费劲, 估计阅读数也不会太高。

___________________________________________________________________________

本期主要从事务入手, 从POSTGRESQL 的事务的原理由浅入深的开始搞搞更深入的东西, 这一个系列注重原理,源码, 少操作.

PostgreSQL 的事务的形成和处理是通过 Transaction Block 块, 这个Transaction Block 块中会包含, 一条SQL ,或者 N 条SQL . 

下面是postgresql 的在事务处理中的事务可能处于的状态, (为后面和代码连接做准备)

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_python_04

事务处理的分为 begin commit  rollback 三个过程, 这里分别有几个函数来代表功能的完成, 在事务处理中, 主要分为上层, 中层, 下层三种函数

上层

1  BeginTransacionBlock

2  EndTransactionBlock

3  UserAbortTransactionBlock

4  DefineSavepoint

5  RollbackToSavepoint

6  ReleaseSavepoint

中层

1 StartTransactionCommand

2 CommitTransactionCommand

3 AbortTransactionCommand

底层

1 StartTransaction

2 CommitTransation

3 AbortTransaction

4 Cleanup Transaction

5 StartSubTransaction

6 CommitSubTransaction

7 AbortSub Transaction

8 CleanupsSub Transaction

下面通过一个事务的实例来看上面的函数和状态如何应用到事务的处理当中

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_java_05

typedef struct TransactionStateData
{
    
    TransactionId transactionId;    
    SubTransactionId subTransactionId;     
    char       *name;        
    int         savepointLevel; 
    TransState  state;         
    TBlockState blockState;     
    int         nestingLevel; 
    int         gucNestLevel;   
   MemoryContext curTransactionContext;   
    ResourceOwner curTransactionOwner;  
    TransactionId *childXids;   
    int         nChildXids;    
    int         maxChildXids;   
    Oid         prevUser;      
    int         prevSecContext; 
    bool        prevXactReadOnly;   
    bool        startedInRecovery; 
    bool        didLogXid;     
    int         parallelModeLevel;   
    struct TransactionStateData *parent;    
} TransactionStateData;

以一个最简单得事务,会使用如下的流程和相关的函数.

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_mysql_06

看上图, 在事务的开始会在 typedef struct TransactionStateData 结构体内修改transState  状态,默认值为trans_default,  在执行了Begin后,会开始获取 transactionID, 然后开始变化结构体的状态, 将状态变为Trans_start, 然后马上执行语句,分配transactionID, 在将事务的状态变为 Trans_inprogress,

在事务运行完毕,并提交是将事务的状态转为 trans_abort.   

在期间调用 heap_insert 函数,将数据插入到数据页面中. 

下图是证明产生事务后,也不见得产生事务ID, 只要整体的事务中没有任何的DML操作, Insert 操作, 则是不会分配事务ID的. 

txid_current_if_assigned()

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_mysql_07

上面的只是非常简单的事务,而复杂的事务,都会包含 子事务, 以及一些回滚点, 如在事务中加载了save point . 则分配事务 SubTransactionId subTransactionId;  

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_面试_08

上面这段代码的就是为事务,以及子事务分配事务号的

if (isSubXact && !TransactionIdIsValid(s->parent->transactionId)

如果是子事务,并且结构体中中没有父的事务ID  则设置初始值

则根据结构体中的 nestingLevel 的级别来分配数组, 

parents = palloc(sizeof(TransactionState) * s->nestingLevel);

然后通过循环得方式, 为每一个子事务来分配事务ID

while (p != NULL && !TransactionIdIsValid(p->transactionId))
        {
            parents[parentOffset++] = p;
            p = p->parent;
        }

PG 获取事务ID 主要是通过无符号整型事务ID的计数器来分配事务ID ,ID是一个32位的整型递增的趋势,通过

1  结构体

2  缓存计数器

3  分配函数

三个部分组成事务ID的分配的任务.

同时由于FREEZEING的问题, 在分配事务ID的时候还要进行相关的判断,判断当前的分配得事务号,是否已经到了警戒线.

下面是这段分配事务ID 的代码, 及个人理解注解

/*
 
TransactionId
GetNewTransactionId(bool isSubXact)
{
    TransactionId xid;
   
    if (IsInParallelMode())
        elog(ERROR, "cannot assign TransactionIds during a parallel operation");
 
#如果在并行模式,则不会进行分配事务ID
 
    
    if (IsBootstrapProcessingMode())
    {
        Assert(!isSubXact);
        MyPgXact->xid = BootstrapTransactionId;
        return BootstrapTransactionId;//--> 1
    }
    #事务初始化之初先进行事务的ID的初始化
   
    if (RecoveryInProgress())
        elog(ERROR, "cannot assign TransactionIds during recovery");
    LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 
#如果事务在运行的模式,则不能分配事务ID, 否则获取LW锁,准备进行事务ID的分配
 
    
    xid = ShmemVariableCache->nextXid;
# 这里通过内存中的共享结构分配一个事务ID
 
#此时为了防止PG数据库事务ID回卷, 需要开始对分配事务ID 与现存最大的事务ID进行比较
#这里有三个设置,
#1 xidVacLimit
#2 xidWarnLimit
#3 xidStopLimit
 
#三个值分别代表, 触发xidVacLimit ,系统则自动开始进行autovacuum的操作
#xidwarnLimit 则系统开始发出警告
#xidStopLimit 则系统开始停止工作,进入单用户模式
    
 
    
    if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidVacLimit))
    {
        
        TransactionId xidWarnLimit = ShmemVariableCache->xidWarnLimit;
        TransactionId xidStopLimit = ShmemVariableCache->xidStopLimit;
        TransactionId xidWrapLimit = ShmemVariableCache->xidWrapLimit;
        Oid         oldest_datoid = ShmemVariableCache->oldestXidDB;
        LWLockRelease(XidGenLock);
        
#当当前的事务ID 除以65536 余数为0 则触发autovacuum的机制
        if (IsUnderPostmaster && (xid % 65536) == 0)
           
            SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
#如果触发了xidStgopLimit
        if (IsUnderPostmaster &&
            TransactionIdFollowsOrEquals(xid, xidStopLimit))
        {
            #获取这个数据库的oid 并转换为数据库名, 然后就开始疯狂的发送告警了
            char       *oldest_datname = get_database_name(oldest_datoid);
         
            if (oldest_datname)
                ereport(ERROR,
                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                         errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"",
                                oldest_datname),
                         errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
                                 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
            else
                ereport(ERROR,
                        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                         errmsg("database is not accepting commands to avoid wraparound data loss in database with OID %u",
                                oldest_datoid),
                         errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
                                 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
        }
        else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
        {
          
            char       *oldest_datname = get_database_name(oldest_datoid);
          
            if (oldest_datname)
                ereport(WARNING,
                        (errmsg("database \"%s\" must be vacuumed within %u transactions",
                                oldest_datname,
                                xidWrapLimit - xid),
                         errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
                                 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
            else
                ereport(WARNING,
                        (errmsg("database with OID %u must be vacuumed within %u transactions",
                                oldest_datoid,
                                xidWrapLimit - xid),
                         errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
                                 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
        }
        /* Re-acquire lock and start over */
        
        # 在每一次获取事务ID 都会触发一次报警,警告信息在上面完成
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
        xid = ShmemVariableCache->nextXid;
 
    }
    #相关的事务ID 需要在CommitTs, WAL LOG中进行更新
    ExtendCLOG(xid);
    ExtendCommitTs(xid);
    ExtendSUBTRANS(xid);
 
    #然后将xid在缓冲结构中更新为下一次其他事务获取ID做准备
    TransactionIdAdvance(ShmemVariableCache->nextXid);
#同时需要判断是否是子事务
if (!isSubXact)
    #不是将就XID 放入缓冲共享体中,让事务开始对其他事务的可见性起作用
        MyPgXact->xid = xid;    
    else               
#如果不是则就是子事务,那么就需要进行循环,为每一个子事务,在这个事务上+1

    {
        int         nxids = MyPgXact->nxids;
        if (nxids < PGPROC_MAX_CACHED_SUBXIDS)
        {
            MyProc->subxids.xids[nxids] = xid;
            pg_write_barrier();
            MyPgXact->nxids = nxids + 1;
        }
        else
            MyPgXact->overflowed = true;
    }
    
    LWLockRelease(XidGenLock);
    return xid;
 
#此时一个整体的事务分配才完成
以下是 shmemVariableCache 的缓存结构
typedef struct VariableCacheData
{
   
    Oid         nextOid;        
    uint32      oidCount;      
    TransactionId nextXid;     
    TransactionId oldestXid;    #当前最小的xid
    TransactionId xidVacLimit;  #存储触发autovacuum的xid预设值
    TransactionId xidWarnLimit; #告警的xid预设值
    TransactionId xidStopLimit; #设置停止工作的xid预设值
   TransactionId xidWrapLimit;  #设置整体系统冻结时的XID预设值
   
    Oid         oldestXidDB;    #当前拥有最老的事务的数据库OID
    TransactionId oldestCommitTsXid;  最老的commit id
    TransactionId newestCommitTsXid;  最新的commit xid
    TransactionId latestCompletedXid;   
    TransactionId oldestClogXid;    /* oldest it's safe to look up in clog */
} VariableCacheData;
typedef VariableCacheData *VariableCache;
VariableCache ShmemVariableCache = NULL;


通过阅读代码可以今天可以了解如下

1  数据库freeze 的触发机制是在分配事务ID时触发的

2  数据库从强制autovacuum 到 FREEZE 是有一个过程的, 相关的的警告信息会不断在这期间进行发送

3  触发事务autovacuum 的机制          if (IsUnderPostmaster && (xid % 65536) == 0)

4   尽量不要建立太多的子事务, 原因从分配事务ID 也可以看出来,save point的功能怎么使用心理的有点数

POSTGRESQL  事务控制(一)   (写着费力,看着费劲系列)_python_09


标签:事务,oldest,xid,database,费力,POSTGRESQL,TransactionId,费劲,ID
From: https://blog.51cto.com/u_14150796/6534684

相关文章

  • PostgreSQL 怎么通过命令来恢复删除的数据
    最近一段工作较忙,更新的速度可能会减慢,敬请见谅,后期采用隔天更新的方式误删除数据的情况,一般都是通过备份,或日志来进行恢复,当然ORACLEFLASHBACK的模式实际上也是对一定期限的数据进行数据的恢复。 对于POSTGRESQL是否可以进行这样的操作,根据POSTGRESQL的原理来说是可以的。下面......
  • POSTGRESQL 15 pg_basebackup 新功能,LOCAL backup 与 数据强力压缩
    与MYSQL不一样,开源XTRABACKUP的备份软件无法跟上MYSQL版本的更迭,PG这点做的是一贯的好。从来没有让人失望过。所以POSTGRESQL数据的备份一直就不是一个问题,众多的工具以及pg_basebackup良好的功能,让POSTGRESQL备份起来速度与硬件有关。但基于POSTGRESQL本身的原理,数据库表......
  • POSTGRESQL 提高POSTGRESQL性能的一些习惯 (1)
    PostgreSQL是一个很有意思的数据库,在使用中有一些习惯可以在同等的硬件下,更加有效的使用硬件提供的资源,让管理和使用POSTGRESQL获得更多的性能。下面就说说一些使用POSTGRESQL的习惯。1 是否需要降低文件的数量POSTGRESQL的文件很多,这里指的文件的数量,主要指两方面的的文件,数......
  • POSTGRESQL VS MYSQL 到底那个数据库 RDS 技术含量高 ?
    以下内容纯属个人看法云数据库的RDS产品,在传统开源的系列里面大致可以选择的是POSTGRESQL和MYSQL两种,诚然在RDS的里面大部分产品最终的选择还是MYSQL,今天不想讨论产品的量,而是想讨论以下产品的难度,RDS产品在POSTGRESQL和MYSQL两种产品的难度问题。先说结果,POSTGRESQL......
  • Postgresql 如何降低 wal 占用磁盘空间,降低磁盘存储成本
    POSTGRESQLWAL的存储一直是一个值得讨论的问题,到底一个POSTGRESQL在极端的情况下,可以用多少的空间来存储WAL日志。这里不是要讨论逻辑复制槽,也不是讨论ARCHIVE,这里要讨论是一种极端的方法,尝试将POSTGRESQLWAL占用的磁盘空间最小化。这里主要针对的对象是,单机的POSTGRESQL,不......
  • POSTGRESQL 存储过程--如何写出新版本PG的存储过程的小案例
    随着问问题的同学越来越多,公众号内部私信回答问题已经很困难了,所以建立了一个群,关于各种数据库的问题都可以,目前主要是POSTGRESQL,MYSQL,MONGODB,POLARDB,REDIS,SQLSERVER等,期待你的加入,最近在开始研究POSTGRESQL的存储过程,主要的原因有以下几个1因为要开发适合目前公司中......
  • POSTGRESQL 提高POSTGRESQL性能的一些习惯 (3)
    随着问问题的同学越来越多,公众号内部私信回答问题已经很困难了,所以建立了一个群,关于各种数据库的问题都可以,目前主要是POSTGRESQL,MYSQL,MONGODB,POLARDB,REDIS,SQLSERVER等,期待你的加入这个系列写到第三期了,实际上POSTGRESQL的优化和一个核心之一,这就是VACUUM,一个弄不清vac......
  • POSTGRESQL 统计信息与数据查询的准确性与多种统计信息类型
    开头还是介绍一下群,如果感兴趣polardb,mongodb,mysql,postgresql,redis等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。自己做了一个统计分析有关的,思维导图之前有一期说过,对于一些特殊的查询中的优化,可以在不建立索引和SQL优化的情况下,我们通过统计......
  • POSTGRESQL vacuum_freeze系列中 三个参数与 vacuum的关系
    开头还是介绍一下群,如果感兴趣polardb,mongodb,mysql,postgresql ,redis等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。最近在整理VACUUM相关知识的时候,发现一个问题对于vacuum_freeze的3个参数的概念掌握的不牢固,那么只能进行恶补了。本次的三个......
  • PostgreSQL 从熊灿灿一个获取固定字符的SQL 分析巧妙之处
    开头还是介绍一下群,如果感兴趣polardb,mongodb,mysql,postgresql,redis等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。某天群里一个同学抛出一个问题,关于获取字段类型中的设置的值,随即熊老师在群里抛出以下的一个SQL (秒抛)SELECTCASEatttypid......