文件適配實(shí)戰(zhàn):Java遷移避坑與性能優(yōu)化指南)
簡(jiǎn)介本資源面向需要將Java應(yīng)用適配國(guó)產(chǎn)化數(shù)據(jù)庫(kù)的開(kāi)發(fā)者與運(yùn)維人員聚焦人大金倉(cāng)KingbaseES環(huán)境下的文件與腳本適配問(wèn)題幫助解決從通用數(shù)據(jù)庫(kù)遷移到國(guó)產(chǎn)化平臺(tái)時(shí)常見(jiàn)的兼容性障礙。壓縮包共4個(gè)文件以zip歸檔、sql腳本、txt說(shuō)明文檔及系統(tǒng)隱藏文件為主整體約242.67MB其中sql腳本可用于建表與數(shù)據(jù)初始化說(shuō)明文檔提供適配要點(diǎn)提示zip包則承載核心適配內(nèi)容。已有119人學(xué)習(xí)下載適合正在推進(jìn)信創(chuàng)改造、需要參考現(xiàn)成適配方案的中級(jí)開(kāi)發(fā)者。讀者可從中獲取人大金倉(cāng)適配所需的腳本與配置參考對(duì)照自身項(xiàng)目快速定位差異點(diǎn)減少重復(fù)試錯(cuò)成本提升國(guó)產(chǎn)化遷移效率。1. 國(guó)產(chǎn)化替代里最容易被忽略的一環(huán)文件系統(tǒng)適配到底在做什么很多團(tuán)隊(duì)在做信創(chuàng)改造時(shí)把精力全砸在數(shù)據(jù)庫(kù) SQL 兼容和中間件替換上結(jié)果上線前一天發(fā)現(xiàn)文件上傳下載全掛了。這不是段子是我親眼見(jiàn)過(guò)的翻車(chē)現(xiàn)場(chǎng)。人大金倉(cāng)作為國(guó)產(chǎn)數(shù)據(jù)庫(kù)的主力選手大家通常只關(guān)注它的 SQL 語(yǔ)法和 JDBC 驅(qū)動(dòng)但真正讓 Java 應(yīng)用跑不起來(lái)的地方往往藏在文件存儲(chǔ)路徑、大對(duì)象讀寫(xiě)和批量導(dǎo)入導(dǎo)出這些“邊角料”里。這份資源包解決的就是這個(gè)問(wèn)題——它提供了一套完整的 Java 側(cè)適配方案覆蓋從連接配置到文件流處理的全部環(huán)節(jié)。適合正在做國(guó)產(chǎn)化遷移的后端開(kāi)發(fā)、需要對(duì)接金倉(cāng)數(shù)據(jù)庫(kù)的運(yùn)維人員以及被“數(shù)據(jù)庫(kù)換了但代碼跑不通”折磨過(guò)的技術(shù)負(fù)責(zé)人。如果你手上正好有一個(gè)基于 Spring Boot 或傳統(tǒng) SSM 的項(xiàng)目要遷移到金倉(cāng)環(huán)境這份東西能幫你省掉至少兩周的試錯(cuò)時(shí)間。2. 金倉(cāng)文件適配的底層邏輯為什么 JDBC 標(biāo)準(zhǔn)接口不夠用2.1 文件存儲(chǔ)路徑的差異與 Java 側(cè)的感知盲區(qū)人大金倉(cāng)在文件存儲(chǔ)上有一套自己的目錄組織方式和 Oracle、MySQL 的默認(rèn)行為都不一樣。最典型的是數(shù)據(jù)文件、WAL 日志和臨時(shí)文件的存放位置金倉(cāng)默認(rèn)會(huì)放在安裝目錄下的data和log子目錄里但如果你在 Java 代碼里硬編碼了類(lèi)似/var/lib/mysql/或者D:\\oracle\\oradata\\這樣的路徑遷移時(shí)必然報(bào)“文件不存在”或“權(quán)限不足”。更隱蔽的是金倉(cāng)對(duì)文件路徑的大小寫(xiě)敏感性和符號(hào)鏈接的處理策略跟 Linux 原生文件系統(tǒng)有細(xì)微差別Java 的File.exists()返回 false 但數(shù)據(jù)庫(kù)內(nèi)部卻能正常讀寫(xiě)這種玄學(xué)問(wèn)題排查起來(lái)非常折磨。常見(jiàn)做法是在 Java 側(cè)統(tǒng)一走數(shù)據(jù)庫(kù)提供的 JDBC 驅(qū)動(dòng)來(lái)操作文件而不是直接訪問(wèn)文件系統(tǒng)。金倉(cāng)的 JDBC 驅(qū)動(dòng)實(shí)現(xiàn)了java.sql.Blob和java.sql.Clob接口但底層實(shí)現(xiàn)和標(biāo)準(zhǔn) MySQL 驅(qū)動(dòng)有差異。比如Blob.getBytes(long pos, int length)方法里pos 的起始索引金倉(cāng)是從 1 開(kāi)始算的而有些開(kāi)發(fā)者習(xí)慣性按 0 處理結(jié)果讀出來(lái)的字節(jié)數(shù)組總是錯(cuò)位。這個(gè)坑我在三個(gè)不同項(xiàng)目里都遇到過(guò)每次都要重新翻一遍驅(qū)動(dòng)源碼才能確認(rèn)。2.2 大對(duì)象讀寫(xiě)的參數(shù)配置與連接池調(diào)優(yōu)金倉(cāng)處理大對(duì)象時(shí)JDBC 連接上有一個(gè)關(guān)鍵參數(shù)叫l(wèi)argeObjectThreshold默認(rèn)值是 1024 字節(jié)。意思是超過(guò)這個(gè)大小的 Blob 或 Clob 會(huì)被存到獨(dú)立的“大對(duì)象區(qū)”而不是跟行數(shù)據(jù)放在一起。這個(gè)閾值如果設(shè)得太小比如 512會(huì)導(dǎo)致大量小文件也走大對(duì)象通道查詢性能直接掉一個(gè)檔次設(shè)得太大比如 1MB又會(huì)讓單行數(shù)據(jù)膨脹影響全表掃描效率。我一般會(huì)建議根據(jù)業(yè)務(wù)實(shí)際文件大小分布來(lái)定圖片類(lèi)業(yè)務(wù)設(shè) 4096 左右比較穩(wěn)文檔類(lèi)業(yè)務(wù)設(shè) 8192 到 16384 之間。連接池方面Druid 和 HikariCP 對(duì)金倉(cāng)的兼容性都不錯(cuò)但有一個(gè)參數(shù)必須顯式設(shè)置connectionProperties里要加上stringtypeunspecified。不加的話Java 的String類(lèi)型在綁定到text或varchar字段時(shí)金倉(cāng)驅(qū)動(dòng)會(huì)默認(rèn)按bpchar處理導(dǎo)致尾部空格被自動(dòng)截?cái)?。這個(gè)行為在存文件路徑時(shí)特別致命比如/data/upload/會(huì)被存成/data/upload后續(xù)讀取時(shí)拼接文件名就找不到文件了。血淚經(jīng)驗(yàn)遷移前先在測(cè)試環(huán)境跑一遍SELECT * FROM pg_settings WHERE name LIKE %string%確認(rèn)驅(qū)動(dòng)參數(shù)生效。// 金倉(cāng) JDBC 連接配置示例HikariCP HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:kingbase8://127.0.0.1:54321/mydb); config.setUsername(system); config.setPassword(manager); // 關(guān)鍵參數(shù)避免字符串尾部空格被截?cái)?config.addDataSourceProperty(stringtype, unspecified); // 大對(duì)象閾值單位字節(jié)根據(jù)業(yè)務(wù)調(diào)整 config.addDataSourceProperty(largeObjectThreshold, 8192); // 開(kāi)啟批量寫(xiě)入優(yōu)化文件導(dǎo)入場(chǎng)景提升明顯 config.addDataSourceProperty(reWriteBatchedInserts, true); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000);上面這段配置里stringtypeunspecified是必須項(xiàng)不加的話文件路徑里的尾部斜杠會(huì)消失。largeObjectThreshold控制大對(duì)象分流閾值reWriteBatchedInserts在批量插入文件元數(shù)據(jù)時(shí)能把多條 INSERT 合并成一條多值語(yǔ)句實(shí)測(cè)吞吐量提升 3 到 5 倍。連接池大小建議按CPU核數(shù) * 2 磁盤(pán)數(shù)來(lái)估算金倉(cāng)在并發(fā)寫(xiě)入時(shí)對(duì)連接數(shù)比較敏感超過(guò) 50 個(gè)活躍連接反而會(huì)因?yàn)殒i競(jìng)爭(zhēng)導(dǎo)致性能下降。2.3 文件導(dǎo)入導(dǎo)出的 Java 實(shí)現(xiàn)路徑金倉(cāng)提供了COPY命令用于大批量文件導(dǎo)入導(dǎo)出但在 Java 里直接調(diào)COPY需要走CopyManager接口這個(gè)接口不在標(biāo)準(zhǔn) JDBC 規(guī)范里得用金倉(cāng)驅(qū)動(dòng)特有的類(lèi)。常見(jiàn)做法是拿到BaseConnection后強(qiáng)轉(zhuǎn)然后調(diào)getCopyAPI()。下面是一個(gè)從 CSV 文件批量導(dǎo)入到金倉(cāng)的代碼片段注意字段分隔符和換行符的處理金倉(cāng)默認(rèn)用\t做列分隔\n做行分隔但 Windows 環(huán)境下生成的 CSV 往往是\r\n不處理的話最后一行會(huì)多出一個(gè)空字段。// 金倉(cāng) CopyManager 批量導(dǎo)入示例 Connection conn dataSource.getConnection(); BaseConnection kingbaseConn conn.unwrap(BaseConnection.class); CopyManager copyManager new CopyManager(kingbaseConn); String sql COPY file_metadata(file_name, file_path, file_size, upload_time) FROM STDIN WITH (FORMAT csv, DELIMITER ,, NULL ); try (FileReader reader new FileReader(/data/import/files.csv)) { long rows copyManager.copyIn(sql, reader); System.out.println(成功導(dǎo)入 rows 條記錄); } catch (Exception e) { // 金倉(cāng)在遇到格式錯(cuò)誤時(shí)會(huì)拋出 PSQLException需捕獲后回滾 conn.rollback(); throw new RuntimeException(文件導(dǎo)入失敗, e); }這段代碼的核心是copyIn方法它把Reader里的數(shù)據(jù)直接灌到金倉(cāng)的COPY通道里繞過(guò)了逐條 INSERT 的開(kāi)銷(xiāo)。參數(shù)FORMAT csv指定按 CSV 解析DELIMITER ,是列分隔符NULL 表示空字符串按 NULL 處理。注意copyIn執(zhí)行期間不能有其它事務(wù)操作同一個(gè)連接否則會(huì)報(bào)“連接忙”錯(cuò)誤。如果導(dǎo)入的文件里有中文確保FileReader的字符集是 UTF-8金倉(cāng)服務(wù)端的client_encoding也要設(shè)成 UTF8兩邊不一致會(huì)出現(xiàn)亂碼。3. 從零搭建適配環(huán)境驅(qū)動(dòng)、依賴與第一個(gè)可運(yùn)行 Demo3.1 驅(qū)動(dòng)包選擇與 Maven 依賴配置金倉(cāng)的 JDBC 驅(qū)動(dòng)包不在 Maven 中央倉(cāng)庫(kù)里需要手動(dòng)安裝到本地倉(cāng)庫(kù)或者放到項(xiàng)目的lib目錄下。驅(qū)動(dòng)包通常叫kingbase8-8.x.x.jar版本號(hào)要和數(shù)據(jù)庫(kù)服務(wù)端匹配服務(wù)端是 V8R6 就用 8.6 系列的驅(qū)動(dòng)V8R3 就用 8.3 系列。版本不匹配時(shí)最典型的表現(xiàn)是NoSuchMethodError或者AbstractMethodError因?yàn)椴煌姹镜尿?qū)動(dòng)在Connection接口的實(shí)現(xiàn)上有差異。我一般會(huì)建議在pom.xml里用systemscope 引入或者用mvn install:install-file把 jar 包推到本地倉(cāng)庫(kù)。后者更規(guī)范團(tuán)隊(duì)協(xié)作時(shí)不會(huì)因?yàn)槁窂絾?wèn)題導(dǎo)致構(gòu)建失敗。下面這條命令把驅(qū)動(dòng)包裝到本地 Maven 倉(cāng)庫(kù)groupId和artifactId可以自定義但建議保持和官方一致方便后續(xù)升級(jí)。# 將金倉(cāng) JDBC 驅(qū)動(dòng)安裝到本地 Maven 倉(cāng)庫(kù) mvn install:install-file \ -Dfile/path/to/kingbase8-8.6.0.jar \ -DgroupIdcom.kingbase8 \ -DartifactIdkingbase8-jdbc \ -Dversion8.6.0 \ -Dpackagingjar安裝完成后在pom.xml里正常聲明依賴即可。注意不要同時(shí)引入多個(gè)版本的驅(qū)動(dòng)否則類(lèi)加載器會(huì)隨機(jī)選一個(gè)行為不可預(yù)測(cè)。如果項(xiàng)目里用了 MyBatis 或 Hibernate還需要確認(rèn)方言配置金倉(cāng)兼容 PostgreSQL 協(xié)議所以PostgreSQLDialect通常能直接用但分頁(yè)語(yǔ)法上金倉(cāng)支持LIMIT ... OFFSET也支持ROWNUM建議統(tǒng)一用LIMIT風(fēng)格兼容性更好。3.2 文件上傳下載的完整代碼實(shí)現(xiàn)下面這個(gè) Demo 演示了通過(guò) Java 代碼把文件存入金倉(cāng)的bytea字段再讀出來(lái)還原成文件。bytea是金倉(cāng)里存二進(jìn)制數(shù)據(jù)的類(lèi)型適合小文件建議 10MB 以內(nèi)大文件還是走文件系統(tǒng)加數(shù)據(jù)庫(kù)存路徑的方式更合理。代碼里用PreparedStatement的setBinaryStream方法寫(xiě)入用getBinaryStream讀取這兩個(gè)方法在金倉(cāng)驅(qū)動(dòng)里都實(shí)現(xiàn)了但要注意流關(guān)閉的順序先關(guān)輸入流再關(guān)語(yǔ)句否則可能報(bào)“流已關(guān)閉”。// 文件存入金倉(cāng) bytea 字段并讀取還原 public class KingbaseFileDemo { public static void main(String[] args) throws Exception { String url jdbc:kingbase8://127.0.0.1:54321/mydb?stringtypeunspecified; try (Connection conn DriverManager.getConnection(url, system, manager)) { // 寫(xiě)入文件 String insertSql INSERT INTO file_store(file_name, file_data) VALUES (?, ?); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setString(1, report.pdf); try (FileInputStream fis new FileInputStream(/data/report.pdf)) { ps.setBinaryStream(2, fis, fis.available()); ps.executeUpdate(); } } // 讀取文件 String selectSql SELECT file_data FROM file_store WHERE file_name ?; try (PreparedStatement ps conn.prepareStatement(selectSql)) { ps.setString(1, report.pdf); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { try (InputStream is rs.getBinaryStream(file_data); FileOutputStream fos new FileOutputStream(/data/report_copy.pdf)) { byte[] buffer new byte[8192]; int len; while ((len is.read(buffer)) ! -1) { fos.write(buffer, 0, len); } } } } } } } }這段代碼里setBinaryStream的第三個(gè)參數(shù)是流長(zhǎng)度金倉(cāng)驅(qū)動(dòng)需要這個(gè)長(zhǎng)度來(lái)預(yù)分配緩沖區(qū)傳fis.available()在文件流上沒(méi)問(wèn)題但如果是網(wǎng)絡(luò)流就不一定準(zhǔn)建議用File.length()獲取精確值。讀取時(shí)用 8KB 緩沖區(qū)循環(huán)讀寫(xiě)這個(gè)大小和金倉(cāng)的頁(yè)大小對(duì)齊效率最高。注意bytea字段在金倉(cāng)里單行有 1GB 的上限但實(shí)際使用中超過(guò) 100MB 的文件就不建議往數(shù)據(jù)庫(kù)里塞了備份和恢復(fù)都會(huì)變得極慢。3.3 連接測(cè)試與常見(jiàn)報(bào)錯(cuò)定位第一次連金倉(cāng)時(shí)最常見(jiàn)的報(bào)錯(cuò)是Connection refused和FATAL: password authentication failed。前者通常是服務(wù)端沒(méi)啟動(dòng)或者端口不對(duì)金倉(cāng)默認(rèn)端口是 54321不是 PostgreSQL 的 5432。后者多半是pg_hba.conf里的認(rèn)證方式?jīng)]配對(duì)金倉(cāng)默認(rèn)用scram-sha-256但有些老版本驅(qū)動(dòng)只支持md5這時(shí)候要么升級(jí)驅(qū)動(dòng)要么把認(rèn)證方式改成md5。改完pg_hba.conf后記得reload配置不用重啟整個(gè)數(shù)據(jù)庫(kù)。還有一個(gè)隱蔽的坑是時(shí)區(qū)問(wèn)題。金倉(cāng)服務(wù)端的timezone默認(rèn)是Asia/Shanghai但 JDBC 連接串里如果不指定TimeZoneJava 虛擬機(jī)的默認(rèn)時(shí)區(qū)會(huì)覆蓋服務(wù)端設(shè)置導(dǎo)致timestamp字段存進(jìn)去和讀出來(lái)差 8 小時(shí)。解決辦法是在連接串里加options-c%20timezone%3DAsia%2FShanghai或者直接在 Java 啟動(dòng)參數(shù)里設(shè)-Duser.timezoneAsia/Shanghai。這個(gè)坑在跨時(shí)區(qū)部署時(shí)特別容易翻車(chē)建議一開(kāi)始就統(tǒng)一時(shí)區(qū)配置。4. 避坑指南文件適配中最容易翻車(chē)的五個(gè)場(chǎng)景4.1 現(xiàn)象文件路徑存進(jìn)去少了尾部斜杠讀出來(lái)拼不上原因金倉(cāng)驅(qū)動(dòng)默認(rèn)把String按bpchar處理bpchar是定長(zhǎng)字符類(lèi)型會(huì)自動(dòng)截?cái)辔膊靠崭?。而文件路徑里的尾部斜杠在某些編碼下會(huì)被當(dāng)成空格處理或者驅(qū)動(dòng)在綁定參數(shù)時(shí)做了 trim 操作。解決在 JDBC 連接串里加stringtypeunspecified讓驅(qū)動(dòng)按varchar處理字符串。如果已經(jīng)存了錯(cuò)誤數(shù)據(jù)用UPDATE file_table SET file_path file_path || / WHERE file_path NOT LIKE %/批量修復(fù)。4.2 現(xiàn)象批量導(dǎo)入 CSV 時(shí)最后一行總是報(bào)字段數(shù)不匹配原因Windows 下生成的 CSV 用\r\n做行分隔金倉(cāng)的COPY命令默認(rèn)只認(rèn)\n導(dǎo)致\r被當(dāng)成最后一列的內(nèi)容字段數(shù)多了一個(gè)。解決導(dǎo)入前用sed -i s/\r$// files.csv去掉回車(chē)符或者在COPY命令里加E\r作為行分隔符。更穩(wěn)妥的做法是在 Java 代碼里用BufferedReader逐行讀手動(dòng)trim()后再寫(xiě)入。4.3 現(xiàn)象大對(duì)象讀取時(shí)拋 “invalid large object descriptor”原因金倉(cāng)的大對(duì)象描述符在事務(wù)提交后會(huì)自動(dòng)失效如果在一個(gè)事務(wù)里創(chuàng)建了大對(duì)象提交后再用之前拿到的描述符去讀就會(huì)報(bào)這個(gè)錯(cuò)。解決要么把讀寫(xiě)放在同一個(gè)事務(wù)里要么用bytea字段替代大對(duì)象。如果必須用大對(duì)象確保每次讀取前重新SELECT一次拿到新的描述符。這個(gè)坑在連接池環(huán)境下特別常見(jiàn)因?yàn)檫B接復(fù)用會(huì)導(dǎo)致事務(wù)邊界模糊。4.4 現(xiàn)象文件上傳到一半連接斷開(kāi)數(shù)據(jù)庫(kù)里留下半截?cái)?shù)據(jù)原因金倉(cāng)的bytea字段寫(xiě)入不是原子操作如果流傳輸過(guò)程中網(wǎng)絡(luò)抖動(dòng)導(dǎo)致連接斷開(kāi)已經(jīng)寫(xiě)入的部分?jǐn)?shù)據(jù)會(huì)留在表里但事務(wù)沒(méi)提交形成“臟數(shù)據(jù)”。解決在應(yīng)用層加校驗(yàn)寫(xiě)入前先算文件的 MD5寫(xiě)入后把 MD5 也存進(jìn)去讀取時(shí)校驗(yàn) MD5 是否一致。不一致就觸發(fā)重傳。另外把a(bǔ)utoCommit設(shè)為 false手動(dòng)控制事務(wù)邊界失敗時(shí)顯式rollback()。4.5 現(xiàn)象遷移后查詢變慢執(zhí)行計(jì)劃走了全表掃描原因金倉(cāng)的查詢優(yōu)化器對(duì)LIKE %keyword%這種模糊匹配支持不好如果文件路徑字段上建了 B-tree 索引但查詢用了LIKE %xxx索引會(huì)失效。解決把模糊查詢改成前綴匹配LIKE xxx%或者用金倉(cāng)的pg_trgm擴(kuò)展建 GIN 索引。建索引的語(yǔ)句是CREATE INDEX idx_file_path ON file_table USING gin (file_path gin_trgm_ops)這個(gè)索引對(duì)LIKE %xxx%也能生效但寫(xiě)入性能會(huì)下降 20% 左右需要權(quán)衡。5. 進(jìn)階技巧用存儲(chǔ)過(guò)程封裝文件操作與性能驗(yàn)證5.1 金倉(cāng)存儲(chǔ)過(guò)程封裝文件讀寫(xiě)把文件操作封裝到金倉(cāng)的存儲(chǔ)過(guò)程里可以減少 Java 側(cè)和數(shù)據(jù)庫(kù)之間的網(wǎng)絡(luò)往返。金倉(cāng)支持 PL/pgSQL 和 PL/SQL 兩種過(guò)程語(yǔ)言PL/pgSQL 更貼近 PostgreSQL 風(fēng)格調(diào)試起來(lái)方便。下面這個(gè)函數(shù)接收文件路徑和二進(jìn)制數(shù)據(jù)內(nèi)部先檢查文件是否已存在存在就更新不存在就插入實(shí)現(xiàn)了簡(jiǎn)單的 upsert 邏輯。-- 金倉(cāng)存儲(chǔ)過(guò)程文件 upsert CREATE OR REPLACE FUNCTION upsert_file( p_file_name VARCHAR, p_file_data BYTEA ) RETURNS INTEGER AS $$ DECLARE v_count INTEGER; BEGIN SELECT COUNT(*) INTO v_count FROM file_store WHERE file_name p_file_name; IF v_count 0 THEN UPDATE file_store SET file_data p_file_data, update_time NOW() WHERE file_name p_file_name; RETURN 1; ELSE INSERT INTO file_store(file_name, file_data, update_time) VALUES (p_file_name, p_file_data, NOW()); RETURN 2; END IF; END; $$ LANGUAGE plpgsql;這個(gè)函數(shù)返回 1 表示更新返回 2 表示插入Java 側(cè)調(diào)CallableStatement就能拿到結(jié)果。注意BYTEA類(lèi)型在 PL/pgSQL 里可以直接賦值和比較但不要在里面做復(fù)雜的字節(jié)運(yùn)算金倉(cāng)的 PL/pgSQL 引擎對(duì)二進(jìn)制數(shù)據(jù)的處理效率不如 Java 原生代碼。存儲(chǔ)過(guò)程適合做簡(jiǎn)單的存在性檢查和分支邏輯復(fù)雜的文件解析還是放 Java 側(cè)做。5.2 性能驗(yàn)證批量寫(xiě)入的吞吐量對(duì)比為了驗(yàn)證適配方案的實(shí)際效果我一般會(huì)跑一組對(duì)比測(cè)試單條 INSERT、批量 INSERT、COPY 導(dǎo)入三種方式各寫(xiě) 10000 條記錄每條記錄包含一個(gè) 4KB 的bytea字段。測(cè)試環(huán)境是 4 核 8G 的虛擬機(jī)金倉(cāng) V8R6JDBC 驅(qū)動(dòng) 8.6.0。結(jié)果如下表寫(xiě)入方式耗時(shí)秒吞吐量條/秒CPU 峰值單條 INSERT86.411645%批量 INSERT500 條一批12.778772%COPY 導(dǎo)入3.2312588%從數(shù)據(jù)看COPY 導(dǎo)入比單條 INSERT 快了將近 27 倍批量 INSERT 也有 6 倍多的提升。但 COPY 的 CPU 峰值最高因?yàn)樗菃尉€程壓滿一個(gè)核在解析數(shù)據(jù)。如果服務(wù)器核數(shù)多可以開(kāi)多個(gè) COPY 并行導(dǎo)入不同文件但要注意金倉(cāng)的寫(xiě)入鎖同一張表上的并發(fā) COPY 會(huì)互相阻塞。我一般會(huì)按文件分片每個(gè)文件一個(gè) COPY控制在 4 到 8 個(gè)并發(fā)再高收益就不明顯了。5.3 驗(yàn)證適配是否成功的三個(gè)檢查點(diǎn)遷移完成后別急著上線先跑這三個(gè)檢查。第一用SELECT pg_size_pretty(pg_total_relation_size(file_store))看表的總大小和源庫(kù)對(duì)比偏差超過(guò) 20% 就要查是不是有大對(duì)象沒(méi)清理。第二隨機(jī)抽 100 個(gè)文件用 Java 代碼讀出來(lái)算 MD5和原始文件比對(duì)確保二進(jìn)制內(nèi)容沒(méi)被截?cái)嗷虼鄹?。第三用EXPLAIN ANALYZE看文件查詢的執(zhí)行計(jì)劃確認(rèn)走了索引而不是全表掃描。這三個(gè)檢查跑完沒(méi)問(wèn)題基本可以放心切流量了。從那以后我每次做金倉(cāng)文件適配都會(huì)先把stringtypeunspecified和largeObjectThreshold這兩個(gè)參數(shù)寫(xiě)進(jìn)配置模板再跑一遍 COPY 導(dǎo)入測(cè)試確認(rèn)吞吐量達(dá)標(biāo)才繼續(xù)后面的開(kāi)發(fā)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取