(十一)详解逻辑备份---数据的导出

1 概述

    对象的定义导出,是要把对象的元信息读出后,把与对象相关的各种信息置于一个链表上,链表的每个节点是一个对象。每个表的数据,都被抽象为一个对象(链表上的所有对象都有自己的类型,TocEntry结构上有个成员“teSection    section”,是标识本节点的类型),这个对象在链表上占据一个节点的位置,当对象定义导出后,数据将被导出时,即依据数据对象节点上的信息,从表中读出数据,然后把数据写出去(所以,内存中,只是链表上的对象的定义,数据是在边读边写出的)。

2 dumpTableData函数细节
2.1 dumpTableData函数的入口参数为:(Archive *fout, TableDataInfo *tdinfo),第一个参数,表示输出流信息,其实也对应的是链表的表头信息(如结构上有个tocCount成员,用于不断统计链表的节点个数);第二个参数,是表的数据对象对应的结构信息,本信息是导出链表上的表的数据对应的节点的元信息。
2.2 如果导出参数没有特别指出是以insert语句方式导出,则使用copy方式导出数据(copy方式,PostgreSQL提供的一种快速导出导入数据的一种方式,有对应的SQL语句存在)。这两个函数分别是:dumpTableData_copy、dumpTableData_insert。
    但请注意,这里需要留意“DataDumperPtr dumpFn”函数的指针使用方式。
2.3 调用ArchiveEntry函数,执行数据的导出。
    ArchiveEntry函数的倒数第二个参数“DataDumperPtr dumpFn”,通常在非数据对象导出的时候,其入口值都是“NULL”,只有导出的对象是数据时,真正才赋予数据导出需要使用的函数(写出数据对应的是WriteDataChunks(AH),本函数通过函数指针,真正调用了如上条提到的dumpTableData_insert等函数)。
    留意,又一处函数指针的使用典范。    
    本函数的入口参数如下:

ArchiveEntry(Archive *AHX,
                    CatalogId catalogId, DumpId dumpId,
                    const char *tag,
                    const char *namespace,
                    const char *tablespace,
                    const char *owner, bool withOids,
                    const char *desc, teSection section,
                    const char *defn,
                    const char *dropStmt, const char *copyStmt,
                    const DumpId *deps, int nDeps,
                    DataDumperPtr dumpFn, void *dumpArg)


     请留意这些参数,能够表明很多信息,如:我们提到的“dumpFn”,表示数据导出时被调用的函数;“dropStmt”参数,表述生成导出对象的drop语句,此功能是支持在恢复时,可以先清理目的数据库中的可能存在的同名同类型对象,避免恢复因同名同类对象存在导致的失败;再如“tablespace”,表示被导出对象处于哪个表空间上,如果被导出对象和表空间没有关系,则本参数传入的值为“NULL”。
       
2.4 WriteDataChunks函数细节

void
     WriteDataChunks(ArchiveHandle *AH)
     {
         TocEntry   *te;
         StartDataPtr startPtr;
         EndDataPtr    endPtr;
     
         for (te = AH->toc->next; te != AH->toc; te = te->next)
         {
             if (te->dataDumper != NULL)
             {
                 AH->currToc = te;
                 /* printf("Writing data for %d (%x)\n", te->id, te); */
     
                 if (strcmp(te->desc, "BLOBS") == 0) // 如果是大对象,也要被当作数据处理
                 {
                     startPtr = AH->StartBlobsPtr;
                     endPtr = AH->EndBlobsPtr;
                 }
                 else
                 {
                     startPtr = AH->StartDataPtr;
                     endPtr = AH->EndDataPtr;
                 }
     
                 if (startPtr != NULL)
                     (*startPtr) (AH, te); // 又一处函数指针的使用,处理大对象的导出
     
                 /*
                  * printf("Dumper arg for %d is %x\n", te->id, te->dataDumperArg);
                  */
     
                 /*
                  * The user-provided DataDumper routine needs to call
                  * AH->WriteData
                  */
                 (*te->dataDumper) ((Archive *) AH, te->dataDumperArg);  //函数指针典范
     
                 if (endPtr != NULL)
                     (*endPtr) (AH, te); // 又一处函数指针的使用,处理大对象的导出
                 AH->currToc = NULL;
             }
         }
     }


3 dumpTableData_insert函数细节
3.1 根据函数名,即可知道是导出表的数据,且采用的是insert语句的方式。这种方式,是生成带有数据的insert语句,在恢复时,在目的库里执行insert语句。
3.2 入口参数:(Archive *fout, void *dcontext),第一个参数,同2.1节介绍的第一个参数相同;第二个参数,表示本数据据对应的“表数据对象”的元信息,其类型是“void *”,在函数内部,对此参数进行了强制类型转换,变为“TableDataInfo *”类型。
3.3 根据目的数据库的版本,从本表读取数据。注意,读取数据的方式,不是简单的select语句,而是采用的“cursor”方式,这样,可以从数据库中逐步读出数据,而不是一下子从数据库读取出所有的数据,此举通常不会导致因数据量大而导致系统内存用完。
3.4 循环从“cursor”中读取数据,每次仅读出100条。
3.5 针对读出的数据,遍历每一条记录,根据列的类型,拼接出完整的insert语句。
3.6 注意不同类型的列数据的拼接方式。