注:本文适用于 Python 3 环境。

最近工作中需要用到 Python 来操作 PostgreSQL 数据库,最终选用的是 Psycopg2 库。关于具体如何用 Psycopg2 来操作 PostgreSQL,本文不再赘述。这里只记录自己踩过的坑。

 

连接数据库时的编码问题

在用 psycopg2.extensions.cursor 类的 execute() 方法从数据库中取数据时,如果结果集中含有中文字符,经常会抛出类似于下面的异常:

Traceback (most recent call last):
  File "./crawler.py", line 227, in 
	CrawlByHost(host_list['whitelist'])
  File "./crawler.py", line 212, in CrawlByHost
	ChildCrawlProcess(host_item, date)
  File "./crawler.py", line 185, in ChildCrawlProcess
	for (url, ) in cursor.fetchall():
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 71: ordinal not in range(128)

起初我疑心是 Psycopg2 库没法正确地处理 UTF-8 字符……但很快又打消了这种想法。原因有两点:

  • 同事用 Python 2 + Psycopg2 操作 PostgreSQL 数据库,并没有出现我遇到的这个问题。没有理由 Psycopg2 对 Python 2 还支持 UTF-8 编码,到了 Python 3 就不支持了。
  • Psycopg2 的项目主页上,清清楚楚地写着:
    Psycopg 2 is both Unicode and Python 3 friendly.

既然不是 Psycopg2 自身的问题,那么最有可能的就是连接数据库时采用的编码不对啦~

Google 了一番之后,终于在这个页面发现了线索:

In Python 3 instead the strings are automatically decoded in the connection encoding, as the str object can represent Unicode characters.

也就是说,Psycopg2 对字符解码采用的就是连接编码。而连接的编码可以用 psycopg2.extensions.connection 类的 set_client_encoding(enc) 方法来设置。其中,enc 参数值必须是 PostgreSQL 支持的编码类型。如果没有用 set_client_encoding(enc) 方法来设置编码类型,则连接编码即默认为 PostgreSQL 的 server_encoding 编码值。

PS. PostgreSQL 的 server_encoding 参数值,可以在 PostgreSQL 的终端下用 show server_encoding; 命令来查看。

经查看,PostgreSQL 和 Psycopg2 的连接编码类型都是 ascii,这样当然无法解析中文字符了。

解决方法很简单,用 Psycopg2 建立 PostgreSQL 数据库连接时,紧接着再调用 set_client_encoding("UTF8") 将连接编码置为 UTF-8 即可。

参考链接:

 

多进程环境下,数据库连接复用问题

出于程序的健壮性考虑,由于自己写的那个 Python 程序采用的是多进程模型,其中每个进程都会操作 PostgreSQL 数据库。很自然,我希望所有的进程都能复用同一个 PostgreSQL 数据库连接。这样,不但可以减少数据库连接数目,而且也免去了在新进程里重新创建数据库连接的烦琐。

可是程序写罢之后,发现程序读写数据库时,总是出现奇怪的错误,根本无法正常地跑起来。

又是各种 Google,发现在 fork() 创建新进程后,Psycopg2 不能复用以前的 PostgreSQL 数据库连接,新进程必须要重新建立连接。

可以参考 Psycopg2 项目这个页面的描述:

The above observations are only valid for regular threads: they don't apply to forked processes nor to green threads. libpq connections shouldn't be used by a forked processes, so when using a module such as multiprocessing or a forking web deploy method such as FastCGI make sure to create the connections after the fork.

原因是 PostgreSQL 的 libpq 库的连接不支持 fork()。具体可以参考 PostgreSQL 官方的页面描述

On Unix, forking a process with open libpq connections can lead to unpredictable results because the parent and child processes share the same sockets and operating system resources. For this reason, such usage is not recommended, though doing an exec from the child process to load a new executable is safe.

参考链接:

Leave a Reply

Your email address will not be published. Required fields are marked *