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