导入导出工具的基本原理:
1 导出工具连接目标数据库,读出要导出的对象的定义和数据(读出用户自定义的对象,系统的对象不导出),以及其他一些信息(如comment、权限)等,对于对象的定义,生成SQL语句;对于对象的数据,生成Insert语句(PostgreSQL对于数据的导出,还提供一种基于自己特有功能的copy方式);把这些生成的SQL语句,置于备份文件中。
2 导入工具读取备份文件,即读出SQL语句,依次发到目的数据库中去执行SQL,执行完毕,导入成功;执行失败,导入工具提示失败信息。
(二)体系结构
1 导出程序的流程
pg_dump.c文件的main函数,主要完成如下工作:
1.1 解析命令行参数
1.2 判断参数间是否相容(不相容则报错退出,如参数指定了“只导出数据+只导出模式对象”,则逻辑不相容,报错退出)
1.3 用CreateArchive函数,打开输出文件,输出流为g_fout(即根据参数决定输出流的格式,用到了“第四类:导出文件格式类”的其一代码。本段评注:PostgreSQL导出工具精妙之一,用四个文件封装了四种不同的文件格式,增加新文件,可以增加新的导出文件类型;各自封装,独立易维护,有点OO的味道--多态)
1.4 g_fout,一个重要的全局变量,等到讲述“2 导出程序的数据结构”时细述
1.5 使用ConnectDatabase函数,连接目的数据库
1.6 在数据库连接上,执行一些SQL,如设定C/S之间的编码、设定数据库对于日期类型的使用格式、针对不同版本的服务器设置一些与版本相关的信息
1.7 开启一个事务(重要设置,保证读出的数据一致性)
do_sql_command(g_conn, "BEGIN");
do_sql_command(g_conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
1.8 再次根据服务器的版本号决定一些变量的取值(因为有版本号的识别,保持了pg_dump工具向低版本兼容)
1.9 对指定模式、指定表导出做计算,收集需要导出、不需要导出的模式、表的信息
1.10 调用getSchemaData函数,决定导出哪些数据库对象(PostgreSQL的数据库对象是隶属于“模式schema”的),本函数又调用了如下函数,从上到下的调用次序,值得关注,在此,不完全决定了对象的导出次序,原则是,被依赖的对象,先导出。
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);
在这些函数中:
1.10.1 注意类似如下的调用序列
tblinfo = getTables(&numTables);
tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo));
这表明先导出表,再导出依附于表的索引信息。
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
本句是要计算表之间的继承关系,父表先于子表导出(PostgreSQL提供了表继承的关系) 。
1.10.2 每一个个getXXXs函数,都将执行如下过程:
1.10.2.1 根据服务器版本号,查询系统表,读出对象的元信息
1.10.2.2 循环遍历,每一个元组,对应一个对象的元信息,拿到某个对象的元信息
1.10.2.3 计算本对象的依赖对象(pg_depend系统表,记录了对象的依赖关系,创建对象期间,置入信息到pg_depend系统表),一并把信息保存在本对象对应的结构体数组中(不同对象有不同的数据结构对应)
1.10.2.4 此处重要的数据结构是DumpableObject
1.11 调用getTableData函数,获得表对应的数据。实际上,并不是真正获得表的数据,而是为某表的数据建立一个“导出对象”,将来真正导出时,要依据本对象的信息,获得真实的数据再导出(此处的设计,也是相当精彩,用“占位”的思想,把要导出的数据也看成一个可“导出对象”,先把要导出的对象串到一个链表上,然后在真正导出处、导出数据,这样不会占用大量内存空间)
1.12 同理上条对于表数据的处理方式,如果需要导出大对象,调用getBlobs
1.13 getDependencies函数,重要函数。上述的步骤收集了对象的依赖关系,此处要用这些依赖关系,重新整理对象间的依赖关系
1.14 getDumpableObjects和sortDumpableObjectsByTypeName(sortDumpableObjectsByTypeOid,早期的版本)、sortDumpableObjects->findDependencyLoops(本函数在pg_dump_sort.c中)函数,重要函数。把所有对象重新排序(不同类型的对象的导出优先级取决于newObjectTypePriority数组;相同类型的对象,按名称排序)
1.15 “导出”编码等信息
1.16 “导出”本连接对应的目的数据库的信息
1.17 遍历所有对象,逐个“导出”对象(调用了dumpDumpableObject函数,本函数调用一堆诸如dumpNamespace、dumpTable等对象)
1.18 如果是“导出”表,则根据“导出”对象的信息,查询系统表,查阅到每个表对应的列信息,生成表对象对应的SQL语句,输出SQL语句到g_fout
1.19 如果是“导出”表数据,则调用dumpTableData,有两种方式选择,一是生成Insert语句,默认的是生成PostgreSQL自身的copy语句(快速导出、导入数据的语句)
1.20 在“导出”每一个对象时,通常都会调用ArchiveEntry,做真正的SQL语句生成工作。另外,还会调用dumpComment、dumpSecLabel、dumpACL等函数,“导出”本对象对应的一些诸如注释、权限等相关信息
问题:如果“导出”信息在此完成,如下步骤,调用RestoreArchive函数,把导出的信息输出到文本文件,则有可能输出两种格式的文件?是这样的吗?
1.21 如果指定“导出”格式是“plain”(普通文本格式),则调用RestoreArchive函数,则输出到文件中的是显示的SQL语句,不再是不可识别的二进制文件
1.22 关闭句柄释放资源等
2 导出程序的数据结构
2.1 ArchiveHandle:全局信息,对应备份文件整体,如数据库名称、输出文件格式等。是导出对象的链表头
2.2 TocEntry:导出对象的信息,导出对象链表上的节点。与第一个对象组成一个导出对象链表。所有导出对象按依赖关系排序后,都置于这个链表中
2.3 g_fout:输出流。根据输出格式参数,决定输出流。主要是调用“第四类:导出文件格式类”中的各个文件中的输出函数,确定如何输出不同对象
2.4 newObjectTypePriority:决定了不同对象类型的导出次序,如模式一定先于表导出,表先于数据导出
2.5 对于导出对象的次序,PostgreSQL说:Objects are sorted by type, and within a type by name
3 疑点
仔细看代码,最后的代码如下:
/* Now the rearrangeable objects. */
for (i = 0; i < numObjs; i++)
dumpDumpableObject(g_fout, dobjs[i]);
/* 问题:如果“导出”信息在此完成,如下步骤,调用RestoreArchive函数,把导出的信息输出到文本文件,则有可能输出两种格式的文件?是这样的吗? */
/*
* And finally we can do the actual output.
*/
if (plainText)
{
ropt = NewRestoreOptions();
ropt->filename = (char *) filename;
ropt->dropSchema = outputClean;
ropt->aclsSkip = aclsSkip;
ropt->superuser = outputSuperuser;
ropt->createDB = outputCreateDB;
ropt->noOwner = outputNoOwner;
ropt->noTablespace = outputNoTablespaces;
ropt->disable_triggers = disable_triggers;
ropt->use_setsessauth = use_setsessauth;
ropt->dataOnly = dataOnly;
if (compressLevel == -1)
ropt->compression = 0;
else
ropt->compression = compressLevel;
ropt->suppressDumpWarnings = true; /* We've already shown them */
RestoreArchive(g_fout, ropt);
}
CloseArchive(g_fout); /* 仔细看这个函数 */
PQfinish(g_conn);
exit(0);
如果导出文件格式是二进制,请查看pg_backup_custom.c,如下:
_CloseArchive(ArchiveHandle *AH)
{
lclContext *ctx = (lclContext *) AH->formatData;
pgoff_t tpos;
if (AH->mode == archModeWrite)
{
WriteHead(AH); //真正地写出文件头
tpos = ftello(AH->FH);
WriteToc(AH); //真正地写出要导出的对象
ctx->dataStart = _getFilePos(AH, ctx);
WriteDataChunks(AH); //真正地写出数据部分
/*
* If possible, re-write the TOC in order to update the data offset
* information. This is not essential, as pg_restore can cope in most
* cases without it; but it can make pg_restore significantly faster
* in some situations (especially parallel restore).
*/
if (ctx->hasSeek &&
fseeko(AH->FH, tpos, SEEK_SET) == 0)
WriteToc(AH); //为什么又写一遍?
}
if (fclose(AH->FH) != 0)
die_horribly(AH, modulename, "could not close archive file: %s\n", strerror(errno));
AH->FH = NULL;
}
所以,dumpDumpableObject函数并非真正的导出(写出对象),而是构造一个导出的对象链表,所以,上述1.15小节到1.20小节,“导出”这两个字,都用了引号。
1 导出工具连接目标数据库,读出要导出的对象的定义和数据(读出用户自定义的对象,系统的对象不导出),以及其他一些信息(如comment、权限)等,对于对象的定义,生成SQL语句;对于对象的数据,生成Insert语句(PostgreSQL对于数据的导出,还提供一种基于自己特有功能的copy方式);把这些生成的SQL语句,置于备份文件中。
2 导入工具读取备份文件,即读出SQL语句,依次发到目的数据库中去执行SQL,执行完毕,导入成功;执行失败,导入工具提示失败信息。
(二)体系结构
1 导出程序的流程
pg_dump.c文件的main函数,主要完成如下工作:
1.1 解析命令行参数
1.2 判断参数间是否相容(不相容则报错退出,如参数指定了“只导出数据+只导出模式对象”,则逻辑不相容,报错退出)
1.3 用CreateArchive函数,打开输出文件,输出流为g_fout(即根据参数决定输出流的格式,用到了“第四类:导出文件格式类”的其一代码。本段评注:PostgreSQL导出工具精妙之一,用四个文件封装了四种不同的文件格式,增加新文件,可以增加新的导出文件类型;各自封装,独立易维护,有点OO的味道--多态)
1.4 g_fout,一个重要的全局变量,等到讲述“2 导出程序的数据结构”时细述
1.5 使用ConnectDatabase函数,连接目的数据库
1.6 在数据库连接上,执行一些SQL,如设定C/S之间的编码、设定数据库对于日期类型的使用格式、针对不同版本的服务器设置一些与版本相关的信息
1.7 开启一个事务(重要设置,保证读出的数据一致性)
do_sql_command(g_conn, "BEGIN");
do_sql_command(g_conn, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
1.8 再次根据服务器的版本号决定一些变量的取值(因为有版本号的识别,保持了pg_dump工具向低版本兼容)
1.9 对指定模式、指定表导出做计算,收集需要导出、不需要导出的模式、表的信息
1.10 调用getSchemaData函数,决定导出哪些数据库对象(PostgreSQL的数据库对象是隶属于“模式schema”的),本函数又调用了如下函数,从上到下的调用次序,值得关注,在此,不完全决定了对象的导出次序,原则是,被依赖的对象,先导出。
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);
在这些函数中:
1.10.1 注意类似如下的调用序列
tblinfo = getTables(&numTables);
tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo));
这表明先导出表,再导出依附于表的索引信息。
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
本句是要计算表之间的继承关系,父表先于子表导出(PostgreSQL提供了表继承的关系) 。
1.10.2 每一个个getXXXs函数,都将执行如下过程:
1.10.2.1 根据服务器版本号,查询系统表,读出对象的元信息
1.10.2.2 循环遍历,每一个元组,对应一个对象的元信息,拿到某个对象的元信息
1.10.2.3 计算本对象的依赖对象(pg_depend系统表,记录了对象的依赖关系,创建对象期间,置入信息到pg_depend系统表),一并把信息保存在本对象对应的结构体数组中(不同对象有不同的数据结构对应)
1.10.2.4 此处重要的数据结构是DumpableObject
1.11 调用getTableData函数,获得表对应的数据。实际上,并不是真正获得表的数据,而是为某表的数据建立一个“导出对象”,将来真正导出时,要依据本对象的信息,获得真实的数据再导出(此处的设计,也是相当精彩,用“占位”的思想,把要导出的数据也看成一个可“导出对象”,先把要导出的对象串到一个链表上,然后在真正导出处、导出数据,这样不会占用大量内存空间)
1.12 同理上条对于表数据的处理方式,如果需要导出大对象,调用getBlobs
1.13 getDependencies函数,重要函数。上述的步骤收集了对象的依赖关系,此处要用这些依赖关系,重新整理对象间的依赖关系
1.14 getDumpableObjects和sortDumpableObjectsByTypeName(sortDumpableObjectsByTypeOid,早期的版本)、sortDumpableObjects->findDependencyLoops(本函数在pg_dump_sort.c中)函数,重要函数。把所有对象重新排序(不同类型的对象的导出优先级取决于newObjectTypePriority数组;相同类型的对象,按名称排序)
1.15 “导出”编码等信息
1.16 “导出”本连接对应的目的数据库的信息
1.17 遍历所有对象,逐个“导出”对象(调用了dumpDumpableObject函数,本函数调用一堆诸如dumpNamespace、dumpTable等对象)
1.18 如果是“导出”表,则根据“导出”对象的信息,查询系统表,查阅到每个表对应的列信息,生成表对象对应的SQL语句,输出SQL语句到g_fout
1.19 如果是“导出”表数据,则调用dumpTableData,有两种方式选择,一是生成Insert语句,默认的是生成PostgreSQL自身的copy语句(快速导出、导入数据的语句)
1.20 在“导出”每一个对象时,通常都会调用ArchiveEntry,做真正的SQL语句生成工作。另外,还会调用dumpComment、dumpSecLabel、dumpACL等函数,“导出”本对象对应的一些诸如注释、权限等相关信息
问题:如果“导出”信息在此完成,如下步骤,调用RestoreArchive函数,把导出的信息输出到文本文件,则有可能输出两种格式的文件?是这样的吗?
1.21 如果指定“导出”格式是“plain”(普通文本格式),则调用RestoreArchive函数,则输出到文件中的是显示的SQL语句,不再是不可识别的二进制文件
1.22 关闭句柄释放资源等
2 导出程序的数据结构
2.1 ArchiveHandle:全局信息,对应备份文件整体,如数据库名称、输出文件格式等。是导出对象的链表头
2.2 TocEntry:导出对象的信息,导出对象链表上的节点。与第一个对象组成一个导出对象链表。所有导出对象按依赖关系排序后,都置于这个链表中
2.3 g_fout:输出流。根据输出格式参数,决定输出流。主要是调用“第四类:导出文件格式类”中的各个文件中的输出函数,确定如何输出不同对象
2.4 newObjectTypePriority:决定了不同对象类型的导出次序,如模式一定先于表导出,表先于数据导出
2.5 对于导出对象的次序,PostgreSQL说:Objects are sorted by type, and within a type by name
3 疑点
仔细看代码,最后的代码如下:
/* Now the rearrangeable objects. */
for (i = 0; i < numObjs; i++)
dumpDumpableObject(g_fout, dobjs[i]);
/* 问题:如果“导出”信息在此完成,如下步骤,调用RestoreArchive函数,把导出的信息输出到文本文件,则有可能输出两种格式的文件?是这样的吗? */
/*
* And finally we can do the actual output.
*/
if (plainText)
{
ropt = NewRestoreOptions();
ropt->filename = (char *) filename;
ropt->dropSchema = outputClean;
ropt->aclsSkip = aclsSkip;
ropt->superuser = outputSuperuser;
ropt->createDB = outputCreateDB;
ropt->noOwner = outputNoOwner;
ropt->noTablespace = outputNoTablespaces;
ropt->disable_triggers = disable_triggers;
ropt->use_setsessauth = use_setsessauth;
ropt->dataOnly = dataOnly;
if (compressLevel == -1)
ropt->compression = 0;
else
ropt->compression = compressLevel;
ropt->suppressDumpWarnings = true; /* We've already shown them */
RestoreArchive(g_fout, ropt);
}
CloseArchive(g_fout); /* 仔细看这个函数 */
PQfinish(g_conn);
exit(0);
如果导出文件格式是二进制,请查看pg_backup_custom.c,如下:
_CloseArchive(ArchiveHandle *AH)
{
lclContext *ctx = (lclContext *) AH->formatData;
pgoff_t tpos;
if (AH->mode == archModeWrite)
{
WriteHead(AH); //真正地写出文件头
tpos = ftello(AH->FH);
WriteToc(AH); //真正地写出要导出的对象
ctx->dataStart = _getFilePos(AH, ctx);
WriteDataChunks(AH); //真正地写出数据部分
/*
* If possible, re-write the TOC in order to update the data offset
* information. This is not essential, as pg_restore can cope in most
* cases without it; but it can make pg_restore significantly faster
* in some situations (especially parallel restore).
*/
if (ctx->hasSeek &&
fseeko(AH->FH, tpos, SEEK_SET) == 0)
WriteToc(AH); //为什么又写一遍?
}
if (fclose(AH->FH) != 0)
die_horribly(AH, modulename, "could not close archive file: %s\n", strerror(errno));
AH->FH = NULL;
}
所以,dumpDumpableObject函数并非真正的导出(写出对象),而是构造一个导出的对象链表,所以,上述1.15小节到1.20小节,“导出”这两个字,都用了引号。