概述
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的工作。
dump 過程分析
pg_dump.c檔案的main函數,主要完成如下工作:
- 解析各類參數,包括對應變量指派和參數間是否互相相容,如果不相容,報錯退出。
- 調用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),以壓縮檔案的格式導出檔案。
- 調用ConnectDatabase函數,連接配接目的資料庫,并在這個資料庫上執行一些SQL語句,如設定C/S之間的編碼、設定資料庫對于日期類型的使用格式、針對不同版本的伺服器設定一些與版本相關的資訊。
- 在上步中的資料庫連接配接上開啟一個事務,保證導出的所有資料的一緻性,同時為了保證能夠導出浮點數,設定正确的浮點數輸出格式:
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");
- 為了保持pg_dump工具向低版本相容,根據伺服器的版本号決定一些變量的取值。
- 查詢并存儲需要導出的模式和表的資訊。
- 調用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);
這表明要父表先于子表導出。
- 每一個getXXXs函數,都将執行如下過程:
-
根據伺服器版本号,查詢系統表,讀出對象的中繼資料資訊。
- 循環周遊每個資料庫對象中繼資料資訊,對每個資料庫對象通過pg_depend系統表計算其依賴對象,并記錄所有對象的中繼資料資訊和它依賴對象的中繼資料資訊。
-
- 調用getTableData函數,“導出”表中的資料。注意,這裡導出加引号并不是真正的導出資料,而是用一個連結清單去存儲每一個需要導出的資料庫對象的基本資訊,到真正需要導出的時候再周遊這個連結清單依次做出對應的處理。這裡使用了占位的思想,不會占用大量的記憶體空間。
- 如果需要導出大對象,調用getBlobs,同上也是建立一個連結清單,并沒有真正去做導出。
- 根據步驟8 得到每個對象的依賴關系,調用getDependencies函數,重新整理對象間的依賴關系,調用sortDumpableObjects來決定各個資料庫對象導出的順序(不同類型的對象的導出優先級取決于newObjectTypePriority數組;相同類型的對象,按名稱排序)。
- 存儲編碼等資訊以及本連接配接對應的目的資料庫的資訊。
- 周遊所有對象,逐個“導出”對象(調用了dumpDumpableObject函數,本函數調用一堆諸如dumpNamespace、dumpTable等對象)。如果是“導出”表,則根據“導出”對象的資訊,查詢系統表,查閱到每個表對應的列資訊,生成表對象對應的SQL語句,輸出SQL語句到g_fout;如果是“導出”表資料,則調用dumpTableData,有兩種方式選擇,一是生成Insert語句,預設的是生成PostgreSQL自身的copy語句。這裡不再具體去介紹。
- 在“導出”每一個對象時,通常都會調用ArchiveEntry,做真正的SQL語句生成工作。另外,還會調用dumpComment、dumpSecLabel、dumpACL等函數,“導出”本對象對應的一些諸如注釋、權限等相關資訊。
- 調用RestoreArchive函數,真正的導出資料,注意這裡是根據不同的導出檔案格式來選擇不同的RestoreArchive函數。
- 關閉句柄釋放資源等。
不同格式的處理函數
我們簡單分析下目前支援的四種導出格式以及如何實作不同導出格式對應不同處理函數。目前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
當然,除了上面的分析外,還有很多其它詳細的内容需要具體分析,比如不同版本的資料庫操作方式、版本相容性的問題、對象權限如何處理、限制關系如何處理等。 這些問題都是值得大家去具體分析的,這裡不再詳細展開。