PostgreSQL之精妙的数据库导入导出工具架构 (二)

导入导出工具的基本原理:
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小节,“导出”这两个字,都用了引号。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值