??? 摘??要: 提出了一種數(shù)據(jù)庫遠程數(shù)據(jù)上報的策略,,并對該策略的程序?qū)崿F(xiàn)過程作了介紹,。
??? 關鍵詞: 遠程數(shù)據(jù)? 上報? 數(shù)據(jù)壓縮
?
在很多數(shù)據(jù)庫應用程序中,,都要用到數(shù)據(jù)遠程上報的功能,,如人口普查、城市危險源及事故普查,、各種考試數(shù)據(jù)上報等,。這些應用程序都需要將各下級部門的特定數(shù)據(jù)提取出來,通過各種方式上報到上級部門,,再由上級部門進行匯總,。上級部門在進行數(shù)據(jù)匯總時,有時候只需要考慮新數(shù)據(jù)的插入問題,。如養(yǎng)路費征收IC卡網(wǎng)絡管理系統(tǒng),,此時下級的每條數(shù)據(jù)記錄是一次輸入的,在數(shù)據(jù)上報之后不會發(fā)生已經(jīng)輸入的數(shù)據(jù)記錄在下級被修改,,從而上級也要發(fā)生相應修改的情況。此時,,用該文提到的用BulkCopy方法就可以滿足其需要,。但是有的情況下,上級部門的數(shù)據(jù)匯總必須考慮下級數(shù)據(jù)所有的變化情況(新數(shù)據(jù)的插入,、已有數(shù)據(jù)記錄的修改,、已有數(shù)據(jù)記錄的刪除)。此時,,用BulkCopy方法就無法對數(shù)據(jù)進行修改與刪除了,。除非在數(shù)據(jù)上報時,下級部門將其所有數(shù)據(jù)都拷貝一份上傳,;上級部門在數(shù)據(jù)接收時,,將該下級部門的歷史數(shù)據(jù)全部刪除,再導入其數(shù)據(jù),,才能保證與下級的數(shù)據(jù)的一致性,。顯然,這樣的處理方式,,必然導致數(shù)據(jù)的傳輸及處理量大增,,而且很多都是做無用功,特別是在很多情況下需要用電話撥號網(wǎng)絡進行數(shù)據(jù)傳輸?shù)臅r候,,其效率非常低,。
1? 總體策略
在作者參與開發(fā)的“城市重大危險源普查及事故管理系統(tǒng)”中,其下級部門為各企業(yè),,上級部門為政府安全生產(chǎn)管理部門,。在該系統(tǒng)中,,企業(yè)定期向政府部門上報相關的危險源及事故信息。其中危險源是指企業(yè)各種鍋爐壓力容器,、加油站等可能發(fā)生危險的地點分布情況,。這些危險源信息可能被增加、修改或刪除,。系統(tǒng)開發(fā)語言為VC++6.0,,企業(yè)端數(shù)據(jù)庫為Access或SQL Server,政府端數(shù)據(jù)庫為SQL Server,,用OLE DB方式連接數(shù)據(jù)庫,,用嵌入式SQL Server語言直接操作數(shù)據(jù)庫數(shù)據(jù)。上下級之間的數(shù)據(jù)傳輸方式為軟盤,、郵件和FTP三種方案,。本系統(tǒng)主要采用了數(shù)據(jù)庫日志及數(shù)據(jù)壓縮技術,盡可能地減少數(shù)據(jù)傳送量,。
數(shù)據(jù)庫日志就是在用VC的嵌入式SQL Server語言進行數(shù)據(jù)庫操作時,,將SQL Server腳本經(jīng)過適當?shù)霓D(zhuǎn)換后記錄在一個數(shù)據(jù)庫日志表(pubTbDBLog)中。上報數(shù)據(jù)時只需要提取pubTbDBLog表的部分內(nèi)容,。政府端接收后,,按順序執(zhí)行上報的SQL Server腳本,即能實現(xiàn)與企業(yè)端的數(shù)據(jù)完全一致,。
數(shù)據(jù)上報與接收的流程如圖1所示,。
?
?
2?數(shù)據(jù)庫日志操作
數(shù)據(jù)庫日志操作的目的是保存SQL腳本,即將企業(yè)端對數(shù)據(jù)庫的操作記錄下來,,存放在數(shù)據(jù)庫日志記錄表pubTbDBLog(nId,,strTable,strSQL,,strOperType,,strGuid,dtOper,,beUped)中,。其中nId為主鍵,自動增量,;strTable為SQL操作的數(shù)據(jù)庫表名,;strSQL為對數(shù)據(jù)庫進行操作的腳本;strOperType為操作類型,,包括插入,、修改、刪除三種,;strGuid為一個GUID值(全球惟一ID號,,長度為32位字符),,用來惟一標識一條記錄;dtOper為操作時間,,取每條記錄生成(即對數(shù)據(jù)庫的每一次操作)的當前時間,;beUped標識當前記錄是否已經(jīng)上報過。
2.1 數(shù)據(jù)庫操作的要求
在本系統(tǒng)中,,當對需要上報數(shù)據(jù)的表進行操作時,,必須滿足如下要求:
(1)數(shù)據(jù)庫記錄的修改(包括添加、刪除,、修改)必須在程序中直接用嵌入式SQL腳本實現(xiàn),。
(2)執(zhí)行數(shù)據(jù)庫操作必須使用一個全局函數(shù)ExecuteSQLCmd(CString strCmd),其中strCmd為執(zhí)行數(shù)據(jù)庫操作的SQL腳本,。該函數(shù)將腳本寫入pubTbDBLog表,。
2.2 保存SQL腳本的過程說明
下面以企業(yè)端用戶向危險源記錄表rsiTbSource(nID,strEnNo,,strName,,strSite,strRank,,strType,,dtSetUp)中插入一條危險源記錄為例,說明保存SQL腳本的操作過程,。
(1)用戶通過對話界面輸入危險源信息,其中strEnNo表示企業(yè)編號,,strName表示危險源名稱,,strSite表示危險源位置,strRank表示危險源等級,,strType表示危險源類型,,dtSetUp表示危險源建立日期。另外nID是一個自動增量,,作為主鍵標識,,沒有具體意義。rsiTbSource表除了以上字段外,,還有其他字段,,本處為簡化而省略。
(2)用戶輸入信息完畢,,點擊“保存”命令按鈕,,系統(tǒng)將用戶輸入的信息拼成一條SQL腳本,如:CString strCmd=INSERT INTO rsiTbSource(strEnNo,,strName,,strSite,,strRank,strType,,dtSetUp)VALUES(‘420124098987001’,,‘熱水鍋爐’,‘武漢市武昌區(qū)武漢大學二區(qū)’,,‘二級’,,‘鍋爐壓力容器’,‘2003-7-21’),。
(3)系統(tǒng)調(diào)用ExecuteSQLCmd函數(shù),,包括以下(4)、(5),、(6)的內(nèi)容,。
(4)對strCmd進行解析,同時生成一個32位的GUID值,,將strCmd修改為strCmd=INSERT INTO rsiTbSource(strEnNo,,strName,strSite,,strRank,,strType,dtSetUp,,strGuid)VALUES(‘420124098987001’,,‘熱水鍋爐’,‘武漢市武昌區(qū)武漢大學二區(qū)’,,‘二級’,,‘鍋爐壓力容器’,‘2003-7-21’,,‘ed32wq3ddvervgjl6tgj8fjFFVBfgwgw’),。
(5)執(zhí)行腳本命令(不使用ExecuteSQLCmd函數(shù))。
(6)如果第(5)步執(zhí)行成功,,則向pubDBLog表插入一條記錄(strTbSource,,strCmd,i,,ed32wq3ddvervgjl6tgj8fjFFVBfgwgw,,2003-7-21,0),,其中的strCmd是第(4)步中strCmd的所有字符,。
3? 數(shù)據(jù)上報
數(shù)據(jù)上報是將pubTbDBLog中符合條件的記錄提取出來,進行壓縮后傳給上級。
3.1 上報數(shù)據(jù)提取
在提取數(shù)據(jù)前要對pubTbDBLog表中的記錄進行一些預處理,。由于每次對需要上報的數(shù)據(jù)庫表的操作都被記錄在了pubTbDBLog表中,,有些記錄(為了方便,稱pubTbDBLog表中的一條記錄為“記錄”,,稱需要上報的數(shù)據(jù)表中的一條記錄為“數(shù)據(jù)”)是不必要的,,上報之前需要進行簡化。例如:向數(shù)據(jù)庫表中插入一條數(shù)據(jù),,后來對該數(shù)據(jù)進行了若干次修改,,最后刪除該條數(shù)據(jù)。此時,,pubTbDBLog中有三個以上與這條數(shù)據(jù)相關的記錄(它們在pubTbDBLog表中的strGuid字段值相同),,其strOperType值分別是“i”、“u”,、“d”,。如果這三個記錄都被上報,并在政府端被先后執(zhí)行,,其結(jié)果還是沒有該條數(shù)據(jù),,白白浪費了執(zhí)行三次SQL命令的時間。
因此,,如果與某個strGuid值相關的數(shù)據(jù)最后被刪除,,則在政府端只需要執(zhí)行一次刪除操作,即與該strGuid值相關的pubTbDBLog表中的記錄只需要上報一條刪除的操作即可,。實現(xiàn)對這種情況的pubTbDBLog表簡化的SQL腳本如下:
DELETE FROM pubTbDBLog WHERE strOperType <> ′d′
AND strGuid IN(SELECT DISTINCT a.strGuid
FROM pubTbDBLog a WHERE a.strOperType=′d′)
完成簡化后,,即可將pubTbDBLog中符合條件(提取數(shù)據(jù)的條件是:未曾上報過的數(shù)據(jù)或指定時間范圍內(nèi)的數(shù)據(jù))的數(shù)據(jù)提取出來放入一個文件中。存放導出數(shù)據(jù)的文件可以是Access 數(shù)據(jù)庫表,,或者是文本文件,。作者用的是Access數(shù)據(jù)庫表,它經(jīng)過壓縮解壓縮后能在政府端直接取Access表中的SQL腳本執(zhí)行,;也可以用文本文件,但上報時需要將符合條件的pubTbDBLog表記錄放入一個臨時文件,,再用bulkCopy的方法存入文本文件,。在政府端用bulkInsert的方法向一個與pubTbDBLog相同結(jié)構(gòu)的表導入數(shù)據(jù),再執(zhí)行該表的記錄中的SQL腳本,。
3.2 數(shù)據(jù)文件壓縮
通常情況下,,企業(yè)端向政府端報送數(shù)據(jù)是經(jīng)過電話撥號網(wǎng)絡進行的,其傳輸速率較低,。因此,,有必要對存放導出數(shù)據(jù)的Access文件或文本文件進行壓縮后再傳遞。本系統(tǒng)使用了WinZip的數(shù)據(jù)壓縮與解壓縮接口,。其過程如下(假定字符串filepathname指向需要壓縮的文件):
(1)獲取文件路徑及文件名信息,。
int nPos,,length,div,;
CString filepathtitle,,filename;
nPos=filepathname.ReverseFind(′.′),;
filepathtitle=filepathname.Left(nPos)
/*去掉擴展名后的文件名,,帶路徑*/
length=filepathname.GetLength( );
div=filepathname.ReverseFind(′\\′),;
filename=filepathname.Right(length-div-1),;/*文件名,不帶路徑*/
(2)創(chuàng)建壓縮文件實例,。
CZipFile zf(filepathtitle+″.cmp″,,0);
char buf[BUF_SIZE],;//BUF_SIZE=2048
CFile f(filepathname,,CFile∷modeRead);
zip_fileinfo zi,;
zf.UpdateZipInfo(zi,,f);
(3)向壓縮文件實例讀入數(shù)據(jù),。
zf.OpenNewFileInZip(filename,,zi,Z_BEST_COMPRESSION),;
int size_read,;
do
{
?????? size_read=f.Read(buf,BUF_SIZE),;
???? ?? if (size_read)
?????? ? ? zf.WriteInFileInZip(buf,,size_read);
}
while (size_read==BUF_SIZE),;
zf.Close( ),;//關閉壓縮文件實例
經(jīng)測試,壓縮文件的大小約為原文件大小的1/5~1/10,,效果較好,。
4? 數(shù)據(jù)接收
政府端數(shù)據(jù)接收的工作包括三個步驟:選擇文件、解壓縮,、導入數(shù)據(jù),。
4.1 選擇數(shù)據(jù)文件
對應于企業(yè)端通過磁盤和郵件上報的數(shù)據(jù),政府端首先需要將其存放到本地計算機的硬盤上,再進行數(shù)據(jù)文件選擇和接收,。
選擇文件時,,調(diào)用打開文件對話框,再由用戶選定一個或多個指定類型(*.cmp)的文件,。對于每一個選中的文件,,系統(tǒng)對其執(zhí)行解壓縮和導入數(shù)據(jù)的操作。
4.2 解壓縮
與前面所述的文件壓縮一樣,,解壓縮與采用了WinZip提供的接口,。其過程如下:
(1)創(chuàng)建解壓縮文件實例,并用選定的壓縮文件初始化,。
CUnzipFile uf(m_strCmpFile),;/*m_strCmpFile為選定的壓縮文件名*/
(2)獲取壓縮文件中被壓縮前的文件信息。
/*指向壓縮文件中的第一個文件(一個壓縮文件可以由多個文件壓縮而成,,本系統(tǒng)只有一個)*/
uf.GoToFirstFile( )
unz_file_info ui,;/*用于管理解壓縮文件信息的一個結(jié)構(gòu)*/
/*獲取壓縮文件中當前文件被壓縮前的信息,如文件名大小*/
uf.GetCurrentFileInfo(&ui),;
int iNameSize=ui.size_filename+1,;
char*pName=new char[iNameSize];
uf.GetCurrentFileInfo(NULL,,pName,,iNameSize);//獲取文件名
TCHAR szDir[_MAX_DIR],;
TCHAR szDrive[_MAX_DRIVE],;
_tsplitpath(m_strUnCmpFile,szDrive,,szDir,,NULL,NULL),;
CString szPath=CString(szDrive)+szDir,;
m_strMovedDB=szPath+pName;/*解壓縮后,,存放文件的路徑與文件名*/
CFile f(m_strMovedDB,,CFile∷modeWrite | CFile∷modeCreate);
delete[ ] pName,;
(3)解壓縮。
uf.OpenCurrentFile( ),;
char buf[BUF_SIZE],;
int size_read;
do
{
?????? size_read=uf.ReadCurrentFile(buf,BUF_SIZE),;
???? ?? if(size_read>0)
?????? ? f.Write(buf,,size_read);
}
while(size_read==BUF_SIZE),;
uf.UpdateFileStatus(f,,ui);? /*還原文件的原來的日期和其他屬性*/
uf.Close( ),;
4.3 導入數(shù)據(jù)
從一個被解壓縮后生成的Access數(shù)據(jù)庫向政府端數(shù)據(jù)庫導入數(shù)據(jù)的過程為:連接該Access數(shù)據(jù)庫,;按先后順序從該數(shù)據(jù)庫表pubTbDBLog中提取記錄;執(zhí)行該條記錄的SQL腳本,。下面只對SQL腳本的執(zhí)行進行說明,。
對于刪除和修改操作,SQL腳本可以直接執(zhí)行,。因為如果上報了重復的數(shù)據(jù),,即使某條記錄已經(jīng)被刪除或修改,再執(zhí)行一次同樣的刪除或修改操作,,SQL命令的執(zhí)行也不會造成語法錯誤或數(shù)據(jù)錯誤,。
對于插入操作,SQL腳本則不能直接執(zhí)行,。因為如果上報了重復的數(shù)據(jù),,則直接執(zhí)行插入會使數(shù)據(jù)庫中存在二條同樣的數(shù)據(jù)。因此,,先要執(zhí)行一次刪除操作,,將對應的數(shù)據(jù)刪除(由于pubTbDBLog表中有二個字段記錄了SQL腳本對應的表名和GUID值,刪除操作很容量執(zhí)行),。即使該數(shù)據(jù)不存在,,SQL命令的執(zhí)行也不會造成語法錯誤或數(shù)據(jù)錯誤。
5? 結(jié)? 論
本文討論的基于數(shù)據(jù)庫操作日志和數(shù)據(jù)壓縮的數(shù)據(jù)上報與接收技術,,有著很強的通用性,。對于任何一個應用系統(tǒng),只要在數(shù)據(jù)庫中建立一個數(shù)據(jù)庫日志表,,并且下級端執(zhí)行數(shù)據(jù)庫操作時滿足2.1中提及的二個條件,,就能使用。特別是對于需要上傳的數(shù)據(jù)涉及的表較多,、下級端對數(shù)據(jù)經(jīng)常需要修改,、上下級數(shù)據(jù)要求較高度的一致性的情況,其優(yōu)點更加明顯,。
本系統(tǒng)的數(shù)據(jù)上報與接收策略也有一些不足之處,。例如,,企業(yè)端pubTbDBLog表中數(shù)據(jù)的導出,可能用bulkCopy效率更高,;數(shù)據(jù)導出與接收時,,暫時還沒有使用多線程技術等。這些都需要在以后的工作中進一步完善,。
參考文獻
1 閻宇馳,,倪津.SQL Server數(shù)據(jù)庫遠程數(shù)據(jù)上報策略及其應用研究.山西交通科技,2002,;(2)
2 羅琨,,陳奇.連鎖店MIS中多數(shù)據(jù)庫數(shù)據(jù)通信的設計和實現(xiàn).計算機應用研究,2000,;(6)
3 薩師宣,,王珊.數(shù)據(jù)庫系統(tǒng)概論.北京:高等教育出版社,1999