指南)
Caché 和 IRIS 數(shù)據(jù)庫自帶的那個終端Terminal用起來什么都好唯一的痛點是中文。你在里面敲一句WRITE 中文測試屏幕上出來的經(jīng)常是涓?嫻嬭瘯錕斤拷或者一排問號。更離譜的是同一個腳本在 A 機器上正常在 B 機器上就亂碼排查半天找不到原因。這篇文章就把我這些年處理 Caché/IRIS 終端亂碼的思路、方法和踩過的坑一次講清楚適合剛從 Windows Terminal 入門、或者天天在 Linux 服務器上用csession/iris session干活的朋友。先說明一點絕大多數(shù)亂碼問題數(shù)據(jù)本身在數(shù)據(jù)庫里是好的壞在顯示鏈路。Caché/IRIS 的字符串在內(nèi)存中以 Unicode 形式管理當你執(zhí)行WRITE時系統(tǒng)要把字符編碼成字節(jié)流交給終端程序終端再按自己的編碼設(shè)置把字節(jié)翻譯成屏幕上的字形。這一路上只要有一個環(huán)節(jié)的編碼不統(tǒng)一中文就會花掉。所以解決方案不是去改數(shù)據(jù)而是把整條鏈路的編碼對齊。下面我按使用場景逐個拆。1. 亂碼到底卡在哪一環(huán)1.1 一條中文 WRITE 指令的完整旅程先理解一下亂碼的完整鏈路。假設(shè)你在 Caché 終端里輸入WRITE 中文測試這條命令的執(zhí)行過程其實分三步。第一步終端程序把中文測試這幾個字符從鍵盤輸入編碼轉(zhuǎn)成內(nèi)存中的 Unicode第二步數(shù)據(jù)庫進程執(zhí)行WRITE把 Unicode 字符串按某個編碼轉(zhuǎn)成字節(jié)第三步終端程序收到這些字節(jié)后按自己的顯示編碼去解碼并渲染到屏幕。只要第二步和第三步的編碼不一致亂碼就出現(xiàn)了。舉個例子如果數(shù)據(jù)庫進程按 UTF-8 輸出中文兩個字得到的是E4 B8 AD E6 96 87這組字節(jié)而終端程序恰好用的是 GBK 代碼頁來解碼它會把E4 B8當成一個 GBK 字符涓把AD E6當成下一個字符最終顯示出來就是涓?囦之類的古怪內(nèi)容。要是終端用西歐編碼Latin-1解碼看到的就會是???–?這種字母串。我看到很多新手一遇到亂碼就懷疑數(shù)據(jù)庫裝壞了或者全局變量里的數(shù)據(jù)丟了于是急著去重裝、導數(shù)據(jù)。其實只要你在WRITE中文之前先執(zhí)行一句WRITE $ASCII(中,1)看到返回的是正常數(shù)字比如某個 Unicode 碼值就說明數(shù)據(jù)在內(nèi)存里一點問題沒有。問題只出在輸出編碼和終端解碼這一層。1.2 三層編碼鏈路決定了你最終看到什么把亂碼問題抽象一下其實就三條鏈路你只需要逐條排查數(shù)據(jù)庫進程對外輸出時的編碼。Caché/IRIS 在把字符串交給終端時用的編碼由 NLSNational Language Support配置決定安裝時選了中文環(huán)境、GBK 還是 UTF-8差異很大。終端程序自身的解碼方式。Windows 圖形終端有 Encoding 選項命令行終端csession/cterm則依賴控制臺代碼頁Linux 下csession依賴父進程的 locale。會話工具SSH 客戶端、Windows Terminal的編碼設(shè)置。例如 SecureCRT、Xshell、PuTTY 都有獨立的字符編碼選項它們夾在中間最容易被人忽略。后面所有的解法本質(zhì)上都是讓這三條鏈路統(tǒng)一到同一種編碼上。我的實踐經(jīng)驗是跨平臺環(huán)境優(yōu)先統(tǒng)一到 UTF-8純 Windows 內(nèi)網(wǎng)環(huán)境統(tǒng)一到 GBKCP936也行但要注意外部數(shù)據(jù)源的編碼千差萬別UTF-8 兼容性最好。2. 分場景解決圖形終端、命令行終端、SSH 終端2.1 Windows 圖形終端Terminal / IRIS Terminal的設(shè)置Caché 和 IRIS 自帶的圖形終端設(shè)置入口都在菜單里。以 Caché 5.x 為例打開終端后點擊Options - Terminal PreferencesIRIS 新版的路徑類似通常在Edit - Preferences或工具欄的齒輪圖標里。進去之后找到Encoding有的版本叫 Character Set下拉框把默認值改成UTF-8保存后重啟終端。設(shè)置完不要急著高興還有一個經(jīng)常被忽略的點字體。終端渲染中文如果當前字體不支持中文字形會顯示成方框□。建議把字體設(shè)置為等寬且支持中文的字體比如 Consolas、YaHei Consolas Hybrid或者直接選微軟雅黑。這一步不進編碼設(shè)置但往往決定最終顯示效果。如果你用的是 IRIS 的圖形終端還可以在啟動時直接指定命名空間和編碼避免每次登錄后手動ZN切換。創(chuàng)建快捷方式時修改啟動命令追加參數(shù)。以cyTerm/irisTerminal這類程序為例不同版本的參數(shù)名略有差異最穩(wěn)妥的辦法是在安裝目錄下執(zhí)行cterm.exe -?或iris terminal -?查看幫助找出當前版本支持的編碼參數(shù)。不要輕信網(wǎng)上寫死的參數(shù)版本不同經(jīng)常變。我建議把常用的命名空間和編碼參數(shù)直接寫進快捷方式例如目標填C:\InterSystems\IRIS\bin\cterm.exe -UUSER編碼參數(shù)按你本版幫助為準這樣雙擊圖標就直達環(huán)境少一層出錯概率。2.2 用 csession/cterm 進入時的代碼頁適配在 Windows 的命令行里跑csession或者cterm情況比圖形終端復雜一些因為這里多了 Windows 控制臺代碼頁這一層。代碼頁是 Windows 控制臺用來解釋字節(jié)的規(guī)則默認跟著系統(tǒng)區(qū)域走中文系統(tǒng)通常是 936GBK。如果你希望整條鏈路用 UTF-8最簡單的方式是在啟動csession之前先執(zhí)行chcp 65001 csession CACHEchcp 65001把控制臺代碼頁切到 UTF-8之后的終端輸出就會按 UTF-8 解碼。但這里有個坑早期版本的 Caché 在 65001 代碼頁下會有顯示錯亂比如光標亂跳、輸出重疊。如果你遇到這個問題不要硬剛我后面的做法是保持代碼頁 936然后把數(shù)據(jù)庫側(cè)的 NLS 和輸出統(tǒng)一成 GBK或者換用 Windows Terminal 作為宿主它對 UTF-8 的支持比傳統(tǒng) cmd 好得多實測基本不出現(xiàn)舊版終端那種兼容性問題。提到 Windows Terminal 多說一句它不是 Caché 的終端程序而是替代 cmd/PowerShell 的終端宿主。在 Windows Terminal 里啟動csession一方面是界面更好看更重要的是它對 UTF-8、字體渲染的支持都遠超老控制臺。如果公司允許你裝新軟件我強烈推薦直接用 Windows Terminal csession亂碼率能降一大半。2.3 SSH 登錄 Linux 服務器時的編碼對齊Linux 服務器上你通常用csession 實例名或iris session 實例名進入終端。這種情況下亂碼往往不是數(shù)據(jù)庫的問題而是 SSH 會話的編碼沒對齊。排查順序我固定是這樣的第一步看服務器端 locale。執(zhí)行echo $LANG如果輸出不是*.UTF-8比如是POSIX或en_US那么csession輸出的中文字節(jié)可能會被截斷或轉(zhuǎn)成?。解決辦法是把 locale 改成 UTF-8 再啟動會話export LANGen_US.UTF-8第二步看 SSH 客戶端的編碼。Xshell 在會話屬性 - 終端 - 編碼里選 UTF-8SecureCRT 在會話選項 - 外觀 - 字符編碼里選 UTF-8PuTTY 在Window - Translation - Remote character set里選 UTF-8。每個工具的位置不一樣但記住一點客戶端編碼要和服務器端 locale 一致一般都選 UTF-8。SecureCRT 有個特別坑的默認行為它有個自動選擇編碼的選項會根據(jù)遠程服務器的 locale 自動判斷。聽起來很方便實際經(jīng)常判斷失誤。比如服務器 locale 是 C非 UTF-8但數(shù)據(jù)庫 NLS 輸出 UTF-8SecureCRT 就按非 UTF-8 解碼中文必亂。我建議直接把編碼硬性指定為 UTF-8別用自動模式。這一步做完絕大多數(shù) SSH 場景的中文亂碼都能解決。如果還亂那就是 NLS 配置層的差異見下一節(jié)。3. 數(shù)據(jù)庫側(cè)的兜底方案NLS 配置與腳本編碼轉(zhuǎn)換3.1 NLS國家語言支持究竟是什么怎么改NLS 是 Caché/IRIS 里決定數(shù)據(jù)庫以什么編碼和終端對話的一套配置。你可以把它理解成一個翻譯官既管輸出編碼也管默認字符排序規(guī)則。同一個實例NLS 的默認 Table 不同WRITE 中文送出去的字節(jié)流可能完全不同。進入 NLS 配置的方法是在終端里執(zhí)行D ^%NLS回車后會彈出一個 NLS 管理菜單。不同版本菜單結(jié)構(gòu)略有差別核心選項一般包括查看當前默認 Table、設(shè)置當前進程的 Table、保存到系統(tǒng)全局。你可以先看看當前默認編碼是什么。如果顯示是GB18030、GBK一類而你終端側(cè)已經(jīng)切到 UTF-8那么兩者就撞車了。要修改系統(tǒng)級默認通常在菜單里選擇保存配置的選項把當前設(shè)置寫入^SYS(NLS)對應的全局節(jié)點。保存后新建的終端會話就會用新的默認值。這里我要重點提醒修改 NLS 的系統(tǒng)級默認會影響這個實例處理外部文件、HTTP 響應、全局變量排序等多個環(huán)節(jié)的默認編碼不是只影響終端顯示。在生產(chǎn)環(huán)境改之前一定要先在測試實例驗證確認對現(xiàn)有業(yè)務無影響再動生產(chǎn)。如果你的服務器是 Windows 且安裝時選了中文區(qū)域默認 NLS Table 通常是 GBK 系列Linux 安裝時如果系統(tǒng) locale 是 UTF-8默認 NLS Table 通常是 UTF-8 系列。所以同樣一段帶中文的代碼在兩臺機器上輸出結(jié)果可能完全不同這就是同一腳本 A 機正常 B 機亂的根源之一。了解了這個原理你就知道該怎么對癥下藥了。3.2 用 $ZCONVERT 在腳本里做編碼轉(zhuǎn)換有些場景下終端和 NLS 都調(diào)好了還是有亂碼那多半是腳本里直接輸出了字節(jié)流。典型情況是從文件讀出一段 UTF-8 編碼的文本、通過 HTTP 請求拿回一段 GBK 編碼的響應體然后你直接WRITE把它甩到屏幕上。這時候數(shù)據(jù)庫并不知道這些字節(jié)代表什么編碼只管按 NLS 默認方式轉(zhuǎn)成 Unicode結(jié)果自然亂。這種情況下正確的姿勢是先用$ZCONVERT手動轉(zhuǎn)換編碼再輸出。$ZCONVERT是 ObjectScript 里用于編碼轉(zhuǎn)換的核心函數(shù)基本思路是把外部字節(jié)串顯式聲明成它的實際編碼轉(zhuǎn)成 Unicode再按終端目標編碼輸出。舉個我實際處理過的例子。某個接口返回的 JSON 文件是 UTF-8 編碼早期在 Windows 圖形終端終端設(shè)置 GBK下顯示時中文全是亂碼。我在讀取該文件后加了這樣一步SET jsonBytes ... ; 從文件或 HTTP 響應拿到的 UTF-8 字節(jié)串 SET unicodeText $ZCONVERT(jsonBytes, UTF8) WRITE unicodeText這樣unicodeText是內(nèi)存里的 Unicode 字符串終端負責按自己的編碼渲染亂碼就消失了。反過來的情況也常見你的 NLS 是 UTF-8但外部程序給你的數(shù)據(jù)是 GBK同樣用$ZCONVERT轉(zhuǎn)成 Unicode 再處理。補充一個細節(jié)$ZCONVERT有三種常見調(diào)用形式一種是只傳目標編碼做單向轉(zhuǎn)換一種是傳FromEncoding和ToEncoding做雙語互轉(zhuǎn)。具體到不同版本參數(shù)的語義細節(jié)有差異建議在出問題前先在自己的實例上拿一小段中文用$ZCONVERT來回轉(zhuǎn)幾遍看哪種寫法符合你的版本??傊悸肥墙y(tǒng)一的不要在腳本里裸奔字節(jié)流明確編碼再輸出。3.3 讓配置對新終端會話自動生效有時候配置改好了但每次新開終端還是亂碼這通常是因為你沒有把配置保存到持久化位置。圖形終端里改Terminal Preferences只改了當前用戶的終端外觀設(shè)置而D ^%NLS里改的默認 Table如果沒有保存也只對當前進程生效。如果你希望每次登錄都自動套用一套確定的編碼可以在登錄后的啟動腳本里顯式設(shè)置。Caché/IRIS 支持在USER命名空間或%SYS的登錄流程里掛初始化代碼比如在^%SYS(STARTUP)或用戶自定義的啟動代碼塊中設(shè)置當前進程的 NLS 編碼。這個方法的好處是不管你是用圖形終端、csession還是iris session登錄只要執(zhí)行了啟動代碼編碼就自動對齊。我個人更推薦的做法是在%SYS命名空間下寫好一個測試函數(shù)專門輸出當前進程的 NLS 狀態(tài)例如執(zhí)行D ^%NLS去確認默認 Table再執(zhí)行WRITE 中文驗證顯示。這樣每次接手新環(huán)境先把腳本跑一遍五分鐘內(nèi)就能判斷是哪一層編碼沒對齊而不是靠肉眼猜。4. 實戰(zhàn)排查清單與避坑心得4.1 一眼識別亂碼類型亂碼其實是可以看特征定原因的。我在下面整理了一張對照表按亂碼的樣子快速定位問題層。屏幕上顯示的亂碼特征大概率原因檢查方向大串問號?????當前編碼不支持該字符字符映射失敗NLS Table 是否包含中文字符集涓??娑?璇? 這類形似中文但語義全錯的字UTF-8 字節(jié)被 GBK/GB18030 解碼終端代碼頁或 SSH 客戶端編碼不是 UTF-8???–? 這種帶小語種字母的串UTF-8 字節(jié)被 Latin-1/西歐編碼解碼SSH 客戶端誤選了西歐編碼錕斤拷編碼被反復轉(zhuǎn)碼污染UTF-8→GBK→UTF-8數(shù)據(jù)文件或接口曾經(jīng)被錯誤轉(zhuǎn)換后入庫替換符GBK 或非 UTF-8 字節(jié)被 UTF-8 解碼終端設(shè)置為 UTF-8但數(shù)據(jù)實際是 GBK這張表的核心邏輯是先看亂碼像不像中文如果像 涓這種生僻中文字基本是 UTF-8 被 GBK 解如果像 ??這種小語種字符基本是 UTF-8 被西歐解如果直接是 ?說明字符集缺字形。方向判斷對了再決定調(diào)哪一層。4.2 一套可以復用的排查順序遇到亂碼我建議按下面這個順序走而不是隨機改設(shè)置先確認數(shù)據(jù)字節(jié)本身的編碼。如果你有問題的文本來自文件或外部接口一定要先明確它是 UTF-8 還是 GBK。最簡單的辦法是把這段文本導出到文件用十六進制工具比如 Notepad 的 HEX 插件、或者xxd命令看字節(jié)UTF-8 編碼的中文一般以E4~E9開頭的三字節(jié)序列居多GBK 中文一般是兩個字節(jié)首字節(jié)范圍B0~F7、次字節(jié)A1~FE。這一步能避免后面瞎調(diào)。然后對齊終端編碼。確認你的終端或 SSH 客戶端當前用的編碼和上面判斷出的數(shù)據(jù)編碼一致。Windows 圖形終端看 Encoding 選項SSH 客戶端看字符集選項Linux 本地csession看LANG。最后驗證 NLS。在終端里執(zhí)行D ^%NLS確認當前默認 Table 和你希望的輸出編碼一致。如果全部對齊了WRITE 中文測試應該馬上正常。驗證時有個順帶的技巧執(zhí)行Do $SYSTEM.License.ShowSummary()這個命令會輸出許可證信息含中文說明的部分也能反映出會話編碼是否正確。首次跑完還是亂的話就用它來輔助判斷是哪個環(huán)節(jié)沒到位順便把許可證信息也確認一遍。這個命令是日常排查后非常快速的驗證工具。4.3 我踩過的幾個終端亂碼坑第一個坑是改了終端編碼卻忘了重啟。圖形的 Terminal Preferences 有些設(shè)置是啟動時讀取的修改后不重啟不會生效。我不是第一次遇到調(diào)了半天才發(fā)現(xiàn)還是老進程。第二個坑是Linux 服務器的 locale 影響全局。csession是在 bash 進程里啟動的子進程csession默認會繼承父進程的 locale 相關(guān)環(huán)境變量。如果你在登錄服務器之后手動 export 了非 UTF-8 的 LANG或者用 systemd 服務方式啟動會話locale 可能不是你預期的那樣。排查時記得先echo $LANG。第三個坑是SecureCRT 的自動編碼判斷。它默認的自動經(jīng)常幫倒忙尤其跨服務器跳板的時候會把 UTF-8 判斷成非 UTF-8。后來我全部改成顯式指定 UTF-8亂碼率大幅下降。第四個坑比較隱蔽全局變量里存了已經(jīng)轉(zhuǎn)過的字節(jié)流。比如你用$ZCONVERT把 UTF-8 字節(jié)存進全局然后又用$ZCONVERT轉(zhuǎn)回 Unicode轉(zhuǎn)來轉(zhuǎn)去數(shù)據(jù)本身已經(jīng)臟了這時候調(diào)終端怎么都沒用。遇到這種怎么調(diào)都亂的情況要回到數(shù)據(jù)源頭重新讀取原始字節(jié)再走一遍干凈的轉(zhuǎn)換流程。第五個坑是命令版本差異。cterm.exe -?和iris terminal -?打印出來的參數(shù)表在不同小版本之間會有變化網(wǎng)上搜到的老參數(shù)可能在新版已廢棄。最可靠的辦法永遠是查本機幫助不要盲抄。處理了這么多亂碼之后我個人的體會是90% 的終端亂碼根本不用動數(shù)據(jù)庫問題都在 SSH 客戶端、終端編碼和 locale 這三層上。做對一件事就夠了——先判斷數(shù)據(jù)是什么編碼再看終端在用什么編碼解碼然后層層對齊不要憑感覺瞎設(shè)置。真正需要動 NLS 的場景其實很少一旦動就要謹慎評估生產(chǎn)影響。最后再分享一個小技巧每接手一臺新服務器我會把echo $LANG、D ^%NLS、WRITE 中文這三條命令按順序執(zhí)行一遍錄成一段固定的驗證流程。幾分鐘下來當前環(huán)境的編碼底線就摸清了。以后再遇到亂碼直接按這張已驗證的基線去比對定位速度能快很多。