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