linux数据后台综合管理学习笔记(九)

第九章:开发数据管理子系统

        学习任务:开发数据管理子系统,本子系统主要包含两个程序,一、数据清理程序,根据清理数据的条件,把表中的唯一键字段查询出来,以唯一键字段为条件,删除表中的记录。二、数据迁移程序,该程序是在数据清理程序的中间增加一个步骤,在删除记录之前先将源表中的记录插入目的表。

9.1开发数据清理程序

        程序从上一章末尾的syncincrementex.cpp拷贝修改而来。命名为/project/tools1/c
/deletetable.cpp

        先看程序的运行参数:在解析参数的代码中,只有程序运行时间starttime是可选参数,无需进行判断。推荐唯一键字段名使用自增字段。

struct st_arg
{
  char connstr[101];     // 数据库的连接参数。
  char tname[31];        // 待清理的表名。
  char keycol[31];       // 待清理的表的唯一键字段名。
  char where[1001];      // 待清理的数据需要满足的条件。
  char starttime[31];    // 程序运行的时间区间。
  int  timeout;          // 本程序运行时的超时时间。
  char pname[51];        // 本程序运行时的程序名。
} starg;


// 显示程序的帮助
void _help(char *argv[])
{
  printf("Using:/project/tools1/bin/deletetable logfilename xmlbuffer\n\n");

  printf("Sample:/project/tools1/bin/procctl 3600 /project/tools1/bin/deletetable /log/idc/deletetable_ZHOBTMIND1.log \"<connstr>127.0.0.1,root,root,ltbo,3306</connstr><tname>T_ZHOBTMIND1</tname><keycol>keyid</keycol><where>where ddatetime<timestampadd(minute,-120,now())</where><starttime>01,02,03,04,05,13</starttime><timeout>120</timeout><pname>deletetable_ZHOBTMIND1</pname>\"\n\n");

  printf("本程序是数据中心的公共功能模块,用于定时清理表中的数据。\n");

  printf("logfilename 本程序运行的日志文件。\n");
  printf("xmlbuffer   本程序运行的参数,用xml表示,具体如下:\n\n");

  printf("connstr     数据库的连接参数,格式:ip,username,password,dbname,port。\n");
  printf("tname       待清理数据表的表名。\n");
  printf("keycol      待清理数据表的唯一键字段名。\n");
  printf("starttime   程序运行的时间区间,例如02,13表示:如果程序运行时,踏中02时和13时则运行,其它时间\n"\
         "            不运行。如果starttime为空,本参数将失效,只要本程序启动就会执行数据迁移,为了减少\n"\
         "            对数据库的压力,数据迁移一般在数据库最闲的时候时进行。\n");
  printf("where       待清理的数据需要满足的条件,即SQL语句中的where部分。\n");
  printf("timeout     本程序的超时时间,单位:秒,建议设置120以上。\n");
  printf("pname       进程名,尽可能采用易懂的、与其它进程不同的名称,方便故障排查。\n\n");
}

        在主程序解析完参数中,紧接着判断程序是否在运行时间段内,如果不在,立刻中止程序。

// 判断当前时间是否在程序运行的时间区间内。
bool instarttime()
{
  // 程序运行的时间区间,例如02,13表示:如果程序启动时,只有踏中02时和13时则运行,其它时间不运行。
  if (strlen(starg.starttime)!=0)
  {
    char strHH24[3];
    memset(strHH24,0,sizeof(strHH24));
    LocalTime(strHH24,"hh24");  // 只获取当前时间中的小时。
    if (strstr(starg.starttime,strHH24)==0) return false;
  }

  return true;
}

         再看业务处理主函数的流程:

// 业务处理主函数。
bool _deletetable()
{
  //从表中提取待删除记录的唯一键

  //拼接绑定删除sql语句where,唯一键,in (...)的字符串
 
  //准备删除的sql,一次删除MAXPARAMS条记录

  while(true)
  {
   //获取结果集

   //每MAXPARAMS条记录进行一次删除
  }

  //不足MAXPARAMS条记录,再进行一次删除

  return true;
}

        注意,本程序要创建两个连接对象,这是因为mysql数据库如果执行了select语句, 在结果集没有获取完全之前,同一个connection中的全部sqlstatement对象都不能执行任何sql语句。

        connection conn1;     // 用于执行查询SQL语句的数据库连接。
        connection conn2;     // 用于执行删除SQL语句的数据库连接。

        完整代码:

/*
 *  程序名:deletetable.cpp,本程序是数据中心的公共功能模块,用于定时清理表中的数据。
 *  作者:sxixia
*/
#include "_public.h"
#include "_mysql.h"

struct st_arg
{
  char connstr[101];     // 数据库的连接参数。
  char tname[31];        // 待清理的表名。
  char keycol[31];       // 待清理的表的唯一键字段名。
  char where[1001];      // 待清理的数据需要满足的条件。
  char starttime[31];    // 程序运行的时间区间。
  int  timeout;          // 本程序运行时的超时时间。
  char pname[51];        // 本程序运行时的程序名。
} starg;

// 显示程序的帮助
void _help(char *argv[]);

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer);

CLogFile logfile;

// 判断当前时间是否在程序运行的时间区间内。
bool instarttime();

connection conn1;     // 用于执行查询SQL语句的数据库连接。
connection conn2;     // 用于执行删除SQL语句的数据库连接。

// 业务处理主函数。
bool _deletetable();
 
void EXIT(int sig);

CPActive PActive;

int main(int argc,char *argv[])
{
  if (argc!=3) { _help(argv); return -1; }

  // 关闭全部的信号和输入输出
  // 处理程序退出的信号
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);

  if (logfile.Open(argv[1],"a+")==false)
  {
    printf("打开日志文件失败(%s)。\n",argv[1]); return -1;
  }

  // 把xml解析到参数starg结构中
  if (_xmltoarg(argv[2])==false) return -1;

  // 判断当前时间是否在程序运行的时间区间内。
  if (instarttime()==false) return 0;

  PActive.AddPInfo(starg.timeout,starg.pname);
  // 注意,在调试程序的时候,可以启用类似以下的代码,防止超时。
  // PActive.AddPInfo(starg.timeout*100,starg.pname);

  if (conn1.connecttodb(starg.connstr,NULL) != 0)
  {
    logfile.Write("connect database(%s) failed.\n%s\n",starg.connstr,conn1.m_cda.message); EXIT(-1);
  }

  if (conn2.connecttodb(starg.connstr,NULL,1) != 0)  // 注意参数,开启自动提交。
  {
    logfile.Write("connect database(%s) failed.\n%s\n",starg.connstr,conn2.m_cda.message); EXIT(-1);
  }

  // logfile.Write("connect database(%s) ok.\n",starg.connstr);

  // 业务处理主函数。
  _deletetable();
}

// 显示程序的帮助
void _help(char *argv[])
{
  printf("Using:/project/tools1/bin/deletetable logfilename xmlbuffer\n\n");

  printf("Sample:/project/tools1/bin/procctl 3600 /project/tools1/bin/deletetable /log/idc/deletetable_ZHOBTMIND1.log \"<connstr>127.0.0.1,root,root,ltbo,3306</connstr><tname>T_ZHOBTMIND1</tname><keycol>keyid</keycol><where>where ddatetime<timestampadd(minute,-120,now())</where><starttime>01,02,03,04,05,13</starttime><timeout>120</timeout><pname>deletetable_ZHOBTMIND1</pname>\"\n\n");

  printf("本程序是数据中心的公共功能模块,用于定时清理表中的数据。\n");

  printf("logfilename 本程序运行的日志文件。\n");
  printf("xmlbuffer   本程序运行的参数,用xml表示,具体如下:\n\n");

  printf("connstr     数据库的连接参数,格式:ip,username,password,dbname,port。\n");
  printf("tname       待清理数据表的表名。\n");
  printf("keycol      待清理数据表的唯一键字段名。\n");
  printf("starttime   程序运行的时间区间,例如02,13表示:如果程序运行时,踏中02时和13时则运行,其它时间\n"\
         "            不运行。如果starttime为空,本参数将失效,只要本程序启动就会执行数据迁移,为了减少\n"\
         "            对数据库的压力,数据迁移一般在数据库最闲的时候时进行。\n");
  printf("where       待清理的数据需要满足的条件,即SQL语句中的where部分。\n");
  printf("timeout     本程序的超时时间,单位:秒,建议设置120以上。\n");
  printf("pname       进程名,尽可能采用易懂的、与其它进程不同的名称,方便故障排查。\n\n");
}

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg,0,sizeof(struct st_arg));

  GetXMLBuffer(strxmlbuffer,"connstr",starg.connstr,100);
  if (strlen(starg.connstr)==0) { logfile.Write("connstr is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"tname",starg.tname,30);
  if (strlen(starg.tname)==0) { logfile.Write("tname is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"keycol",starg.keycol,30);
  if (strlen(starg.keycol)==0) { logfile.Write("keycol is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"where",starg.where,1000);
  if (strlen(starg.where)==0) { logfile.Write("where is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"starttime",starg.starttime,30);

  GetXMLBuffer(strxmlbuffer,"timeout",&starg.timeout);
  if (starg.timeout==0) { logfile.Write("timeout is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"pname",starg.pname,50);
  if (strlen(starg.pname)==0) { logfile.Write("pname is null.\n"); return false; }

  return true;
}

void EXIT(int sig)
{
  logfile.Write("程序退出,sig=%d\n\n",sig);

  conn1.disconnect();
  conn2.disconnect();

  exit(0);
}

// 业务处理主函数。
bool _deletetable()
{
  CTimer Timer;          //计时器,用于日志记录时间

  char tmpvalue[51];    // 存放从表提取待删除记录的唯一键的值。

  // 从表提取待删除记录的唯一键。
  sqlstatement stmtsel(&conn1);
  stmtsel.prepare("select %s from %s %s",starg.keycol,starg.tname,starg.where);
  //唯一键名,表名,查询条件where
  stmtsel.bindout(1,tmpvalue,50);
  //绑定输出,查询的结果存放在temvalue里
  // 拼接绑定删除SQL语句where 唯一键 in (...)的字符串。
  char bindstr[2001];    
  char strtemp[11];

  memset(bindstr,0,sizeof(bindstr));

  for (int ii=0;ii<MAXPARAMS;ii++)
  {
    memset(strtemp,0,sizeof(strtemp));
    sprintf(strtemp,":%lu,",ii+1);       
    strcat(bindstr,strtemp);
  }
  //这一部分结束后,bindstr的格式是<:1,:2,:3,...:MAXPARAMS,>多余了最后一个','
  bindstr[strlen(bindstr)-1]=0;    // 最后一个逗号是多余的。

  char keyvalues[MAXPARAMS][51];   // 存放唯一键字段的值。

  // 准备删除数据的SQL,一次删除MAXPARAMS条记录。
  sqlstatement stmtdel(&conn2);
  stmtdel.prepare("delete from %s where %s in (%s)",starg.tname,starg.keycol,bindstr);
  //表名,唯一键字段名,bindstr
  for (int ii=0;ii<MAXPARAMS;ii++)
    stmtdel.bindin(ii+1,keyvalues[ii],50);

  int ccount=0;
  memset(keyvalues,0,sizeof(keyvalues));

  if (stmtsel.execute()!=0)
  {
    logfile.Write("stmtsel.execute() failed.\n%s\n%s\n",stmtsel.m_sql,stmtsel.m_cda.message); return false;
  }

  while (true)
  {
    memset(tmpvalue,0,sizeof(tmpvalue));

    // 获取结果集。
    if (stmtsel.next()!=0) break;

    strcpy(keyvalues[ccount],tmpvalue);
    ccount++;

    // 每MAXPARAMS条记录执行一次删除语句。
    if (ccount==MAXPARAMS)
    {
      if (stmtdel.execute()!=0)
      {
        logfile.Write("stmtdel.execute() failed.\n%s\n%s\n",stmtdel.m_sql,stmtdel.m_cda.message); return false;
      }

      ccount=0;
      memset(keyvalues,0,sizeof(keyvalues));
    
      PActive.UptATime();
    }
  }

  // 如果不足MAXPARAMS条记录,再执行一次删除。
  if (ccount>0)
  {
    if (stmtdel.execute()!=0)
    {
      logfile.Write("stmtdel.execute() failed.\n%s\n%s\n",stmtdel.m_sql,stmtdel.m_cda.message); return false;
    }
  }

  if (stmtsel.m_cda.rpc>0) logfile.Write("delete from %s %d rows in %.02fsec.\n",starg.tname,stmtsel.m_cda.rpc,Timer.Elapsed());

  return true;
}

// 判断当前时间是否在程序运行的时间区间内。
bool instarttime()
{
  // 程序运行的时间区间,例如02,13表示:如果程序启动时,踏中02时和13时则运行,其它时间不运行。
  if (strlen(starg.starttime)!=0)
  {
    char strHH24[3];
    memset(strHH24,0,sizeof(strHH24));
    LocalTime(strHH24,"hh24");  // 只获取当前时间中的小时。
    if (strstr(starg.starttime,strHH24)==0) return false;
  }

  return true;
}

         捋一捋程序的业务处理主程序运行逻辑:我们首先拼接查询语句的sql语句,最终完成是这样的:select keyid from t_zhobtmind1 where ddatetime<timestampadd(minute,-120,now());这条语句执行的结果是:

 每次取出一条记录存放在tmpvalue[51]里面,紧接着拼接绑定删除SQL语句where 唯一键 in (...)的字符串。类似于whre 10068 in (10066,10067,10068)  这一部分结束后,bindstr的格式是        :1,:2,:3,...:MAXPARAMS,    多余了最后一个','

memset(bindstr,0,sizeof(bindstr));

for (int ii=0;ii<MAXPARAMS;ii++)
{
  memset(strtemp,0,sizeof(strtemp));
  sprintf(strtemp,":%lu,",ii+1);       
  strcat(bindstr,strtemp);
}

bindstr[strlen(bindstr)-1]=0;    // 最后一个逗号是多余的。

        接着准备删除的sql语句:

stmtdel.prepare("delete from %s where %s in (%s)",starg.tname,starg.keycol,bindstr) 
delete from t_zhobtmind1 where keyid in (231,6104)   最后的结果类似这样一个语句。既然bindstr已经创建好了,自然需要一个变量来存放查询出来的结果,再使用bindin函数将实际内容与占位参数绑定起来。这个变量就是char keyvalues[MAXPARAMS][51];   // 存放唯一键字段的值。

        一切准备就绪,执行查询的语句,在while循环里,取出一条记录例如10068放入tmpvalue里,把该变量的内容复制到二维数组keyvalues[ccount]里,这就是为什么变量keyvalues的一维长度是MAXPARAMS,select的结果集多达2517条,每次达到MAXPARAMS条记录就执行一次删除,同时记得清空计数器和keyvalues数组和更新程序心跳。在取出结果集失败,代表着已经没有记录时退出循环,如果此时ccountr大于0,代表还有不足MAXPARASM条记录,再执行一次删除。写日志,程序结束。

        MAXPARAMS在_mysql.h中定义:

// 执行SQL语句前绑定输入或输出变量个数的最大值,256是很大的了,可以根据实际情况调整。
#define MAXPARAMS  256

9.2开发数据迁移程序

        程序从刚刚的代码复制过来,命名为migratetable.cpp。首先修改参数以及帮助文档:做了一些命名的改变以及增加两个新参数:dsttname    迁移目的表的表名,注意,srctname和dsttname的结构必须完全相同。虽然我们可以支持两个表的结构不相同的迁移,但是没有必要。

        maxcount    每执行一次迁移操作的记录数,不能超过MAXPARAMS(256)。那为什么不直接使用MAXPARAMS呢?一是当数据库中可能存在blob字段时,会占用大量的空间,这时没批的迁移数不应该太多,二是如果迁移的过程中出现了唯一键冲突时,会导致一整批的记录迁移失败,所以有时候可能会采用一批只迁移一条记录的方法,出现唯一键冲突的记录不管他,继续迁移后面的记录。

struct st_arg
{
  char connstr[101];     // 数据库的连接参数。
  char srctname[31];     // 待迁移的表名。
  char dsttname[31];     // 目的表的表名。
  char keycol[31];       // 待迁移的表的唯一键字段名。
  char where[1001];      // 待迁移的数据需要满足的条件。
  char starttime[31];    // 程序运行的时间区间。
  int  maxcount;         // 每执行一次迁移操作的记录数。
  int  timeout;          // 本程序运行时的超时时间。
  char pname[51];        // 本程序运行时的程序名。
} starg;


// 显示程序的帮助
void _help(char *argv[])
{
  printf("Using:/project/tools1/bin/migratetable logfilename xmlbuffer\n\n");

  printf("Sample:/project/tools1/bin/procctl 3600 /project/tools1/bin/migratetable /log/idc/migratetable_ZHOBTMIND.log \"<connstr>127.0.0.1,root,root,ltbo,3306</connstr><srctname>T_ZHOBTMIND</srctname><dsttname>T_ZHOBTMIND_HIS</dsttname><keycol>keyid</keycol><where>where ddatetime<timestampadd(minute,-120,now())</where><starttime>01,02,03,04,05,13</starttime><maxcount>300</maxcount><timeout>120</timeout><pname>migratetable_ZHOBTMIND</pname>\"\n\n");

  printf("本程序是数据中心的公共功能模块,用于迁移表中的数据。\n");

  printf("logfilename 本程序运行的日志文件。\n");
  printf("xmlbuffer   本程序运行的参数,用xml表示,具体如下:\n\n");

  printf("connstr     数据库的连接参数,格式:ip,username,password,dbname,port。\n");
  printf("srctname    待迁移数据源表的表名\n");
  printf("dsttname    迁移目的表的表名,注意,srctname和dsttname的结构必须完全相同。\n");
  printf("keycol      待迁移数据表的唯一键字段名。\n");
  printf("starttime   程序运行的时间区间,例如02,13表示:如果程序运行时,踏中02时和13时则运行,其它时间\n"\
         "            不运行。如果starttime为空,本参数将失效,只要本程序启动就会执行数据迁移,为了减少\n"\
         "            对数据库的压力,数据迁移一般在数据库最闲的时候时进行。\n");
  printf("where       待迁移的数据需要满足的条件,即SQL语句中的where部分。\n");
  printf("maxcount    每执行一次迁移操作的记录数,不能超过MAXPARAMS(256)。\n");
  printf("timeout     本程序的超时时间,单位:秒,建议设置120以上。\n");
  printf("pname       进程名,尽可能采用易懂的、与其它进程不同的名称,方便故障排查。\n\n");
}

        数据的迁移需要执行插入的sql语句,所以需要先从表的数据字典中把全部的字段名取出来,在业务处理主函数中:由于用到了类CCTABCOLS,所以头文件应该改成#include "_tools.h"。

// 业务处理主函数。
bool _migratetable()
{
  CTimer Timer;

  // 从数据字典中获取表中全部的字段名。
  CTABCOLS TABCOLS;

  // 获取待迁移数据表的字段名,用starg.srctname或starg.dsttname都可以。
  if (TABCOLS.allcols(&conn2,starg.dsttname)==false)
  {
    logfile.Write("表%s不存在。\n",starg.dsttname); return false;
  }

        注意将所有的MAXPARAMS替换成我们的参数starg.maxcount,这里绑定的是conn2,conn1负责查询。insert语句是:

insert into T_ZHOBTMIND_HIS(obtid,ddatetime,t,p,u,wd,wf,r,vis,upttime,keyid) select obtid,ddatetime,t,p,u,wd,wf,r,vis,upttime,keyid from T_ZHOBTMIND where keyid in (?,?,...?)  这里总共有starg.maxcount个?,对照着后面的参数部分很容易理解。?部分就是我们上一节分析过的bindstr。别忘了插入的bindstr也需要绑定参数。与删除的bindstr同时绑定即可。

// 准备插入和删除表数据的sql,一次迁移starg.maxcount条记录。
  sqlstatement stmtins(&conn2);
  stmtins.prepare("insert into %s(%s) select %s from %s where %s in (%s)",starg.dsttname,TABCOLS.m_allcols,TABCOLS.m_allcols,starg.srctname,starg.keycol,bindstr);

         每starg.maxcount条记录执行一次迁移语句。执行删除语句前先执行一次插入语句,1062代表着违反唯一键约束,也就是我们说的违反唯一键约束时,继续插入,跳过这一条,除此之外其他的错误应该立即返回false。在while循环外的删除语句前也做同样的处理。注意:我们此处启用了conn2的事务,最后需要提交conn2的事务。没有做业务处理失败回滚事务的处理,笔者认为可以改进这一点。

  while (true)
  {
    memset(tmpvalue,0,sizeof(tmpvalue));

    // 获取结果集。
    if (stmtsel.next()!=0) break;

    strcpy(keyvalues[ccount],tmpvalue);
    ccount++;

    // 每starg.maxcount条记录执行一次迁移语句。
    if (ccount==starg.maxcount)
    {
      // 先插入starg.dsttname表。
      if (stmtins.execute()!=0)
      {
        logfile.Write("stmtins.execute() failed.\n%s\n%s\n",stmtins.m_sql,stmtins.m_cda.message); 
        if (stmtins.m_cda.rc!=1062) return false;
      }

      // 再删除starg.srctname表。
      if (stmtdel.execute()!=0)
      {
        logfile.Write("stmtdel.execute() failed.\n%s\n%s\n",stmtdel.m_sql,stmtdel.m_cda.message); return false;
      }

      conn2.commit();

      ccount=0;
      memset(keyvalues,0,sizeof(keyvalues));
    
      PActive.UptATime();
    }
  }

       完整代码:

/*
 *  程序名:migratetable.cpp,本程序是数据中心的公共功能模块,用于迁移表中的数据。
 *  作者:sxixia
*/
#include "_tools.h"

struct st_arg
{
  char connstr[101];     // 数据库的连接参数。
  char srctname[31];     // 待迁移的表名。
  char dsttname[31];     // 目的表的表名。
  char keycol[31];       // 待迁移的表的唯一键字段名。
  char where[1001];      // 待迁移的数据需要满足的条件。
  char starttime[31];    // 程序运行的时间区间。
  int  maxcount;         // 每执行一次迁移操作的记录数。
  int  timeout;          // 本程序运行时的超时时间。
  char pname[51];        // 本程序运行时的程序名。
} starg;

// 显示程序的帮助
void _help(char *argv[]);

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer);

CLogFile logfile;

// 判断当前时间是否在程序运行的时间区间内。
bool instarttime();

connection conn1;     // 用于执行查询SQL语句的数据库连接。
connection conn2;     // 用于执行插入和删除SQL语句的数据库连接。

// 业务处理主函数。
bool _migratetable();
 
void EXIT(int sig);

CPActive PActive;

int main(int argc,char *argv[])
{
  if (argc!=3) { _help(argv); return -1; }

  // 关闭全部的信号和输入输出
  // 处理程序退出的信号
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);

  if (logfile.Open(argv[1],"a+")==false)
  {
    printf("打开日志文件失败(%s)。\n",argv[1]); return -1;
  }

  // 把xml解析到参数starg结构中
  if (_xmltoarg(argv[2])==false) return -1;

  // 判断当前时间是否在程序运行的时间区间内。
  if (instarttime()==false) return 0;

  PActive.AddPInfo(starg.timeout,starg.pname);
  // 注意,在调试程序的时候,可以启用类似以下的代码,防止超时。
  // PActive.AddPInfo(starg.timeout*100,starg.pname);

  if (conn1.connecttodb(starg.connstr,NULL) != 0)
  {
    logfile.Write("connect database(%s) failed.\n%s\n",starg.connstr,conn1.m_cda.message); EXIT(-1);
  }

  if (conn2.connecttodb(starg.connstr,NULL) != 0)  
  {
    logfile.Write("connect database(%s) failed.\n%s\n",starg.connstr,conn2.m_cda.message); EXIT(-1);
  }

  // 业务处理主函数。
  _migratetable();
}

// 显示程序的帮助
void _help(char *argv[])
{
  printf("Using:/project/tools1/bin/migratetable logfilename xmlbuffer\n\n");

  printf("Sample:/project/tools1/bin/procctl 3600 /project/tools1/bin/migratetable /log/idc/migratetable_ZHOBTMIND.log \"<connstr>127.0.0.1,root,root,ltbo,3306</connstr><srctname>T_ZHOBTMIND</srctname><dsttname>T_ZHOBTMIND_HIS</dsttname><keycol>keyid</keycol><where>where 1</where><starttime>01,02,03,04,05,16</starttime><maxcount>300</maxcount><timeout>120</timeout><pname>migratetable_ZHOBTMIND</pname>\"\n\n");

  printf("本程序是数据中心的公共功能模块,用于迁移表中的数据。\n");

  printf("logfilename 本程序运行的日志文件。\n");
  printf("xmlbuffer   本程序运行的参数,用xml表示,具体如下:\n\n");

  printf("connstr     数据库的连接参数,格式:ip,username,password,dbname,port。\n");
  printf("srctname    待迁移数据源表的表名\n");
  printf("dsttname    迁移目的表的表名,注意,srctname和dsttname的结构必须完全相同。\n");
  printf("keycol      待迁移数据表的唯一键字段名。\n");
  printf("starttime   程序运行的时间区间,例如02,13表示:如果程序运行时,踏中02时和13时则运行,其它时间\n"\
         "            不运行。如果starttime为空,本参数将失效,只要本程序启动就会执行数据迁移,为了减少\n"\
         "            对数据库的压力,数据迁移一般在数据库最闲的时候时进行。\n");
  printf("where       待迁移的数据需要满足的条件,即SQL语句中的where部分。\n");
  printf("maxcount    每执行一次迁移操作的记录数,不能超过MAXPARAMS(256)。\n");
  printf("timeout     本程序的超时时间,单位:秒,建议设置120以上。\n");
  printf("pname       进程名,尽可能采用易懂的、与其它进程不同的名称,方便故障排查。\n\n");
}

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg,0,sizeof(struct st_arg));

  GetXMLBuffer(strxmlbuffer,"connstr",starg.connstr,100);
  if (strlen(starg.connstr)==0) { logfile.Write("connstr is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"srctname",starg.srctname,30);
  if (strlen(starg.srctname)==0) { logfile.Write("srctname is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"dsttname",starg.dsttname,30);
  if (strlen(starg.dsttname)==0) { logfile.Write("dsttname is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"keycol",starg.keycol,30);
  if (strlen(starg.keycol)==0) { logfile.Write("keycol is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"where",starg.where,1000);
  if (strlen(starg.where)==0) { logfile.Write("where is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"starttime",starg.starttime,30);

  GetXMLBuffer(strxmlbuffer,"maxcount",&starg.maxcount);
  if (starg.maxcount==0) { logfile.Write("maxcount is null.\n"); return false; }
  if (starg.maxcount>MAXPARAMS) starg.maxcount=MAXPARAMS;

  GetXMLBuffer(strxmlbuffer,"timeout",&starg.timeout);
  if (starg.timeout==0) { logfile.Write("timeout is null.\n"); return false; }

  GetXMLBuffer(strxmlbuffer,"pname",starg.pname,50);
  if (strlen(starg.pname)==0) { logfile.Write("pname is null.\n"); return false; }

  return true;
}

void EXIT(int sig)
{
  logfile.Write("程序退出,sig=%d\n\n",sig);

  conn1.disconnect();
  conn2.disconnect();

  exit(0);
}

// 业务处理主函数。
bool _migratetable()
{
  CTimer Timer;

  // 从数据字典中获取表中全部的字段名。
  CTABCOLS TABCOLS;

  // 获取待迁移数据表的字段名,用starg.srctname或starg.dsttname都可以。
  if (TABCOLS.allcols(&conn2,starg.dsttname)==false)
  {
    logfile.Write("表%s不存在。\n",starg.dsttname); return false;
  }

  char tmpvalue[51];    // 从数据源表查到的需要迁移记录的key字段的值。

  // 从数据源表提取待迁移记录的唯一键,无需排序。
  sqlstatement stmtsel(&conn1);
  stmtsel.prepare("select %s from %s %s",starg.keycol,starg.srctname,starg.where);
  stmtsel.bindout(1,tmpvalue,50);

  // 拼接绑定删除SQL语句where 唯一键 in (...)的字符串。
  char bindstr[2001];    
  char strtemp[11];

  memset(bindstr,0,sizeof(bindstr));

  for (int ii=0;ii<starg.maxcount;ii++)
  {
    memset(strtemp,0,sizeof(strtemp));
    sprintf(strtemp,":%lu,",ii+1);       
    strcat(bindstr,strtemp);
  }

  bindstr[strlen(bindstr)-1]=0;    // 最后一个逗号是多余的。

  char keyvalues[starg.maxcount][51];   // 存放唯一键字段的值。

  // 准备插入和删除表数据的sql,一次迁移starg.maxcount条记录。
  sqlstatement stmtins(&conn2);
  stmtins.prepare("insert into %s(%s) select %s from %s where %s in (%s)",starg.dsttname,TABCOLS.m_allcols,TABCOLS.m_allcols,starg.srctname,starg.keycol,bindstr);
  
  sqlstatement stmtdel(&conn2);
  stmtdel.prepare("delete from %s where %s in (%s)",starg.srctname,starg.keycol,bindstr);

  for (int ii=0;ii<starg.maxcount;ii++)
  {
    stmtins.bindin(ii+1,keyvalues[ii],50);
    stmtdel.bindin(ii+1,keyvalues[ii],50);
  }

  int ccount=0;
  memset(keyvalues,0,sizeof(keyvalues));

  if (stmtsel.execute()!=0)
  {
    logfile.Write("stmtsel.execute() failed.\n%s\n%s\n",stmtsel.m_sql,stmtsel.m_cda.message); return false;
  }

  while (true)
  {
    memset(tmpvalue,0,sizeof(tmpvalue));

    // 获取结果集。
    if (stmtsel.next()!=0) break;

    strcpy(keyvalues[ccount],tmpvalue);
    ccount++;

    // 每starg.maxcount条记录执行一次迁移语句。
    if (ccount==starg.maxcount)
    {
      // 先插入starg.dsttname表。
      if (stmtins.execute()!=0)
      {
        logfile.Write("stmtins.execute() failed.\n%s\n%s\n",stmtins.m_sql,stmtins.m_cda.message); 
        if (stmtins.m_cda.rc!=1062) return false;
      }

      // 再删除starg.srctname表。
      if (stmtdel.execute()!=0)
      {
        logfile.Write("stmtdel.execute() failed.\n%s\n%s\n",stmtdel.m_sql,stmtdel.m_cda.message); return false;
      }

      conn2.commit();

      ccount=0;
      memset(keyvalues,0,sizeof(keyvalues));
    
      PActive.UptATime();
    }
  }

  // 如果不足starg.maxcount条记录,再执行一次迁移。
  if (ccount>0)
  {
    if (stmtins.execute()!=0)
    {
      logfile.Write("stmtins.execute() failed.\n%s\n%s\n",stmtins.m_sql,stmtins.m_cda.message);
      if (stmtins.m_cda.rc!=1062) return false;
    }

    if (stmtdel.execute()!=0)
    {
      logfile.Write("stmtdel.execute() failed.\n%s\n%s\n",stmtdel.m_sql,stmtdel.m_cda.message); return false;
    }

    conn2.commit();
  }

  if (stmtsel.m_cda.rpc>0) logfile.Write("migrate %s to %s %d rows in %.02fsec.\n",starg.srctname,starg.dsttname,stmtsel.m_cda.rpc,Timer.Elapsed());

  return true;
}

// 判断当前时间是否在程序运行的时间区间内。
bool instarttime()
{
  // 程序运行的时间区间,例如02,13表示:如果程序启动时,踏中02时和13时则运行,其它时间不运行。
  if (strlen(starg.starttime)!=0)
  {
    char strHH24[3];
    memset(strHH24,0,sizeof(strHH24));
    LocalTime(strHH24,"hh24");  // 只获取当前时间中的小时。
    if (strstr(starg.starttime,strHH24)==0) return false;
  }

  return true;
}

9.3配置自动化脚本

        在之前我们配置了这应该脚本:但是这只是临时的办法,现在我们有了数据清理和迁移程序,这一行脚本不需要了。

        在启动脚本后添加这四行,注意对应自己的数据库连接参数:

# 把T_ZHOBTMIND表中120分钟之前的数据迁移到T_ZHOBTMIND_HIS表。
/project/tools1/bin/procctl 3600 /project/tools1/bin/migratetable /log/idc/migratetable_ZHOBTMIND.log "<connstr>127.0.0.1,root,root,ltbo,3306</connstr><srctname>T_ZHOBTMIND</srctname><dsttname>T_ZHOBTMIND_HIS</dsttname><keycol>keyid</keycol><where>where ddatetime<timestampadd(minute,-120,now())</where><maxcount>300</maxcount><timeout>120</timeout><pname>migratetable_ZHOBTMIND</pname>"

# 清理T_ZHOBTMIND1表中120分钟之前的数据。
/project/tools1/bin/procctl 3600 /project/tools1/bin/deletetable /log/idc/deletetable_ZHOBTMIND1.log "<connstr>127.0.0.1,root,root,ltbo,3306</connstr><tname>T_ZHOBTMIND1</tname><keycol>keyid</keycol><where>where ddatetime<timestampadd(minute,-120,now())</where><timeout>120</timeout><pname>deletetable_ZHOBTMIND1</pname>"

# 清理T_ZHOBTMIND2表中120分钟之前的数据。
/project/tools1/bin/procctl 3600 /project/tools1/bin/deletetable /log/idc/deletetable_ZHOBTMIND2.log "<connstr>127.0.0.1,root,root,ltbo1,3306</connstr><tname>T_ZHOBTMIND2</tname><keycol>recid</keycol><where>where ddatetime<timestampadd(minute,-120,now())</where><timeout>120</timeout><pname>deletetable_ZHOBTMIND2</pname>"

# 清理T_ZHOBTMIND3表中120分钟之前的数据。
/project/tools1/bin/procctl 3600 /project/tools1/bin/deletetable /log/idc/deletetable_ZHOBTMIND3.log "<connstr>127.0.0.1,root,root,ltbo1,3306</connstr><tname>T_ZHOBTMIND3</tname><keycol>recid</keycol><where>where ddatetime<timestampadd(minute,-120,now())</where><timeout>120</timeout><pname>deletetable_ZHOBTMIND3</pname>"

# 清理T_ZHOBTMIND_HIS表中240分钟之前的数据。
/project/tools1/bin/procctl 3600 /project/tools1/bin/deletetable /log/idc/deletetable_ZHOBTMIND_HIS.log "<connstr>127.0.0.1,root,root,ltbo,3306</connstr><tname>T_ZHOBTMIND_HIS</tname><keycol>keyid</keycol><where>where ddatetime<timestampadd(minute,-240,now())</where><timeout>120</timeout><pname>deletetable_ZHOBTMIND_HIS</pname>"

         停止脚本这里不再演示,自行添加kill命令即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值