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

1、概述

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

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

2、經驗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語句狀況,根據報錯信息便可具體定位是哪一個表引發。

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

原本到這階段已經至關接近成功了,但仍是在此犯了錯誤:過於依賴頁面所顯示的內容,實在是不該該。由於瀏覽器、某些圖形化工具在處理含有'\0'的字符串時會自動截斷'\0'後面的內容,依舊沒法肯定是表裏的哪一個字段。工具

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

同時,代碼緣由仍是數據緣由也隨之肯定。在本例中,特殊格式的數據文件是由一個早期版本的C程序生成的,極可能因爲字符串初始化不完全,生成的部分字段內容在正確內容後附加了一個'\0'和少量亂碼,從而引起此次事件。編碼

4、事件解決

若是按照官方的推薦作法而直接對嫌疑字符串使用str.replaceAll('\u0000', ''),雖然避免了錯誤發生,以後的亂碼卻會存入數據庫並最終顯示在頁面。經與客戶溝通,確認'\0'以後均爲亂碼,因而在程序代碼中將全部的嫌疑字段的'\0'及亂碼一塊兒截斷:日誌

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

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

 

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

相關文章
相關標籤/搜索