一次倍受折磨的“invalid byte sequence for encoding "UTF8": 0x00”事件的經驗教訓


一、概述

invalid byte sequence for encoding "UTF8": 0x00(注意:若不是0x00則很可能是字符集設置有誤),是PostgreSQL獨有的錯誤信息,直接原因是varchar型的字段或變量不接受含有'\0'(也即數值0x00、UTF編碼'\u0000')的字符串 。官方給出的解決方法:事先去掉字符串中的'\0',例如在Java代碼中使用str.replaceAll('\u0000', ''),貌似這是目前唯一可行的方法。

幾天前,項目的一個程序就出現這種錯誤,該程序是將一批特殊格式的文件導入到數據庫的若干張表中。雖然已知道用replaceAll('\u0000', '')可解決問題,但由於要插入多張表、每個表含多個varchar字段、插入操作由JPA實現、插入操作要批量進行等因素,程序日志內容太籠統,為判斷是哪個(或哪些)表、字段造成的、以及是代碼原因還是數據原因提供的幫助很少,因而過程中麻煩多多困難重重,現在將其中的經驗與教訓總結出來,希望對同行們有所幫助。

二、經驗1:從PostgreSQl的運行日志中定位表

一開始用普通方法,即通過在應用程序代碼里加斷點來跟蹤執行情況,但在本例中,一旦跟蹤到JPA持久化時就無法繼續下去。而由於數據內容很多,用人工一一去檢查費時費力,因而走了很多彎路。

后來,通過修改PostgreSQL配置文件,在運行日志(不是WAL和提交日志)中輸出SQL語句執行情況,可以准確定位到哪個表會引發錯誤。具體方法是:

  • 修改配置文件postgresql.conf,通常在$pgdata目錄下,本例中是在D:\PostgreSQL\data\pg94目錄;
  • 找到“where to log”塊,將logging_collector設置為on,這意味着開啟運行日志,所在目錄由log_directory參數指定;
  • 找到“when to log”塊,將log_statement設置為mod或all,這意味着sql語句被記錄到運行日志;
  • 仍在“when to log”塊,確保log_min_error_statement為error或更低級別,以記錄錯誤信息;因缺省值已是error,一般無須修改;
  • 仍在“when to log”塊,確保log_min_message為info或更低級別,這樣成功執行的sql語句所綁定的變量也能查到(可選);
  • 重啟PostgreSQL,執行那個導入程序,此時運行日志已記錄下執行的sql語句情況,根據報錯信息即可具體定位是哪個表引起。

三、經驗2:在程序代碼中輸出字符串內容

本來到這階段已經相當接近成功了,但還是在此犯了錯誤:過於依賴頁面所顯示的內容,實在是不應該。因為瀏覽器、某些圖形化工具在處理含有'\0'的字符串時會自動截斷'\0'后面的內容,依舊無法確定是表里的哪個字段。

后來,干脆使用古老而經典的方法:在程序日志中按字節內容輸出字符串變量(最好加上其長度),很快就准確找到了引發錯誤的字段。

同時,代碼原因還是數據原因也隨之確定。在本例中,特殊格式的數據文件是由一個早期版本的C程序生成的,很可能由於字符串初始化不徹底,生成的部分字段內容在正確內容后附加了一個'\0'和少許亂碼,從而引發這次事件。

四、事件解決

如果按照官方的推薦做法而直接對嫌疑字符串使用str.replaceAll('\u0000', ''),雖然避免了錯誤發生,之后的亂碼卻會存入數據庫並最終顯示在頁面。經與客戶溝通,確認'\0'之后均為亂碼,於是在程序代碼中將所有的嫌疑字段的'\0'及亂碼一起截斷:

str.trim().split('\u0000')[0];

至此,這次折磨人多日的事件終於得到解決。

 

PS:該程序以前在Oracle環境沒出現問題,因為Oracle可接受中間帶'\0'的字符串進行存儲,並在各種界面顯示內容時會自動截斷后面的內容,因而查不出原因,只有通過length()函數查詢字符串長度才能發現不一致之處。


免責聲明!

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



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