阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

PostgreSQL逻辑备份pg_dump使用及其原理浅析

213次阅读
没有评论

共计 8321 个字符,预计需要花费 21 分钟才能阅读完成。

一、原理分析

1、循环调用 getopt_long 解析命令行参数,将参数保存到 static DumpOptions dopt; 中
2、判断参数是否相容,不相容则退出:
 options -s/–schema-only and -a/–data-only cannot be used together
 options -c/–clean and -a/–data-only cannot be used together
 options –inserts/–column-inserts and -o/–oids cannot be used together
 option –if-exists requires option -c/–clean

3、调用 CreateArchive 打开输出文件,输出流为 fout。该函数使用 4 个文件封装了 4 种不同 dump 文件格式,增加新文件可以增加新的导出文件类型各自封装,独立易于维护。
 CreateArchive->_allocAH:
  switch (AH->format){
   case archCustom:
    InitArchiveFmt_Custom(AH);
    break;
   case archNull:
    InitArchiveFmt_Null(AH);
    break;
   case archDirectory:
    InitArchiveFmt_Directory(AH);
    break;
   case archTar:
    InitArchiveFmt_Tar(AH);
    break;
   default:
    exit_horribly(modulename, “unrecognized file format \”%d\”\n”, fmt);
  }

4、fout 是一个重要的全局变量

5、调用 ConnectDatabase 连接数据库

6、调用 setup_connection,在连接上执行一些 SQL 语句:
 SELECT pg_catalog.set_config(‘search_path’, ”, false);
 set client_encoding to ‘%s’//pg_dump - E 指定
 SET ROLE %s//
 SET DATESTYLE = ISO;
 SET INTERVALSTYLE = POSTGRES;
 SET extra_float_digits TO 3;
 SET synchronize_seqscans TO off;
 SET statement_timeout = 0;
 SET lock_timeout = 0;
 SET idle_in_transaction_session_timeout = 0;
 SET row_security = off;
 BEGIN;
 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY;

7、为兼容低版本,根据服务器版本号决定一些变量取值

8、调用 tblinfo = getSchemaData(fout, &numTables); 决定导出哪些数据库对象。本函数又调用如下函数,值得关注哦。为了存储每个对象的元数据,这些函数会 malloc 申请空间,直到 pg_dump 进程结束才释放。
 extinfo = getExtensions(fout, &numExtensions);
 extinfoindex = buildIndexArray(extinfo, numExtensions, sizeof(ExtensionInfo));
 getExtensionMembership(fout, extinfo, numExtensions);
 nspinfo = getNamespaces(fout, &numNamespaces);
 nspinfoindex = buildIndexArray(nspinfo, numNamespaces, sizeof(NamespaceInfo));
 tblinfo = getTables(fout, &numTables);
 tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo));
 getOwnedSeqs(fout, tblinfo, numTables);
 funinfo = getFuncs(fout, &numFuncs);
 funinfoindex = buildIndexArray(funinfo, numFuncs, sizeof(FuncInfo));
 typinfo = getTypes(fout, &numTypes);
 typinfoindex = buildIndexArray(typinfo, numTypes, sizeof(TypeInfo));
 getProcLangs(fout, &numProcLangs);
 getAggregates(fout, &numAggregates);
 oprinfo = getOperators(fout, &numOperators);
 oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo));
 getAccessMethods(fout, &numAccessMethods);
 getOpclasses(fout, &numOpclasses);
 getOpfamilies(fout, &numOpfamilies);
 getTSParsers(fout, &numTSParsers);
 getTSTemplates(fout, &numTSTemplates);
 getTSDictionaries(fout, &numTSDicts);
 getTSConfigurations(fout, &numTSConfigs);
 getForeignDataWrappers(fout, &numForeignDataWrappers);
 getForeignServers(fout, &numForeignServers);
 getDefaultACLs(fout, &numDefaultACLs);
 collinfo = getCollations(fout, &numCollations);
 collinfoindex = buildIndexArray(collinfo, numCollations, sizeof(CollInfo));
 getConversions(fout, &numConversions);
 getCasts(fout, &numCasts);
 getTransforms(fout, &numTransforms);
 inhinfo = getInherits(fout, &numInherits);
 getEventTriggers(fout, &numEventTriggers);
 processExtensionTables(fout, extinfo, numExtensions);
 flagInhTables(tblinfo, numTables, inhinfo, numInherits);
 getTableAttrs(fout, tblinfo, numTables);
 flagInhAttrs(fout->dopt, tblinfo, numTables);
 getIndexes(fout, tblinfo, numTables);
 getExtendedStatistics(fout);
 getConstraints(fout, tblinfo, numTables);
 getTriggers(fout, tblinfo, numTables);
 getRules(fout, &numRules);
 getPolicies(fout, tblinfo, numTables);
 getPublications(fout);
 getPublicationTables(fout, tblinfo, numTables);
 getSubscriptions(fout);

对于每个 getXXXs 函数都将执行下面流程,以 getTables 为例:

1)根据服务器版本号查询系统表,读出对象的元数据信息

2)malloc 内存空间并将查询结果存放到对象的数据结构中,TableInfo

3)对于每条元数据信息,调用 selectDumpableTable 标记需要导出的表,如果 - t 指定导出表,遍历该列表,得到对应表并标记:DUMP_COMPONENT_ALL;- T 指定删除表,标记 tbinfo->dobj.dump = DUMP_COMPONENT_NONE

4)dumpIdMap[dobj->dumpId] = dobj; 将导出表的元数据存放到 dumpIdMap 数组中

5)在导出表上执行 LOCK TABLE %s IN ACCESS SHARE MODE

6)将所有元数据信息保存后,执行 SET statement_timeout = 0 保证语句不超时,能够一直执行下去

9、调用 getTableData 函数,获取表对应的数据。实际上,并不是表真正数据,而是为表数据建立一个“导出对象”,将来导出时,依据导出对象获取真是的数据再导出。虽然先把导出对象放到 AH->toc 链表上,真正导出时导出数据,不会占用大量内存空间,但是针对这些元数据,当表特别多的时候,由于不到进程退出不释放内存,占用内存还是非常可观的。

该函数调用 makeTableDataInfo:

1)view、外部表、分区表字表(从父表导出)和 unlogged permanent table 不用导出 

2)判断该表是否制定导出时被排除

3)malloc 一个 TableDataInfo,保存表信息
  typedef struct _tableDataInfo
  {
   DumpableObject dobj;
   TableInfo  *tdtable;  /* link to table to dump */
   bool  oids;   /* include OIDs in data? */
   char    *filtercond;  /* WHERE condition to limit rows dumped */
  } TableDataInfo;

        4)tdinfo->dobj.catId.tableoid、tdinfo->dobj.catId.oid、tdinfo->dobj.name、tdinfo->dobj.namespace 信息,并将 dobj 保存到 dumpIdMap 数组

10、如果需要导出大对虾,调用 getBlobs,同上也是保存到数组,并没有真正导出数据

11、调用 getDependencies 重新整理每个对象的依赖关系。

12、getDumpableObjects 从 dumpIdMap 数组中获取 dump 对象

13、sortDumpableObjectsByTypeName、sortDataAndIndexObjectsBySize(如果是并行 dump,需要按表大小排序)、sortDumpableObjects 把所有对象重新排列:不同类型对象导出优先级依赖于 dbObjectTypePriority 数组;相同类型按名称排序
 static const int dbObjectTypePriority[] =
 {
  1, /* DO_NAMESPACE */
  4, /* DO_EXTENSION */
  5, /* DO_TYPE */
  5, /* DO_SHELL_TYPE */
  6, /* DO_FUNC */
  7, /* DO_AGG */
  8, /* DO_OPERATOR */
  8, /* DO_ACCESS_METHOD */
  9, /* DO_OPCLASS */
  9, /* DO_OPFAMILY */
  3, /* DO_COLLATION */
  11, /* DO_CONVERSION */
  18, /* DO_TABLE */
  20, /* DO_ATTRDEF */
  28, /* DO_INDEX */
  29, /* DO_STATSEXT */
  30, /* DO_RULE */
  31, /* DO_TRIGGER */
  27, /* DO_CONSTRAINT */
  32, /* DO_FK_CONSTRAINT */
  2, /* DO_PROCLANG */
  10, /* DO_CAST */
  23, /* DO_TABLE_DATA */
  24, /* DO_SEQUENCE_SET */
  19, /* DO_DUMMY_TYPE */
  12, /* DO_TSPARSER */
  14, /* DO_TSDICT */
  13, /* DO_TSTEMPLATE */
  15, /* DO_TSCONFIG */
  16, /* DO_FDW */
  17, /* DO_FOREIGN_SERVER */
  32, /* DO_DEFAULT_ACL */
  3, /* DO_TRANSFORM */
  21, /* DO_BLOB */
  25, /* DO_BLOB_DATA */
  22, /* DO_PRE_DATA_BOUNDARY */
  26, /* DO_POST_DATA_BOUNDARY */
  33, /* DO_EVENT_TRIGGER */
  38, /* DO_REFRESH_MATVIEW */
  34, /* DO_POLICY */
  35, /* DO_PUBLICATION */
  36, /* DO_PUBLICATION_REL */
  37 /* DO_SUBSCRIPTION */
 };

 14、dumpEncoding、dumpStdStrings、dumpSearchPath 导出编码信息,使用双向链表 TOCEntry 保存导出对象。例如:
 newToc->defn:”SET client_encoding=’UTF8′;\n”
 SET standard_conforming_string=’on’;
 SELECT pg_catalog.set_config(‘search_path’,”,false);\n

15、dumpDatabase 导出本链接对应的目的数据库信息,同样是 newToc,newToc->defn:CREATE DATABASE yzs WITH TEMPLATE=template0 ENCODING=’UTF8′ LC_COLLATE=’zh_CN.UTF-8′ LC_CTYPE=’zh_CN.UTF-8′

16、遍历所有对象,对于每个对象调用 dumpDumpableObject,本函数用一堆诸如 dumpNamespace、dumpExtension 等,将其插入循环链表。
 for (i = 0; i < numObjs; i++)
  dumpDumpableObject(fout, dobjs[i]);

————————– 以上所有导出,不真正导出数据 —————————-

17、遍历链表标记哪些对象 Toc entry 需要导出:ProcessArchiveRestoreOptions

18、如果导出格式时 plain,则调用 RestoreArchive,输出到文件显示的是 SQL 语句,不再是不可识别的二进制文件

19、关闭句柄释放资源 CloseArchive,根据函数指针调用不同文件类型的_CloseArchive

二、不同格式的处理函数

-F, –format=c|d|t|p  output file format (custom, directory, tar,plain text (default))

目前,pg_dump 支持 4 种导出格式:

    custum(pg_backup_custum.c):导出二进制格式的文件。包括文件头和文件体。文件体是一个链表,保存每个备份对象,每个可备份对象都有一套统一的结构表示,支持压缩

    plain(pg_backup_null.c):把 SQL 脚本内容输出到标准输出,默认方式

    file(pg_backup_file.c):导出包括备份一个主文件和一些辅助文件,主文件方式类似于 custom 文件格式,辅助文件是数据文件,每个辅助文件对应备份��象中的一个表,需要和 - f 一起使用

    tar(pg_backup_tar.c):文件备份基本类似“file”方式,但最后备份的所有文件都要归档到一个 tar 文件。文件最大大小为 8GB(受限于 tar file format)

PostgreSQL 通过函数指针来实现这四种导出格式。在 pg_backup_archive.h 文件中有诸如下面的大量函数指针:
 typedef void (*ClosePtrType) (ArchiveHandle *AH);
 typedef void (*ReopenPtrType) (ArchiveHandle *AH);
 typedef void (*ArchiveEntryPtrType) (ArchiveHandle *AH, TocEntry *te);

这些函数指针,在下面文件里分别初始化:
 pg_backup_custum.c->InitArchiveFmt_Custom(ArchiveHandle *AH)
 pg_backup_null.c->InitArchiveFmt_Null(ArchiveHandle *AH)
 pg_backup_file.c->InitArchiveFmt_Directory(ArchiveHandle *AH)
 pg_backup_tar->InitArchiveFmt_Tar(ArchiveHandle *AH)

在数据结构 ArchiveHandle 中使用了大量函数指针,是的在初始化不同导出文件格式的 Archive 结构时,能为处理函数赋值为各自不同的处理函数。这样在 pg_dump.c 中只需要根据用户指定的文件格式的参数,就可以调用相应的处理函数。见第一部分的第 3 步。

概况的说,pg_dump 导出的内容可以分为数据库对象的定义和数据。数据库对象的定义导出时通过查询系统表把对应元数据信息读取出来后,把该对象的各类信息置于一个链表上包括其依赖对象的 oid。而具体的数据,也就是每个数据包的数据也被抽象为一个数据库对象,保存在此链表中。通过调节导出顺序把数据库对象的定义导出然后导出数据,置于通过链表中对应数据对象节点的信息,执行相应的 SQL 语句,从表中读出数据然后导出写出去。所以,在内存中只是链表上对象的定义,数据是边读边写出的,可以使用流式读出。

三、使用方法

1)以目录格式导出,需要和 - f 一起使用。toc.dat 保存所有可导出对象的信息(表定义等),其他文件是数据,以表的 oid 为命名,test 是目录。
[postgres@localhost ~]$ pg_dump –format=d yzs -f test
[postgres@localhost ~]$ cd test
[postgres@localhost test]$ ll
total 8
-rw-rw-r–. 1 postgres postgres  31 Mar 23 06:07 3010.dat.gz
-rw-rw-r–. 1 postgres postgres 2124 Mar 23 06:07 toc.dat

2)导出 SQL 语句到 test.sql 中
[postgres@localhost ~]$ pg_dump –format=p yzs -f test.sql

3)以二进制格式输出
[postgres@localhost ~]$ pg_dump –format=c -f test yzs

4)以 tar 格式输出。与 d 格式不同在于多了一个 restore.sql 文件(plain 格式文件),并将所有文件打包成一个文件
[postgres@localhost ~]$ pg_dump –format=t -f test yzs
[postgres@localhost ~]$ tar -xvf test
toc.dat
3010.dat
restore.sql

5)仅导出数据库结构(不指定库,默认是 postgres)
pg_dump -s yzs -f 1.sql

6)导出时导出 drop database 和 create database 语句。需注意,导入时如有用户连接这该库,则 drop 语句执行失败
pg_dump -s yzs -C -c -f 1.txt

7、- t 指定导出某些表,只导出 item 开头的表等对象
pg_dump -t temp* -f 1.txt yzs

8、- n 只导出指定的 schema,可以多个 -n;- N 指定不导出的 schema

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-22发表,共计8321字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中