赞
踩
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.c文件的main函数,主要完成如下工作:
调用CreateArchive函数,打开输出文件,输出流为g_fout,g_fout是Archive类型,这里比较巧妙的地方就是根据不同的文件格式,会产生不同的g_fout,对应也就是使用不同的.c文件独立封装了不同导出的文件格式下的处理函数,这样可以很容易地增加新的导出文件格式,提高了可维护性和扩展性,具体的实现方法我们会在下面进行分析。目前支持四种导出文件格式分别是:
在上步中的数据库连接上开启一个事务,保证导出的所有数据的一致性,同时为了保证能够导出浮点数,设置正确的浮点数输出格式:
- 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");
调用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);
这表明要父表先于子表导出。
每一个getXXXs函数,都将执行如下过程:
我们简单分析下目前支持的四种导出格式以及如何实现不同导出格式对应不同处理函数。目前PostgreSQL提供四种导出文件格式,具体如下:
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语句,从表中读出数据,然后把数据写出去。所以,在内存中只是链表上的对象的定义,数据是在边读边写出的,完全可以实现流式导出,如下:
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
当然,除了上面的分析外,还有很多其它详细的内容需要具体分析,比如不同版本的数据库操作方式、版本兼容性的问题、对象权限如何处理、约束关系如何处理等。 这些问题都是值得大家去具体分析的,这里不再详细展开。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。