PgSQL · 源碼分析· pg_dump分析(數據庫導出)


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過程分析

pg_dump.c文件的main函數,主要完成如下工作:

(1) 解析各類參數,包括對應變量賦值和參數間是否相互兼容,如果不兼容,報錯退出。

(2) 調用CreateArchive函數,打開輸出文件,輸出流為g_fout,g_fout是Archive類型,這里比較巧妙的地方就是根據不同的文件格式,會產生不同的g_fout,對應也就使用不同的.c文件獨立封裝了不同導出的文件格式下的處理函數,這樣可以很容易地增加新的導出文件格式,提高了可維護性和擴展性,具體的實現方法我們會在下面進行分析。目前支持四種導出文件格式和分別是:

custom pg_backup_custom.c 導出對象存儲到二進制格式的文件中
file pg_backup_files.c 導出對象存儲到指定的文件中
plain pg_backup_null.c 導出文件到標准輸出
tar pg_backup_tar.c 以壓縮文件的格式導出文件

(3)調用ConnectDatabase函數,連接目的數據庫,並在這個數據庫上執行一些SQL語句,如設定C/S之間的編碼、設定數據庫對於日期類型的使用格式、針對不同版本的服務器設置一些與版本相關的信息。

(4) 在(3)中的數據庫連接上開啟一個事務,保證導出的所有數據的一致性,同時為了保證能夠導出浮點數,設置正確的浮點數輸出格式:

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"); 

(5) 為了保持pg_dump工具向低版本兼容,根據服務器的版本號決定一些變量的取值。

(6) 查詢並存儲需要導出的模式和表的信息。

(7) 調用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);

上句表明要父表先於子表導出。

(8) 每一個getXXXs函數,都將執行如下過程:

  • 根據服務器版本號,查詢系統表,讀出對象的元數據信息。
  • 循環遍歷每個數據庫對象元數據信息,對每個數據庫對象通過pg_depend系統表計算其依賴對象,並記錄所有對象的元數據信息和它依賴對象的元數據信息。

(9) 調用getTableData函數,“導出”表中的數據。注意,這里導出加引號並不是真正的導出數據,而是用一個鏈表去存儲每一個需要導出的數據庫對象的基本信息,到真正需要導出的時候再遍歷這個鏈表依次做出對應的處理。這里使用了占位的思想,不會占用大量的內存空間。

(10) 如果需要導出大對象,調用getBlobs,同上也是建立一個鏈表,並沒有真正去做導出。

(11) 根據步驟(8)得到每個對象的依賴關系,調用getDependencies函數,重新整理對象間的依賴關系,調用sortDumpableObjects來決定各個數據庫對象導出的順序(不同類型的對象的導出優先級取決於newObjectTypePriority數組;相同類型的對象,按名稱排序)。

(12) 存儲編碼等信息以及本連接對應的目的數據庫的信息。

(13) 遍歷所有對象,逐個“導出”對象(調用了dumpDumpableObject函數,本函數調用一堆諸如dumpNamespace、dumpTable等對象)。如果是“導出”表,則根據“導出”對象的信息,查詢系統表,查閱到每個表對應的列信息,生成表對象對應的SQL語句,輸出SQL語句到g_fou;如果是“導出”表數據,則調用dumpTableData,有兩種方式選擇,一是生成Insert語句,默認的是生成PostgreSQL自身的copy語句。這里不再具體去介紹。

(14) 在“導出”每一個對象時,通常都會調用ArchiveEntry,做真正的SQL語句生成工作。另外,還會調用dumpComment、dumpSecLabel、dumpACL等函數,“導出”本對象對應的一些諸如注釋、權限等相關信息。

(15) 調用RestoreArchive函數,真正的導出數據,注意這里是根據不同的導出文件格式來選擇不同的RestoreArchive函數。

(16) 關閉句柄釋放資源等。

接下來,我們簡單分析下目前支持的四種導出格式以及如何實現不同導出格式對應不同處理函數。目前PostgreSQL提供四種導出文件格式,具體如下:

custom pg_backup_custom.c 導出到二進制格式的備份文件,包括文件頭和文件體。文件體是一個鏈表,保存每個備份對象,每一個可備份對象都有一套統一的結構標識,支持壓縮(壓縮功能依賴於系統編譯選項和pg_config.h文件中的宏定義開關)。
plain pg_backup_null.c 把SQL腳本內容輸出到標准輸出,默認方式。
file pg_backup_files.c 導出包括備份一個主文件和一些輔助文件;主文件方式類似於custom的文件格式,輔助文件是數據文件,每一個輔助文件對應備份對象中的一個表。
tar pg_backup_tar.c 文件備份基本類似“file”方式,但是,最后備份的所有文件都要歸檔到一個tar文件中。文件最大大小為8GB(受限於tar file format)。

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語句,從表中讀出數據,然后把數據寫出去。所以,在內存中只是鏈表上的對象的定義,數據是在邊讀邊寫出的,完全可以實現流式導出,如下:

導出數據,通過管道和psql工具,導入到目的庫

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 

當然,除了上面的分析外,還有很多其它詳細的內容需要具體分析,比如不同版本的數據庫操作方式、版本兼容性的問題、對象權限如何處理、約束關系如何處理等。 這些問題都是值得下一步具體分析的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM