PgSQL · 源码分析· pg_dump分析
PostgreSQL本身提供了逻辑导出工具pg_dumpall和pg_dump,其中pg_dumpall导出所有的数据库,pg_dump导出单个数据库,两个工具的用法和参数不再详细介绍,本文从代码层面上对此过程进行分析。
概括地说,逻辑导出要干的事情就是连接对应数据库,读出各个数据库对象的定义和数据,此外还包括comment、服务器配置和权限控制等等,这些数据库对象定义的SQL语句会被写入到对应的dump文件中。其中可以设置只导出模式或者只导出数据,默认是导出模式和数据,这样就可以支持分步导出和恢复。而数据表数据可以选择COPY方式或者INSERT语句的方式写入备份文件中。
这个过程主要涉及几个文件,包括pg_dumpall.c,pg_dump.c,pg_backup_db.c。其中pg_dumpall.c导出所有的数据库,pg_dump.c导出单个数据库,会被pg_dumpall.c不断调用,从而导出所有的数据库,这里重点分析下pg_dump.c的工作。
pg_dump过程分析
pg_dump.c文件的main函数,主要完成如下工作:
(1) 解析各类参数,包括对应变量赋值和参数间是否相互兼容,如果不兼容,报错退出。
(2) 调用CreateArchive函数,打开输出文件,输出流为g_fout,g_fout是Archive类型,这里比较巧妙的地方就是根据不同的文件格式,会产生不同的g_fout,对应也就使用不同的.c文件独立封装了不同导出的文件格式下的处理函数,这样可以很容易地增加新的导出文件格式,提高了可维护性和扩展性,具体的实现方法我们会在下面进行分析。目前支持四种导出文件格式和分别是:
custom
pg_backup_custom.c
导出对象存储到二进制格式的文件中
file
pg_backup_files.c
导出对象存储到指定的文件中
plain
pg_backup_null.c
导出文件到标准输出
tar
pg_backup_tar.c
以压缩文件的格式导出文件
(3)调用ConnectDatabase函数,连接目的数据库,并在这个数据库上执行一些SQL语句,如设定C/S之间的编码、设定数据库对于日期类型的使用格式、针对不同版本的服务器设置一些与版本相关的信息。
(4) 在(3)中的数据库连接上开启一个事务,保证导出的所有数据的一致性,同时为了保证能够导出浮点数,设置正确的浮点数输出格式:
do_sql_command(g_conn, "BEGIN");
do_sql_command(g_conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
do_sql_command(g_conn, "SET extra_float_digits TO 2");
(5) 为了保持pg_dump工具向低版本兼容,根据服务器的版本号决定一些变量的取值。
(6) 查询并存储需要导出的模式和表的信息。
(7) 调用getSchemaData函数,决定导出哪些数据库对象,并调用了如下函数保存具体的数据库对象:
proclanginfo = getProcLangs(&numProcLangs);
agginfo = getAggregates(&numAggregates);
oprinfo = getOperators(&numOperators);
oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo));
opcinfo = getOpclasses(&numOpclasses);
prsinfo = getTSParsers(&numTSParsers);
tmplinfo = getTSTemplates(&numTSTemplates);
dictinfo = getTSDictionaries(&numTSDicts);
cfginfo = getTSConfigurations(&numTSConfigs);
fdwinfo = getForeignDataWrappers(&numForeignDataWrappers);
srvinfo = getForeignServers(&numForeignServers);
daclinfo = getDefaultACLs(&numDefaultACLs);
opfinfo = getOpfamilies(&numOpfamilies);
convinfo = getConversions(&numConversions);
tblinfo = getTables(&numTables);
tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo));
inhinfo = getInherits(&numInherits);
ruleinfo = getRules(&numRules);
castinfo = getCasts(&numCasts);
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
getTableAttrs(tblinfo, numTables);
flagInhAttrs(tblinfo, numTables);
getIndexes(tblinfo, numTables);
getConstraints(tblinfo, numTables);
getTriggers(tblinfo, numTables);
值得注意的是,在此不完全决定了对象的导出次序,原则是被依赖的对象先导出。在这些函数中,注意类似如下的调用序列:
tblinfo = getTables(numTables);
tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo));
这表明先导出表,再导出依附于表的索引信息。
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
上句表明要父表先于子表导出。
(8) 每一个getXXXs函数,都将执行如下过程:
- 根据服务器版本号,查询系统表,读出对象的元数据信息。
- 循环遍历每个数据库对象元数据信息,对每个数据库对象通过pg_depend系统表计算其依赖对象,并记录所有对象的元数据信息和它依赖对象的元数据信息。
(9) 调用getTableData函数,“导出”表中的数据。注意,这里导出加引号并不是真正的导出数据,而是用一个链表去存储每一个需要导出的数据库对象的基本信息,到真正需要导出的时候再遍历这个链表依次做出对应的处理。这里使用了占位的思想,不会占用大量的内存空间。
(10) 如果需要导出大对象,调用getBlobs,同上也是建立一个链表,并没有真正去做导出。
(11) 根据步骤(8)得到每个对象的依赖关系,调用getDependencies函数,重新整理对象间的依赖关系,调用sortDumpableObjects来决定各个数据库对象导出的顺序(不同类型的对象的导出优先级取决于newObjectTypePriority数组;相同类型的对象,按名称排序)。
(12) 存储编码等信息以及本连接对应的目的数据库的信息。
(13) 遍历所有对象,逐个“导出”对象(调用了dumpDumpableObject函数,本函数调用一堆诸如dumpNamespace、dumpTable等对象)。如果是“导出”表,则根据“导出”对象的信息,查询系统表,查阅到每个表对应的列信息,生成表对象对应的SQL语句,输出SQL语句到g_fou;如果是“导出”表数据,则调用dumpTableData,有两种方式选择,一是生成Insert语句,默认的是生成PostgreSQL自身的copy语句。这里不再具体去介绍。
(14) 在“导出”每一个对象时,通常都会调用ArchiveEntry,做真正的SQL语句生成工作。另外,还会调用dumpComment、dumpSecLabel、dumpACL等函数,“导出”本对象对应的一些诸如注释、权限等相关信息。
(15) 调用RestoreArchive函数,真正的导出数据,注意这里是根据不同的导出文件格式来选择不同的RestoreArchive函数。
(16) 关闭句柄释放资源等。
接下来,我们简单分析下目前支持的四种导出格式以及如何实现不同导出格式对应不同处理函数。目前PostgreSQL提供四种导出文件格式,具体如下:
custom
pg_backup_custom.c
导出到二进制格式的备份文件,包括文件头和文件体。文件体是一个链表,保存每个备份对象,每一个可备份对象都有一套统一的结构标识,支持压缩(压缩功能依赖于系统编译选项和pg_config.h文件中的宏定义开关)。
plain
pg_backup_null.c
把SQL脚本内容输出到标准输出,默认方式。
file
pg_backup_files.c
导出包括备份一个主文件和一些辅助文件;主文件方式类似于custom的文件格式,辅助文件是数据文件,每一个辅助文件对应备份对象中的一个表。
tar
pg_backup_tar.c
文件备份基本类似“file”方式,但是,最后备份的所有文件都要归档到一个tar文件中。文件最大大小为8GB(受限于tar file format)。
PostgreSQL通过函数指针来实现这四种导出文件格式对应不同的处理函数。在pg_backup_archiver.h文件中,定义有大量的函数指针,如:
typedef void (*ClosePtr) (struct _archiveHandle * AH);
typedef void (*ReopenPtr) (struct _archiveHandle * AH);
typedef void (*ArchiveEntryPtr) (struct _archiveHandle * AH, struct _tocEntry * te);
这些函数指针,被用到了如下文件中(文件->被调用的函数):
pg_backup_custom.c->InitArchiveFmt_Custom(ArchiveHandle *AH)
pg_backup_null.c->InitArchiveFmt_Null(ArchiveHandle *AH)
pg_backup_files.c->InitArchiveFmt_Files(ArchiveHandle *AH)
pg_backup_tar.c->InitArchiveFmt_Tar(ArchiveHandle *AH)
在数据结构ArchiveHandle中,使用了大量的函数指针,使得在初始化不同导出文件格式的Archive结构时能够为处理函数赋值为各自不同的处理函数。 这样在pg_dump.c中,只要根据用户指定的文件格式的参数,就可以调用相应的处理函数,代码如下:
/* open the output file */
if (pg_strcasecmp(format, "a") == 0 || pg_strcasecmp(format, "append") == 0)
{
/* This is used by pg_dumpall, and is not documented */
plainText = 1;
g_fout = CreateArchive(filename, archNull, 0, archModeAppend);
}
else if (pg_strcasecmp(format, "c") == 0 || pg_strcasecmp(format, "custom") == 0)
g_fout = CreateArchive(filename, archCustom, compressLevel, archModeWrite);
else if (pg_strcasecmp(format, "f") == 0 || pg_strcasecmp(format, "file") == 0)
{
/*
* Dump files into the current directory; for demonstration only, not
* documented.
*/
g_fout = CreateArchive(filename, archFiles, compressLevel, archModeWrite);
}
else if (pg_strcasecmp(format, "p") == 0 || pg_strcasecmp(format, "plain") == 0)
{
plainText = 1;
g_fout = CreateArchive(filename, archNull, 0, archModeWrite);
}
else if (pg_strcasecmp(format, "t") == 0 || pg_strcasecmp(format, "tar") == 0)
g_fout = CreateArchive(filename, archTar, compressLevel, archModeWrite);
else
{
write_msg(NULL, "invalid output format \"%s\" specified\n", format);
exit(1);
}
概括得说,pg_dump导出的内容可以分为数据库对象的定义和对象数据。数据库对象的定义导出,是通过查询系统表把对应的元信息读取出来后,把该对象的各类信息置于一个链表上,包括其依赖的对象oid。而具体的数据,也就是每个数据表的数据,也被抽象为了一个数据库对象(这种对象我们可以称为数据对象),保存在此链表中(链表上的所有对象都有自己的类型,TocEntry结构上有个成员“teSection section”,是标识本节点的类型)。通过调节导出顺序,会先把数据库对象的定义导出,然后导出其数据对象,只要通过链表中对应数据对象节点的信息,执行相应的SQL语句,从表中读出数据,然后把数据写出去。所以,在内存中只是链表上的对象的定义,数据是在边读边写出的,完全可以实现流式导出,如下:
导出数据,通过管道和psql工具,导入到目的库
pg_dump -h host1 dbname | psql -h host2 dbname
导出数据到标准输出
pg_dump dbname > outfile
导出大于操作系统所支持的最大文件的大数据量
pg_dump dbname | gzip > filename.gz
恢复大数据量的数据到目的库
gunzip -c filename.gz | psql dbname
or:
cat filename.gz | gunzip | psql dbname
当然,除了上面的分析外,还有很多其它详细的内容需要具体分析,比如不同版本的数据库操作方式、版本兼容性的问题、对象权限如何处理、约束关系如何处理等。 这些问题都是值得下一步具体分析的。
PgSQL · 源码分析· pg_dump分析的更多相关文章
- 从源码的角度分析ViewGruop的事件分发
从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...
- 安卓图表引擎AChartEngine(二) - 示例源码概述和分析
首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...
- 第九节:从源码的角度分析MVC中的一些特性及其用法
一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...
- 通过官方API结合源码,如何分析程序流程
通过官方API结合源码,如何分析程序流程通过官方API找到我们关注的API的某个方法,然后把整个流程执行起来,然后在idea中,把我们关注的方法打上断点,然后通过Step Out,从内向外一层一层分析 ...
- HTTP请求库——axios源码阅读与分析
概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...
- 如何实现一个HTTP请求库——axios源码阅读与分析 JavaScript
概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...
- qt creator源码全方面分析(3-3)
目录 qtcreatordata.pri 定义stripStaticBase替换函数 设置自定义编译和安装 QMAKE_EXTRA_COMPILERS Adding Compilers 示例1 示例2 ...
- qt creator源码全方面分析(3-5)
目录 qtcreatorlibrary.pri 使用实例 上半部 下半部 结果 qtcreatorlibrary.pri 上一章节,我们介绍了src.pro,这里乘此机会,把src目录下的所有项目文件 ...
- jQuery源码 Ajax模块分析
写在前面: 先讲讲ajax中的相关函数,然后结合函数功能来具体分析源代码. 相关函数: >>ajax全局事件处理程序 .ajaxStart(handler) 注册一个ajaxStart事件 ...
随机推荐
- CircleImageManager——圆形 / 圆角图片的工具类
这个类可以实现圆角,或者是圆形图片的操作. CircleImageManager.java package com.kale.utils; import android.content.Context ...
- 100base-T
100Base-T是一种以100Mbps速率工作的局域网(LAN)标准,它通常被称为快速以太网标准,并使用两对UTP(非屏蔽双绞线)铜质电缆. 快速以太网 : 与10BASE-T的区别在于网络速率是1 ...
- UEFI与 Legacy BIOS两种启动模式详解
(1). UEFI启动模式 与 legacy启动模式 legacy启动模式: 就是这么多年来PC一直在使用的启动方式(从MBR中加载启动程序),UEFI BIOS作为一种新的BIOS自然也应该兼容这种 ...
- [转]用 jQuery 实现页面滚动(Scroll)效果的完美方法
转自: http://zww.me/archives/25144 很多博主都写过/转载过用 jQuery 实现页面滚动(Scroll)效果的方法,但目前搜来的方法大都在 Opera 下有个小 Bug: ...
- 节约内存,请使用标签页管理工具:onetab、better onetab
OneTab可以管理chrome和firefox的标签页,把暂时不用的标签页收藏起来,形成一个列表,当然,可以对列表进行分类管理,以方便后续打开查看. 这样就不用打开很多tab,占用大量内存. 由于O ...
- HTML中的转义字符 (转)
HTML中<, >,&等有特殊含义,(前两个字符用于链接签,&用于转义),不能直接使用.使用这三个字符时,应使用它们的转义序列,如下所示: & 或 & &a ...
- CSS- 横向和纵向时间轴
时间轴在展示公司发展信息,服务流程中用的比较多,常见的注册登录有的是通过引导,一步一步的来完成,上面会通过时间轴告诉用户当前在哪一步,公司在关于我们或者发展流程的时候也特别喜欢用时间轴来展示,简单的实 ...
- Vue上传图片预览组件
父组件: <template> <div> <h4>基于Vue.2X的html5上传图片组件</h4> <div style="widt ...
- 转:Logistic regression (逻辑回归) 概述
Logistic regression (逻辑回归)是当前业界比较常用的机器学习方法,用于估计某种事物的可能性.比如某用户购买某商品的可能性,某病人患有某种疾病的可能性,以及某广告被用户点击的可能性等 ...
- Eclipse Maven项目报错3之找不到配置文件spring-servlet-context.xml
一.具体错误如下图所示 根据文字提示可以看出是这个文件找不到,但是我去项目的这个目录找了,这个文件确实存在,那么是什么问题呢 二.解决问题 原因分析(来自网上) 代码编译的过程,是一个自动生成相应编译 ...