当前位置: 首页 > news >正文

PostgreSQL中的技术内幕

1、表中的系统字段

每个表都有多个系统字段,这些字段是由系统隐式定义的。这些系统字段在psql中使用“\d”命令返回的结果中并不显示,所以需要记住实际表中还存在这些隐含字段。因为表中已隐含这些名字的字段,所以用户定义的名称不能与这些字段的名称相同,这一限制与名字是否为关键字没有关系,即使字段名称用双引号括起来也不行。

这些系统字段如下。

·oid:行对象标识符(对象ID)。该字段只有在创建表时使用了“with oids”或配置参数“default_with_oids”的值为真时出现。该字段的类型是oid(类型名和字段名相同)。

·tableoid:包含本行的表的oid。对父表(该表存在有继承关系的子表)进行查询时,使用此字段就可以知道某一行来自父表还是子表,以及是来自哪个子表。tableoid可以和pg_class的oid字段连接起来获取表名字。

·xmin:插入该行版本的事务ID。

·xmax:删除此行时的事务ID,第一次插入时,此字段为0。如果查询出来此字段不为0,则可能是删除这行的事务还未提交,或者是删除此行的事务回滚了。

·cmin:事务内部的插入类操作的命令ID,此标识是从0开始的。

·cmax:事务内部的删除类操作的命令ID,如果不是删除命令,此字段为0。

·ctid:一个行版本在它所处的表内的物理位置。后面将重点介绍oid、xmin、xmax、cmin、cmax、ctid,而tableoid比较简单,就不详细介绍了。

1.1 oid

PostgreSQL在内部使用对象标识符(OID)作为各种系统表的主键。系统不会给用户创建的表增加一个oid系统字段,但用户可以在建表时使用“with oids”选项为表增加oid字段。目前oid类型用一个4字节的无符号整数实现,它不能提供大数据范围内的唯一性保证,甚至在单个的大表中也不行。因此,PostgreSQL官方不鼓励在用户创建的表中使用oid字段,建议oid字段只是用于系统表。另外,不同表的oid字段生成的序列值是全局的,就好像所有的oid都使用了一个全局的序列

 1.2 ctid

ctid表示数据行在它所处的表内的物理位置。ctid字段的类型是tid。尽管ctid可以非常快速地定位数据行,但每次VACUUM FULL之后,数据行在块内的物理位置会移动,即ctid会发生变化,所以ctid是不能作为长期的行标识符的,应该使用主键来标识逻辑行。

1.3 xmin,xmax,cmin,xmax

xmin、xmax、cmin、cmax这4个字段在多版本实现中用于控制数据行是否对用户可见。PostgreSQL将修改前后的数据存储在相同的结构中,分为以下几种情况:

·新插入一行时,将新插入行的xmin填写为当前的事务ID,xmax填“0”。

·修改这一行时,实际上新插入一行,原数据行上的xmin不变,xmax改为当前的事务ID,新数据行上的xmin填为当前的事务ID,xmax填“0”。

·删除一行时,把被删除行上的xmax填写当前的事务ID。

从上面的叙述中就可以知道,xmin就是标记插入数据行的事务ID,而xmax就是标记删除数据行的事务ID。

       没有修改数据行的操作,因为修改数据行,实际上就是把原数据行上的xmax标记上自己的事务ID(相当于打上删除标记),然后再新插入一条记录。上面解释了xmin和xmax的含义,另两个字段“cmin”和“cmax”有什么作用呢?cmin和cmax用于判断同一个事务内的不同命令导致行版本的变化是否可见。如果一个事务内的所有命令都是严格顺序执行的,那么每个命令都能看到之前该事务内的所有变更,这种情况下不需要使用命令标识。一般编程中,遍历一个数组或列表时,是不允许在遍历过程中删除或增加元素的,因为这样会导致逻辑错误。而在数据库中,对游标进行遍历时,可以对游标引用的表进行插入或删除行的操作而不会出现逻辑错误,这是因为游标是一个快照,遍历过程中的删除或增加操作不会影响游标的数据,遍历游标时看到的是声明游标时的数据快照而不是执行时的数据,所以它在扫描数据时会忽略声明游标后对数据的变更,因为这些变更对该游标都应该是无效的。

游标后续看到的数据都是声明游标之前的快照,相当于游标与后续的命令并发交错执行,这与事务之间的交错执行类似,存在数据可见性的问题。PostgreSQL使用与解决事务内可见性问题类似的方法引入了命令ID的概念。行上记录了操作这行的命令ID,当其他命令读取这行数据时,如果当前的命令ID大于等于数据行上的命令ID,说明这行数据是可见的;如果当前的命令ID小于数据行上的命令ID,则这条数据不可见。命令ID的分配规则如下:·每个命令使用事务内一个全局命令标识计数器的当前值作为当前命令标识。·事务开始时,命令标识计数器被置为初值“0”。·执行更新性的命令时,如INSERT、UPDATE、DELETE、SELECT...FOR UPDATE,在SQL命令执行后命令标识计数器加1。·当命令标识计数器经过不断累加又回到初值“0”时,报错“cannot have more than 2^32-1 commands in a transaction”,即一个事务中命令的个数最多为232-1个。

2.多版本并发控制

多版本并发控制(Multi-Version Concurrency Control,MVCC),是数据库中并发访问数据时保证数据一致性的一种方法。本节将详细讲解MVCC的原理及PostgreSQL中MVCC实现中的一些特色。

2.1 多版本并发控制原理

在并发操作中,当正在写时,如果有用户在读,这时写可能只写了一半,如一行的前半部分刚写入,后半部分还没有写入,这时可能读的用户读取到的数据行的前半部分数据是新的,后半部分数据是原来的,这就导致了数据一致性问题。解决这个问题的最简单的方法是使用读写锁,写的时候不允许读,正在读的时候也不允许写,但这种方法会导致读和写的操作不能并发执行。于是,有人想到了一种能够让读写并发执行的方法,这种方法就是MVCCMVCC方法是写数据时,原数据并不删除,并发的读还能读到原数据,这样就不会有数据一致性问题了。实现MVCC的方法有以下两种。

·第一种:写新数据时,把原数据移到一个单独的位置,如回滚段中,其他用户读数据时,从回滚段中把原数据读出来。

·第二种:写新数据时,原数据不删除,而是把新数据插入进来。PostgreSQL数据库使用的是第二种方法,而Oracle数据库和MySQL数据库中的InnoDB引擎使用的是第一种方法。10.2.2节将详细讲解PostgreSQL中多版本实现的原理。

2.2 PostgreSQL中的多版本并发控制。

前面讲过,PostgreSQL中的多版本实现是通过把原数据留在数据文件中,新插入一条数据来实现多版本的功能的。如上所述,每张表上都有4个系统字段“xmin”“xmax”“cmin”“cmax”,这4个字段就是为多版本的功能而添加的。当两个事务同时访问记录时,通过参考xmin和xmax的标记判断记录的版本,根据版本号与自己当前的事务标识进行比较,确定自己的数据权限。当删除数据时,记录并没有从数据块中被删除,空间也没有立即释放。

PostgreSQL的多版本实现中首先要解决的是原数据的空间释放问题。PostgreSQL通过运行Vaccum进程来回收之前的存储空间,默认PostgreSQL数据库中的AutoVacuum是打开的,也就是说,当一个表的更新量达到一定值时,AutoVacuum自动回收空间。当然也可以关闭AutoVacuum进程,然后在业务低峰期手动运行VACUUM命令来回收空间。在PostgreSQL中,若一个事务执行失败,在数据文件中该事务产生的数据并不会在事务回滚时被清理掉。为什么要这样做呢?为什么不在事务提交时把这些数据标记成有效,而在事务回滚时把这些数据标记成无效呢?这是出于效率的考虑。若事务提交或回滚时再次标记数据,那这些数据就有可能会被刷新到磁盘中,再次标记会导致另一次I/O,从而降低性能。那么如何知道这些数据是有效还是无效呢?PostgreSQL通过记录事务的状态来实现。数据行上记录了xmin和xmax,只需了解xmin和xmax对应的事务是成功提交还是回滚了,就可以知道这些数据行是否有效。PostgreSQL把事务状态记录在Commit Log中,简称CLOG,CLOG在数据目录的pg_clog子目录下。

事务的状态有以下4种。

·TRANSACTION_STATUS_IN_PROGRESS=0x00:表示事务正在进行中。

·TRANSACTION_STATUS_COMMITTED=0x01:表示事务已提交。

·TRANSACTION_STATUS_ABORTED=0x02:表示事务已回滚。

·TRANSACTION_STATUS_SUB_COMMITTED=0x03:表示子事务已提交。事务ID,在PostgreSQL中有时缩写为xid,是一个32bit的数字。有以下3个特殊的事务ID是给系统内部使用的,代表特殊的含义。·InvalidTransactionId=0:表示是无效的事务ID。

·BootstrapTransactionId=1:表示系统表初使化时的事务ID。

·FrozenTransactionId=2:冻结的事务ID。

所以数据库系统第一个正常的事务ID是从3开始的,然后连续递增,达到最大值后,再从3开始。事务ID为0、1、2的始终保留。

2.3 PostgreSQL多版本的优劣分析

3.物理存储结构

3.1 PostgreSQL中的术语

Relation: 表示表或索引,也就是其他数据库的Table或Index.具体表示的是Table还是Index需要看具体情况。

Tuple:表示表中的行, 在其他数据库中使用Row表示。

Page: 表示在磁盘中的数据块。

Buffer:表示在内存中的数据块。

3.2 数据块结构

数据块的大小默认是8KB,最大是32KB,一个数据块中存储了多行的数据。块中的结构是先有一个块头,后面记录了块中各个数据行的指针,行指针向后顺序排列的,而实际的数据行内容是从块尾向前反向排列的,行数据指针与行数之间的部分就是空闲时间。

块头记录了如下信息:

  • 块的checksum值。
  • 空闲空闲的起始位置和结束位置。
  • 特殊数据的起始位置。

行指针是一个32bit的数字,具体结构如下:

  • 行内容的偏移量,占用15bit.
  • 指针的标记,占用2bit
  • 行内容的长度,占用15bit

行指针中表示内容的偏移量是15bit,能表示的最大偏移量是215=32768,因此在PostgreSQL中,块的最大大小是32768即32KB.

3.3 Tuple结构

从图中可以看出,行的物理结构是先有一个行头,后面跟了各项数据。行头中记录了以下重要信息。

·oid、ctid、xmin、xmax、cmin、cmax、ctid:这些信息的含义在前面已介绍过。

·natts&infomask2:字段数,其中低11位表示这行有多少个列。其他的位则是HOT(Heap Only Touples)技术及行可见性的标志位。

·infomask:用于标识行当前的状态,比如行是否具有OID,是否有空属性,共有16位,每位都代表不同的含义。

·hoff:表示行头的长度。

·bits:是一个数组,用于标识该行上哪些字段(列)为空。

 3.4 数据块空闲空间管理

         在表中的数据块中插入、更新和删除数据会在表中产生旧版本的数据,这些旧版本数据通过Vacuum进程的清理会在数据块中产生空闲空间。再向表中插入数据时,最好的办法就是继续使用这些旧数据块中的空闲空间,如果所有的新数据都分配新的数据块,会导致数据文件不断膨胀。当插入新行时,如果多个数据块中都有空闲空间,应把数据行插到哪个有空闲空间的数据块中呢?首先,有空闲空间的数据块不一定能容纳下新的数据行,所以要插入一行数据时,首先要快速找到一个数据块,且此数据块中的空闲空间能够放下此数据行。

要完成这一操作,要实现以下两个功能:

·首先是要记录每个数据块空闲空间的大小。

·查找时,不能一个一个地找,要实现快速查找。PostgreSQL数据库使用一个名为“FSM”的文件记录每个数据块的空闲空间。FSM是英文“Free Space Map”的缩写。

PostgreSQL为缩小FSM文件的大小,只使用一个字节来记录一个数据块中的空闲空间,很明显一个字节是无法记录空闲空间实际大小的,该字节值实际上代表空闲空间的一个范围,其方法如表10-2所示。

 

 4.控制文件解密

4.1 控制文件介绍

PostgreSQL的控制文件记录了数据库的重要信息,如数据库的系统标识符“system_identifier”、系统表版本“Catalog version number”、实例状态、Checkpoint信息、数据页的块大小、WAL日志的页大小及文件大小、一些实例备份和恢复信息等。所以PostgreSQL的控制文件与Oracle数据库的控制文件的作用基本相同,都是记录数据库的重要信息,只是在细节上有所不同,PostgreSQL的控制文件没有Oracle数据库中的那么复杂。

4.2 数据库的唯一标识串解密

数据库的唯一标识串“Database system identifier”用于唯一标识一套数据库系统,物理复制的主数据库和备数据库有相同的数据库唯一标识串。数据库的唯一标识串是在Initdb初始化数据库实例时生成的,它是一个64bit的整数。该整数由当前的时间戳和执行Initdb进程的PID的两个部分组成,生成的算法可参见PostgreSQL源码xlog.c中的BootStrapXLOG函数,内容如下:

pg_control version number:

gettimeofday(&tv, NULL);

sysidentifier = ((uint64) tv.tv_sec) << 32;

sysidentifier |= ((uint64) tv.tv_usec) << 12;

sysidentifier |= getpid() & 0xFFF;

从上面的算法中我们可以知道,高44位的时间戳中由于取了时间戳的微秒部分,所以重复的概率极低。低12位是进程PID。

4.3 Checkpoint信息解密

我们先简单介绍一下什么是检查点(Checkpoint),可以想象一个场景:如果WAL重做日志可以无限地增大,如果仅从不丢失数据的角度来看是不需要把缓冲池中的脏数据块写入磁盘的,因为当发生宕机时,完全可以通过WAL重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。但这种想法明显存在以下几个问题:

·WAL重做日志不可以无限增大,因为WAL日志会占用一定的空间。

·重放WAL日志会占用时间,不可能一个数据库宕机后我们花费很长时间来进行恢复,通常需要在有限的时间内完成恢复,如在几分钟之内完成。

·缓冲区不可能无限大,所以不管怎么样,都需要把一定的脏数据刷新到磁盘中,需要考虑必须要先刷新哪些脏数据等问题。

应用检查点技术就是为了解决这些问题当恢复数据库时,不需要把所有的WAL日志全部重新应用,只需要应用某个时间点之后的WAL日志应用就可以了,当然要做到这一点,就需要把这一时间点之前产生的脏数据全部刷新到磁盘中,所以检查点只是一个数据库事件,该事件触发后将会执行一个操作,而此操作可以保证把事件之前的脏数据全部刷新到磁盘中。当然,我们让Checkpoint发生得越频繁,在数据库实例宕机后重放的WAL日志量就越少,当然重做的日志量的多少也取决于发生宕机的时间点,发生宕机的时间点越靠近最后的检查点,重做的日志量也就越少。

4.4 与Standby相关的信息

我们知道备库在不停地应用WAL日志,对于Hot Standby,在应用WAL日志的同时,还会对外提供服务。备库本身也可能因断电或其他故障而宕机,当备库在重新启动时,不能一启动就对外提供只读服务,因为这时的数据可能还不一致,如果这时提供只读服务,用户会读到不一致的数据。这两个参数用于指定当备库异常终止再启动时,只有应用WAL日志超过指定点之后才能对外提供只读服务。而有人可能会问,为什么在主库上不需要这两项内容呢?因为在主库上,只有把当前所有的WAL日志全部应用完成之后才能对外提供服务,而备库是不断地从主库接收日志,然后不断地应用日志,没有把当前WAL日志应用完的说法,所以在备库上需要知道应用多少日志之后就可以对外提供只读服务了。

5.WAL文件解密

5.1WAL文件介绍

WAL文件是“Write Ahead Log”的简称,就是数据库重做日志,与Oracle的Redo Log的功能是一样的。WAL文件在PostgreSQL9.X及以下版本是在pg_xlog目录下的,而在PostgreSQL10.X及以上版本是在pg_wal目录下的。查看WAL文件所在的目录,会看到如下文件列表:

 5.2 WAL文件名的秘密

初学者会看不懂24个字母长度的WAL文件名,为了解释清楚其中的奥秘,我们先解释一下另一个概念LSN,即“Log Sequence Number(日志序列号)”,是一个不断增长的8字节(64bit)长数字,用于记录WAL日志的绝对位置,随着数据库WAL日志的不断增加,LSN也会不断地增长。

而WAL文件名的24个字符由三部分组成,如图10-4所示。

从图10-4中可知,WAL文件名由下面三部分组成。

  • ·时间线:英文为timeline,是以1开始的递增数字,如1,2,3,…。
  • ·LogId:32bit长的一个数字,是以0开始递增的,如0,1,2,3,…。实际为LSN的高32bit。
  • ·LogSeg:32bit长的一个数字,是以0开始递增的,如0,1,2,3,…。LogSeg是LSN的低32bit的值再除以WAL文件大小(通常为16MB)的结果。注意:当LogId为0时,LogSeg是从1开始的。

        WAL日志文件默认大小为16MB,如果想改变其大小,在PostgreSQL10.X及之前的版本中需要重新编译程序,在PostgreSQL11.X版本之后,可以在Initdb初始化数据库实例时指定WAL文件的大小。

        如果WAL文件是默认大小,即16MB时,LogSeg最大为FF,即000000~0000FF,即在文件名中,最后8字节中前6字节总是0。这时因为LSN的低32bit的值再除以WAL文件大小[2**32/(16*1024*1024)=256]最大只能是256,换算成十六进制,即FF。

5.3 WAL文件循环复用原理

        当我们查看WAL日志的目录时,从表面上来看好像是旧文件不断地被删除,新的WAL文件会不断地产生。熟悉Oracle数据库的人会发现这点与Oracle数据库中的Redo Log不一样。Oracle数据库中Redo Log的个数固定,是会被循环覆盖的。这种以循环覆盖的方式写Redo Log的机制在文件系统上相比于append方式有更高的性能,因为对于文件系统来说,如果用append方式,文件不断地增加时,除了添加的内容数据,文件的尺寸大小数据也需要持久化下去,这样相当于每次写产生了两次I/O,一次是内容数据的I/O,一次是记录文件大小的I/O。而对于Oracle数据库是先初始化Redo Log,真正在写Redo Log时是覆盖写,这样每次写,只有一次I/O。

PostgreSQL的循环覆盖写是通过把旧的WAL日志“重命名”来实现的。发生一次Checkpoint之后,此Checkpoint点之前的WAL日志文件都可以删除,而PostgreSQL中一般并不会将其删除,而是“重命名”旧的WAL文件使之成为一个新的WAL文件所以WAL文件目录下文件序号最大的那个WAL文件并不是当前正在写的WAL文件,因为这个WAL文件有可能是前一次Checkpoint时重命名旧文件产生的。我们用一个示例来说明这种情况。

6.CommitLog文件与事务ID技术解密

6.1 CommitLog文件介绍

前面我们讲过,PostgreSQL把事务状态记录在CommitLog中。PostgreSQL 9.X及之前版本中CLOG文件在数据目录的pg_clog子目录下,从PostgreSQL10版本开始,CLOG文件是在pg_xact子目录下

前面也介绍过事务的状态有以下4种

·TRANSACTION_STATUS_IN_PROGRESS=0x00:表示事务正在进行中

·TRANSACTION_STATUS_COMMITTED=0x01:表示事务已提交

·TRANSACTION_STATUS_ABORTED=0x02:表示事务已回滚

·TRANSACTION_STATUS_SUB_COMMITTED=0x03:表示子事务已提交

        实际上,CommitLog文件是一个位图文件,因为事务有上述4种状态,所以需要用两位来表示一个事务的状态。理论上数据库最多记录20亿个事务,所以CommitLog最多占用512MB空间。CommitLog也会被VACUUM清理,而数据库中的参数“autovacuum_freeze_max_age”的默认设置为2亿,这样AutoVacuum会尽力保证数据库的事务数是2亿个,所以通常数据库的CommitLog占用的空间是51MB左右。

·PostgreSQL对CommitLog文件进行了Cache,即在共享内存中有clog buffer,所以多数情况下不需要读取CommitLog文件。

·在每行上有一个标志字段“t_infomask”,如果标志位“HEAP_XMIN_COMMITTED”被设置,就知道xmin代表的事务已提交,则不需要到CommitLog文件中去判断。同样,如果“HEAP_XMAX_COMMITTED”被设置,就知道xmin代表的事务已提交,则不需要到CommitLog文件中去判断。PostgreSQL数据库通过以上优化手段解决了读取行时判断事务状态效率低的问题。

6.2 事务ID技术

前面介绍了,事务ID是一个32bit长的数字,其总是会消耗完的,消耗完之后会重新从头开始分配,但这些旧的事务ID已经被分配过,如果重新分配,需要先把旧的事务ID回收。而原先分配的旧事务是写到每个表中每一行的xmin或xmax字段上的,所以回收旧的事务ID的工作实际上就是清理表中行上的xmin或xmax字段。此工作是由VACUUM动作来完成的,如果行上的xmin是较旧的事务ID,则把其替换成FrozenXID,即替换成2的值。

7.实例恢复与热备份原理解密

7.1 实现恢复的原理

导致数据库实例异常终止的原因有以下几种:

  • ·内存不足时被OOM Killer或用户kill掉。
  • ·操作系统崩溃。
  • ·硬件故障导致机器停机或重启。

只要磁盘上的数据没有丢失,PostgreSQL就能保证数据不会丢失,这里说的不丢数据是指如下情况:

  • ·数据库实例还能再次启动,如果数据库无法启动,很多时候相当于数据全部丢失,PostgreSQL会全力保证这种情况不会发生。
  • ·已提交的数据,数据库重启后还在。
  • ·不会出现数据错乱的情况。

数据库一般是通过重做日志来保证不丢失数据的,每项操作都记录到重做日志中,实例重新启动后,重演(replay)重做日志,这个动作称为“前滚”。前滚完成后,多数数据库还会把未完成的事务取消掉,就像这些事务从来没有执行过一样,这个动作称为“回滚”。在前滚过程中,数据库是不能被用户访问的。每次前滚时,从哪个点开始?Checkpoint点的概念产生了,每次Checkpoint操作之后都保证Checkpoint点之前的数据已持久化到硬盘中了,实例恢复是,只需要从上一次的Checkpoint点开始重演重做日志就可以了,因为这个点之前的数据已经持久化了,不需要再重演该点之前的重做日志了。在PostgreSQL数据库中重做日志叫WAL日志,即“Write Ahead Log”的缩写。

7.2 热备份的原理

我们通过底层执行热备份的过程如下:

·调用函数pg_start_backup()开始热备份,如“select pg_start_backup('osdba201901282217');”。

·使用你熟悉的文件系统备份工具(如tar或者cpio,不是pg_dump或者pg_dumpall)来执行备份。·调用函数pg_stop_backup()结束热备份,如“SELECT * FROM pg_stop_backup();”。

·把热备份开始时的WAL日志文件全部进行备份。

·使用热备份做恢复的过程如下:

  • ·把备份的WAL文件复制到备份文件集中的pg_wal目录中。
  • ·启动数据库即可完成恢复。

在上述过程中,我们首先会有一个疑问:使用熟悉的文件系统备份工具备份正在运行的数据库,备份文件是不一致的,这是因为备份会持续一段时间,先复制的和后复制的文件或即使同一个文件中不同部分的内容,也不是同一个时间点的内容,在备份文件集上启动数据库后,数据明显是不一致的,这怎么能行呢?但实际上我们打开数据库后会发现数据却是一致的,其中的奥秘就是热备份使用备份过程中产生的WAL纠正了不一致的日志,简单来说就是通过重演这些WAL文件把数据文件集恢复到一致的状态,这就是热备份的最基本的原理。

具体执行过程的解释如下:

执行函数pg_start_backup()时,会对数据库做一次Checkpoint,同时会把这次Checkpoint的点记录到一个特殊的文件中,即backup_label文件,该文件中有如下内容:

=========================================================================

START WAL LOCATION: 0/20000028 (file 000000010000000000000020)

CHECKPOINT LOCATION: 0/20000060

BACKUP METHOD: pg_start_backup

BACKUP FROM: master

START TIME: 2019-01-28 23:37:38

CST LABEL: osdba201901282217

START TIMELINE: 1

=========================================================================

        从上面的文件内容中可以看到开始备份时WAL日志的位置。这个backup_label在源数据库的数据目录下。

        然后我们不管是开始tar还是使用scp做文件系统的备份时,backup_label都会被复制到备份中。当我们执行pg_stop_backup()时,backup_label文件会被删除,当然在删除该文件之前已经将其复制到了备份中。当我们在备份文件集中启动数据库时,数据库开始做实例恢复,但因为存在backup_label文件,其不会从备份集中的控制文件中指定的上一次Checkpoint点开始应用WAL日志,而是从backup_label文件中指定的WAL日志点开始恢复数据库,然后不停地应用WAL日志,把数据文件推到一个一致点。这里有一个问题,不停地应用WAL日志,什么时候才能知道到达了备份结束的时候呢?如果没有到备份的结束时间点,打开数据库还是会出现不一致的情况。这个过程是靠pg_stop_backup()来实现的,调用pg_stop_backup()时会在数据库的WAL日志中写入一条“XLOG_BACKUP_END”记录,当应用到这条“XLOG_BACKUP_END”记录时就可以知道数据库的备份结束了,数据到达了一致点,这时候数据库就可以对外提供服务了,且不必再担心数据不一致问题。因为执行pg_start_backup()后,在数据目录中生成了一个固定名字的backup_label文件,所以不能再次执行pg_start_backup(),否则会再次生成一个backup_label文件,这会导致数据混乱。所以这种备份只能启动一个,不能对主库同时启动多个备份,这种备份称为独占型备份(Exclusive Backup)。

相关文章:

  • MHA高可用
  • 记录VSCode C++网络编程 编译失败出现 undefined reference to _imp_socket等等
  • 记一次升级maven的坑(idea 2021.3.2; maven3.5.0升级3.8.5)
  • 微前端——qiankun(乾坤)实例
  • 设置JVM的内存大小
  • 3D感知技术(4)双目立体视觉测距
  • 孙卫琴的《精通JPA与Hibernate》的读书笔记: 用JPQL批量处理数据
  • linux常用小知识点记录
  • 91.(leaflet之家)leaflet态势标绘-进攻方向绘制
  • Spring Boot + Netty + WebSocket 实现消息推送
  • 【毕业设计】Yolov安全帽佩戴检测 危险区域进入检测 - 深度学习 opencv
  • 18. JavaScript 中如何进行隐式类型转换?
  • 【面试题】2022前端面试真题
  • TIA博途V17中ProDiag功能的使用方法示例(二)可编辑的文本框
  • SSM学生健康防疫信息管理毕业设计-附源码041613
  • const let
  • Date型的使用
  • Java编程基础24——递归练习
  • JS 面试题总结
  • spring学习第二天
  • 从setTimeout-setInterval看JS线程
  • 聊聊hikari连接池的leakDetectionThreshold
  • 如何在GitHub上创建个人博客
  • 我有几个粽子,和一个故事
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 正则与JS中的正则
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • ​一些不规范的GTID使用场景
  • ###C语言程序设计-----C语言学习(6)#
  • #define、const、typedef的差别
  • #pragma预处理命令
  • #Z2294. 打印树的直径
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • $.each()与$(selector).each()
  • (1) caustics\
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (力扣)1314.矩阵区域和
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .Net MVC4 上传大文件,并保存表单
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .net开发时的诡异问题,button的onclick事件无效
  • .NET面试题(二)
  • .NET序列化 serializable,反序列化
  • [ai笔记9] openAI Sora技术文档引用文献汇总
  • [BeginCTF]真龙之力
  • [C#]winform部署yolov5-onnx模型
  • [flask]http请求//获取请求体数据
  • [IDF]被改错的密码
  • [jQuery]div滚动条回到最底部