postgres x psycopg2 踩坑:连接中文名称数据库

-1 为什么要强调「在 Mac OS X」下

据江湖传言,在 Linux 下没有这个问题……没想到 Mac 也会入这种坑,大跌眼镜

0 背景

目前 Python 社区中最流行的用于和 Postgres 打交道的模块非 psycopg2 莫属了,连 Postgres 官方 wiki 都在介绍该模块

猜:psycopg2 = Python + SYstem + COnnection + PostGres + 2

顺便安利一下好友珂皓专门写的一个中间层 pgdb 用它来和 Postgres 打交道可以省点力气。

为了方便配置,我在连接数据库一般是在配置文件中这么写:

[conn01_name]
database = conn01_name
host = 192.168.0.123
port = 5678
user = user_sssj
password = 

然后在需要连接数据库的文件中这么写

# db.py
import pgdb

conn_01_name = pgdb.Connection(
    database=config.get('conn_01_name', 'database'),
    user=config.get('conn_01_name', 'user'),
    password=config.get('conn_01_name', 'password'),
    host=config.get('conn_01_name', 'host'),
    port=config.get('conn_01_name', 'port')
)

数据库名即 conn_01_name 为英文名如 zoo 时,这样做没什么问题。问题就在数据库名是中文的时候:

下面是在交互式shell中。这里使用的数据库名为:大象系统

>>> from db import conn_01_name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/db.py", line 52, in <module>
    port=config.get('conn_01_name', 'port')
  File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 28, in __init__
    self._reconnect()
  File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 63, in _reconnect
    self.connection = psycopg2.connect(*self.db_args, **self.db_kwargs)
  File "/path/to/py35venv/lib/python3.5/site-packages/psycopg2/__init__.py", line 129, in connect
    dsn = _ext.make_dsn(dsn, **kwargs)
  File "/path/to/py35venv/lib/python3.5/site-packages/psycopg2/extensions.py", line 177, in make_dsn
    parse_dsn(dsn)
psycopg2.ProgrammingError: invalid dsn: missing "=" after "大象系统" in connection info string

报错啦!

01 尝试

作为一个面向 StackOverFlow 和面向 Google 编程的程序猿,我当然是把错误码(最下方那行)复制起来丢到 Google 上去搜索。可惜无论我如何修改英文关键字,结果寥寥。遂想:

这类坑可能也只有我们中文圈比较有可能趟上吧……?

干脆加点中文关键字,结果依旧少得可怜……嗯其实是几乎就没有结果,不过好歹找到一篇中文博客,大意是说这可能是编码类型造成的?唔这我之前也有想过,但怎么设置呢?是哪里的编码类型?文章中说的是要去设置 connection.extensions.set_client_encoding,顾名思义是客户端的编码类型,设为 "UTF8" 即可。

我当时就像看到了救命稻草,也没再仔细看,尝试了一下

conn_01_name = pgdb.Connection(
    database=config.get('conn_01_name', 'database'),
    user=config.get('conn_01_name', 'user'),
    password=config.get('conn_01_name', 'password'),
    host=config.get('conn_01_name', 'host'),
    port=config.get('conn_01_name', 'port')
)
conn_01_name.connection.set_client_encoding('UTF-8')

(注:这里用 Pgdb 实验其实没有太大影响,因为 Connection 用于包装的中间层非常简单,见下可知)

# pgdb.py
import psycopg2

class Connection:
      def __init__(self, *args, **kwargs):
          self.connection = None
          self.db_args = args
          self.db_kwargs = kwargs
          self._reconnect()
      
      def _reconnect(self):
          self._close()
          self.connection = psycopg2.connect(*self.db_args, **self.db_kwargs)
          self.autocommit = True

呃,还是连不上。我想了一下就知道自己思路不对:

是因为那个方法要执行,必须要建立连接【后】才有用?而我的连接都还没建立就报错了,自然无法到执行 set_client_encoding 的那步就报错了

要不我能不能看看建立连接【时】有没有相应参数来初始化?又扫了一遍那篇中文博客,文中确实提到的是「取数据时」,情况和我不同,也侧面验证了一下我的猜测。查了一下官方文档,是有这个参数的:即在初始化 psycopg2.connect 类时,可使用 client_encoding 来指定编码类型

client_encoding

This sets the client_encoding configuration parameter for this connection. In addition to the values accepted by the corresponding server option, you can use auto to determine the right encoding from the current locale in the client (LC_CTYPE environment variable on Unix systems).

(注意到下面这份代码直接改用 psycopg2 来连接了,这样比 pgdb 实验更快一点)

import psycopg2
c = psycopg2.connect(dbname="大象系统", user="ssj", password="",
        port="5678", host="192.168.0.123", client_encoding="UTF-8")

结果也是失败的:仍然和上面是同样的报错。我甚至考虑了会不会是参数的位置导致的?当然事实证明也没有用。

02 转机

不死心,继续找答案,一边看文档。我想了一下,报错一直说 dsn:

psycopg2.ProgrammingError: invalid dsn: missing "=" after "大象系统"
in connection info string

上面强行换行,方便看

dsn是啥?可能不是很重要,但是上面这,以及刚才在看官方文档时的线索似乎给了我提示:(先看文档,我当时注意到了这部分)

# psycopg2 初始化连接的 2 种方式
conn = psycopg2.connect("dbname=test user=postgres password=secret")

# 或者
conn = psycopg2.connect(dbname="test", user="postgres", password="secret")

结合报错提示,它是说实际上 psycopg2 的解析过程是把关键字参数转换成字符串什么的一种叫 dsn 的东西,然后用 parse_dsn (见上面的报错提示)解析时报错是吗?于是我尝试重新连接:

import psycopg2
conn = psycopg2.connect("dbname=大象系统 user=ssj password='' port=5678 host=192.168.0.123")

仍是失败的。与此同时,我看到了一个提示

import psycopg2
conn_string = "host='localhost' dbname='my_database' user='postgres' password='secret'"
conn = psycopg2.connect(conn_string)

啊!引号嵌套,我可能就是少了这个。那么其实我可以这样写

import psycopg2
conn = psycopg2.connect("dbname='%(dbname)s' user='%(user)s' password='%(password)s'\
port='%(port)s' host='%(host)s'"
% {'dbname': '大象系统', 'user': 'ssj', 'password': '', 'port': '5678',
   'host': '192.168.0.123'})

这样尝试之后就成功了。对应的 pgdb 写法是

import pgdb
conn = pgdb.Connection("dbname='%(dbname)s' user='%(user)s' password='%(password)s'\
port='%(port)s' host='%(host)s'"
% {'dbname': '大象系统', 'user': 'ssj', 'password': '', 'port': '5678',
   'host': '192.168.0.123'})

03 尾声和风波

终于能够成功连接中文名称的数据库了,别提有多高兴。这当然马上要查询一下某些表看看效果啦:

>>> from db import conn
>>> sql = "SELECT animalcode FROM 动物园"
>>> ret = conn.query(sql,)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 76, in query
    raise e
  File "/path/to/py35venv/lib/python3.5/site-packages/pgdb/pgdb.py", line 69, in query
    cursor.execute(*args, **kwargs)
psycopg2.ProgrammingError: relation "动物园" does not exist
LINE 1: SELECT animalcode FROM 动物园

啥?这也行?我还以为是中文表名又出错了。尝试了一些英文表名,结果也是 relation xxx does not exist

难道是我被「骗」了吗?我没有实际建立连接但绕开了报错?于是我用了 conn_zoo.ensure_connected 和 cursor 等方法查询了一下,好像是有建立起来呀……我索性用同样的连接法(字符串初始化连接法)去尝试连接其他数据库(非中文名),连上了且成功查询到其他英文表名的记录。

这说明我这种初始化方法应该是没有大问题的。但到底是哪里出错我一下子也想不起来。后来我看了一下配置文件,突然想到这两天配服务器时看各种文档,专门说到一些数据库的端口值会设为非默认值(有时候可能是为了安全,有时候是为了方便隧道访问什么的)——会不会是端口值错了?啊!那么,可能用户名之类的也有问题。

然后我去核对了 DataGrip 中可以正确连接数据库的配置,和我写到程序配置文件中的数据库配置……就知道是我大意了:确实是配置文件写错了。

改完配置文件后,就能正常连接中文名称的数据库,查询 SQL 语句啦!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341