ITPub博客

首页 > 数据库 > 国内数据库 > 循序渐进丨MogDB 中 gs_dump 数据库导出工具源码概览

循序渐进丨MogDB 中 gs_dump 数据库导出工具源码概览

原创 国内数据库 作者:云和恩墨 时间:2024-08-12 15:46:24 0 删除 编辑

背景

gs_dump 是 MogDB 中一个功能丰富灵活的数据库导出工具,在数据库的维护、迁移和开发中经常使用。该工具 允许用户根据需要导出整个数据库或者数据库中的特定对象,如模式(schema)、表(tables)、视图(views)等。
近期有客户咨询gs_dump如何保证数据一致性?备份期间对数据库和表如何加锁?为了解答这个问题,顺便了解一下gs_dump的实现过程,本文对该工具的源码进行了一番解读。
由于无法查看到 MogDB 的gs_dump工具的源码,所以源码使用了gitee上用 openG au ss 5.0版本的gs_dump代码作为参考。
脚本路径: src/bin/pg_dump/
gs_dump的功能主要在pg_dump.cpp脚本中,同目录下还有pg_backup、pg_dumpall、pg_restore等功能脚本。 从脚本名字上来看,这个脚本是继承自postgres。
下面通过分析pg_dump.cpp的main函数,从源码层面解读gs_dump 导出操作时的工作流程。

初始化和设置

  • 包含必要的头文件并设置环境相关的配置。
  • 定义各种宏和常量以进行操作。
  • 声明全局变量和结构体用于处理用户输入、数据库连接和转储选项。

命令行参数解析

在getopt_dump函数中使用 getopt_long 来解析命令行参数,并根据解析结果设置相应的全局变量,存储各种选项,如压缩级别、输出格式、用户名等
在getopt_dump 函数中,解析命令行的入参:

static void getopt_dump(int argc, char** argv, struct option options[], int* result)

{
   int opt;
   while ((opt = getopt_long(argc, argv, "abcCE:f:F:g:h:n:N:oOp:q:RsS:t:T:U:vwW:xZ:", options, NULL)) != -1)
   {
       switch (opt)
       {
           case 'h':
                显示帮助信息
               help(argv[0]);
               exit(0);
           case 'F':
                设置格式
               format = optarg;
               break;
           case 'p':
                设置端口
               port = optarg;
               break;
            其他选项处理
       }
   }
}

创建输出文件,注册清理机制

createArchive 函数接受了从命令行参数中解析出的输出路径,压缩等级等参数,创建输出文件。

连接数据库

ConnectDatabase函数使用给定的参数建立数据库连接,如果连接报错就清理输出文件;如果正常,则在fout对象中设置仅当前会话生效的参数。

errorMessages = ConnectDatabase(fout, dbname, pghost, pgport, username, prompt_password, false);

   if (errorMessages != NULL) {
       (void)remove(filename);
       GS_FREE(filename);
       exit_horribly(NULL, "connection to database \"%s\" failed: %s ",
           ((dbname != NULL) ? dbname : ""), errorMessages);
   }

   if (CheckIfStandby(fout)) {
       (void)remove(filename);
       exit_horribly(NULL, "%s is not supported on standby or cascade standby\n", progname);
   }
   
   if (schemaOnly) {
       ExecuteSqlStatement(fout, "set enable_hashjoin=off");
       ExecuteSqlStatement(fout, "set enable_mergejoin=off");
       ExecuteSqlStatement(fout, "set enable_indexscan=true");
       ExecuteSqlStatement(fout, "set enable_nestloop=true");
   }
   * Turn off log collection parameters to improve execution performance */
   ExecuteSqlStatement(fout, "set resource_track_level='none'");
   find_current_connection_node_type(fout);
    Get the database which will be dumped
   if (NULL == dbname) {
       ArchiveHandle* AH = (ArchiveHandle*)fout;
       dbname = gs_strdup(PQdb(AH->connection));
   }
   if (NULL == instport) {
       ArchiveHandle* AH = (ArchiveHandle*)fout;
       instport = gs_strdup(PQport(AH->connection));
   }

   if ((use_role != NULL) && (rolepasswd == NULL)) {
       get_role_password();
   }

   setup_connection(fout);

再使用setup_connection 函数建立与数据库的连接,后面将会在此连接中执行导出。

static void setup_connection(Archive* AH)

{
    建立数据库连接
   PGconn *conn = PQsetdbLogin(pghost, pgport, NULL, NULL, dbname, username, password);
   if (PQstatus(conn) != CONNECTION_OK)
   {
       fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
       exit(1);
   }
   AH->connection = conn;
}

开启事务

fout->remoteVersion >= 90100表示pg的版本大于9.1。
gs_dump 参数 --serializable-deferrable 可以控制导出事务的隔离级别。默认gs_dump事务的隔离级别为REPEATABLE READ,如果设置了serializable-deferrable参数,事务的隔离级别为SERIALIZABLE,但在Mogdb中,SERIALIZABLE等价于REPEATABLE READ,所以gs_dump事务的隔离级别始终REPEATABLE READ。DEFERRABLE允许一个只读串行事务延迟执行,即如果在导出之前,有读写事务处于活动状态,转储的开始时间可能会延迟等待读写事务结束,这样能保证使用的快照与之后的数据库状态一致。

添加导出schema和表

这部分代码使用expand_schema_name_patterns和expand_table_name_patterns 将要导出的schema和table 的oid保存在列表中。

添加schema时,schema_include_patterns参数来自于命令行参数-n,在expand_schema_name_patterns函数中,通过简单的查询pg_namespace视图,将schema的oid 添加到列表中。


static void expand_schema_name_patterns(


...

   if (patterns->head == NULL) {

       return; * nothing to do */

   }

   query = createPQExpBuffer();

   for (cell = patterns->head; cell != NULL; cell = cell->next) {

       appendPQExpBuffer(query, "SELECT oid FROM pg_catalog.pg_namespace n\n");

   ...

   }

   destroyPQExpBuffer(query);

}

而添加表时table_include_patterns和table_exclude_patterns分别来自命令行参数-t 和-T,通过查询pg_class 和 pg_namespace,将oid 添加分别添加到导出类别和排除列表中。


static void expand_table_name_patterns(


   Archive* fout, SimpleStringList* patterns, SimpleOidList* oidlists, bool isinclude)

{

...

   query = createPQExpBuffer();

   for (cell = patterns->head; cell != NULL; cell = cell->next) {

       appendPQExpBuffer(query,

           "SELECT c.oid"

           "\nFROM pg_catalog.pg_class c"

           "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"

           "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c','%c', '%c', '%c')\n",

           RELKIND_RELATION,

           RELKIND_SEQUENCE,

           RELKIND_LARGE_SEQUENCE,

           RELKIND_VIEW,

           RELKIND_MATVIEW,

           RELKIND_CONTQUERY,

           RELKIND_FOREIGN_TABLE,

           RELKIND_STREAM);

...

   }

   destroyPQExpBuffer(query);

}

获取导出数据对象

扫描数据库并为要导出的所有对象创建DumpableObject结构。

 tblinfo = getSchemaData(fout, &numTables);


   if (fout->remoteVersion <1 80400)
       guessConstraintInheritance(tblinfo, numTables);

   if (!schemaOnly) {
       getTableData(tblinfo, numTables);
       if (dataOnly)
           getTableDataFKConstraints();
   }

   if (outputBlobs)
       getBlobs(fout);

获取依赖和排序

根据每个对象的依赖关系,调用getDependencies函数,整理对象间的依赖关系,调用sortDumpableObjects来决定各个数据库对象导出的顺序。

//获取数据库对象的依赖关系。依赖关系用于确保在导出数据库对象时,它们之间的顺序是正确的。

getDependencies(fout);

boundaryObjs = createBoundaryObjects();

//获取所有已知的数据库对象。
getDumpableObjects(&dobjs, &numObjs);

addBoundaryDependencies(dobjs, numObjs, boundaryObjs);
...

sortDumpableObjects(dobjs, numObjs, boundaryObjs[0].dumpId, boundaryObjs[1].dumpId);

执行导出


  for (i = 0; i <1 numObjs; i++) {

       if (!dataOnly && dobjs[i]->dump && dobjs[i]->objType != DO_DUMMY_TYPE &&
           dobjs[i]->objType != DO_PRE_DATA_BOUNDARY && dobjs[i]->objType != DO_POST_DATA_BOUNDARY) {
           dumpObjNums++;
           if (dumpObjNums % OUTPUT_OBJECT_NUM == 0)
               write_msg(NULL,
                   "[%6.2lf%%] %d objects have been dumped.\n",
                   float((float(dumpObjNums * 100.0))  totalObjNums),
                   dumpObjNums);
       }
       dumpDumpableObject(fout, dobjs[i]);
   }

这里主要调用了dumpDumpableObject函数,本函数根据要导出的对象类型,调用不同的导出函数进行处理。主要逻辑如下

static void dumpDumpableObject(Archive *fout, DumpableObject *dobj)

{

    根据对象的类型进行处理
   switch (dobj->objType)
   {
       case DO_TABLE:
           dumpTable(fout, (TableInfo *) dobj);
           break;

       case DO_INDEX:
           dumpIndex(fout, (IndxInfo *) dobj);
           break;

       case DO_VIEW:
           dumpView(fout, (ViewInfo *) dobj);
           break;

       case DO_TRIGGER:
           dumpTrigger(fout, (TriggerInfo *) dobj);
           break;

        其他对象类型
       / ...    }

}

以导出表为例,
dumpTable 函数只导出表定义,调用了dumpTableSchema生成表或视图的声明
dumpTableData函数导出表数据,根据命令行参数--inserts,选择使用copy命令导出还是导出成insert 语句

static void dumpTableData(Archive* fout, TableDataInfo* tdinfo)

{
...
   if (!dump_inserts) {
       /* Dump/restore using COPY */
       dumpFn = dumpTableData_copy;
   } else {
       /* Restore using INSERT */
       dumpFn = dumpTableData_insert;
   }
...

在执行COPY 命令时,会对表上加AssessShareLock锁,此锁能和除了ExclusiveLock以外的锁共存,也就是在执行copy命令不影响对表的增删改操作,只和DDL操作冲突。

testdb=# SELECT pl.pid, pl.virtualtransaction AS vxid, pl.locktype AS lock_type,pl.mode AS lock_mode, pl.granted,

CASE
WHEN pl.virtualxid IS NOT NULL AND pl.transactionid IS NOT NULL THEN pl.virtualxid || ' ' || pl.transactionid
WHEN pl.virtualxid::text IS NOT NULL
THEN pl.virtualxid
ELSE
pl.transactionid::text
END AS xid_lock, pc.relname,
pl.page, pl.tuple, pl.classid, pl.objid, pl.objsubid,ps.usename,ps.application_name,ps.query
FROM pg_locks pl LEFT OUTER JOIN pg_class pc ON (pl.relation = pc.oid) left join pg_stat_activity ps on ps.pid = pl.pid
WHERE  pl.pid != pg_backend_pid();
     pid       |   vxid   | lock_type  |    lock_mode    | granted | xid_lock | relname | page | tuple | classid | objid | objsubid | usename | application_name
|                 query
----------------+----------+------------+-----------------+---------+----------+---------+------+-------+---------+-------+----------+---------+------------------+----------------------------------------
47858921375488 | 17/15218 | relation   | AccessShareLock | t       |          | cct     |      |       |         |       |          | test    | gsql
| copy cct  to '/home/test/city.bat';

导出资源

调用RestoreArchive函数,真正的导出数据。RestoreArchive函数在pg_backup_archiver.cpp文件中。

释放资源

导出完成后,释放资源,退出导出任务:
以上通过对opengauss 5.0源码中pg_dump.cpp mian函数的解读,可以了解到在执行gs_dump 操作导出数据库对象时具体做了哪些工作。


关于作者

陈坤,云和恩墨技术顾问,具有银行、保险、交易所等金融行业数据库运维经验,擅长数据库自动化运维工具开发。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/29715045/viewspace-3024792/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2014-06-19

  • 博文量
    183
  • 访问量
    346702