Xupeng's blog

Jan 1, 2009 - 2 minute read - Comments

使用 PostgreSQL 时遇到的编码错误

PostgreSQL 数据库的编码为 UTF8(在 PostgreSQL 里 UNICODE 是 UTF8 的别名,二者要表达的意义是一样的),当往数据表的 varchar 字段里插入字符串的时候,遇到编码错误:

test=# insert into bb values (E'\x80\x02]q\x01(U\x01aU\x06\xe5\x93\x88\xe5\x93\x88q\x02e.');
ERROR:  invalid byte sequence for encoding "UTF8": 0x80
HINT:  This error can also happen if the byte sequence does not match the encoding expected by the server, which is controlled by "client_encoding".

为了理解造成这个错误的原因,我和小蔡花了差不多一个小时在这上面,从这个错误的字面意义上理解,是在以 UTF8 编码存储这个字符串的时候,遇到了编码错误,其实类似这样的编码错误在使用 Python 进行文本解析做编解码时会经常遇到,但又通常忽略之。

还是从遇到这个问题的源头说起,起因是要使用 cPickle 把一个不算太大的 Python dict 序列号以后存储到数据库中,但是在使用默认参数进行序列化后向数据库中存的时候就遇到了类似上面的编码错误:

data = {'title' : 'This is the title', ......}
s_data = cPickle.dumps(data, 2)
# Insert s_data into PostgreSQL database table

在经过一番 RTFM 和揣摩验证之后发现,当往 UTF8 的 PostgreSQL 数据库中存储字符串时,PostgreSQL 会根据 client_encoding 判断字符串的编码,然后根据这个编码解码,再编码为 UTF8 进行存储。可以在 PostgreSQL 的 console 下使用 show client_encoding 来查看当前的客户端编码,同时也可以使用 \encoding CLIENT-ENCODING 来设置客户端编码。

我的客户端编码为 UTF8,同时数据库编码为 UTF8,因此 PostgreSQL 把我提交的字符表当做 UTF8 串来对待,而再回头看我要插入的字符串: “\x80\x02]q\x01(U\x01aU\x06\xe5\x93\x88\xe5\x93\x88q\x02e”,这下恍然大悟了,为什么呢?因为这根本不是一个合法的 UTF8 串,错误消息也明确的说\x80是’invalid byte sequence for encoding “UTF8”’,好,那看一下 UTF8的定义:

80-BF是只能出现在多字节字符的第二、三或者第四字节,而在这个字符串中它却出现在了第一个字节,因此很显然它不是合法的 UTF8 串。那为什么会有这样的一个字符串呢?恩,是我的字段拿 cPickle.dumps(data,2) 序列化以后产生的,经过试验,在使用 cPickle.dumps(data,2) 对字典做序列化后,会有 \x80 作为前缀,如此以来,这个序列化后的串就不能被存储在 UTF8 的 PostgreSQL 数据库中,但是事实上使用 protocol 1 来进行序列化就可以了:

data = {'title' : 'This is the title', ......}
s_data = cPickle.dumps(data, 1)
# Insert s_data into PostgreSQL database table