一次倍受折磨的“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

相关文章
相关标签/搜索