Redrock Postgres 文档
主页 切换暗/亮/自动模式 切换暗/亮/自动模式 切换暗/亮/自动模式 返回首页

与 psycopg2 的区别

Psycopg 3 使用的是同许多其他数据库适配器一样的 DBAPI 结构,并尝试尽可能在行为上接近psycopg2。然而,有一些差异需要注意。

大多数情况下,此处建议的解决方法会同时适用于 Psycopg 2 和 3,如果您要移植或编写同时适用于 Psycopg 2 和 3 的程序,这可能会很有用。

服务端绑定

Psycopg 3 将查询和参数分别发送到服务端,而不是在客户端合并它们。服务端绑定适用于普通SELECT语句和数据操作语句(INSERTUPDATEDELETE),但不适用于许多其他语句。例如,它不适用于SETNOTIFY语句:

>>> conn.execute("SET TimeZone TO %s", ["UTC"])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: SET TimeZone TO $1
                        ^

>>> conn.execute("NOTIFY %s, %s", ["chan", 42])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: NOTIFY $1, $2
               ^

也不适用于任何数据定义语句:

>>> conn.execute("CREATE TABLE foo (id int DEFAULT %s)", [42])
Traceback (most recent call last):
...
psycopg.errors.UndefinedParameter: there is no parameter $1
LINE 1: CREATE TABLE foo (id int DEFAULT $1)
                                         ^

有时,PostgreSQL 提供了替代方案:例如,可以使用 set_config() 函数代替SET语句,可以使用 pg_notify() 函数代替NOTIFY语句:

>>> conn.execute("SELECT set_config('TimeZone', %s, false)", ["UTC"])

>>> conn.execute("SELECT pg_notify(%s, %s)", ["chan", "42"])

如果无法使用服务端绑定,您必须在客户端合并查询和参数。您可以使用psycopg.sql对象来执行此操作:

>>> from psycopg import sql

>>> cur.execute(sql.SQL("CREATE TABLE foo (id int DEFAULT {})").format(42))

或创建客户端绑定游标,例如ClientCursor

>>> cur = ClientCursor(conn)
>>> cur.execute("CREATE TABLE foo (id int DEFAULT %s)", [42])

如果您经常需要ClientCursor,可以设置Connection.cursor_factory默认调用Connection.cursor()创建它们。这样,Psycopg 3 的行为方式与 Psycopg 2 基本相同。

请注意,无论是服务端还是客户端,您都只能指定作为参数(即用单引号括起来的字符串)。如果需要参数化语句的不同部分(例如表名),则必须使用该psycopg.sql模块:

>>> from psycopg import sql

# This will quote the user and the password using the right quotes
# e.g.: ALTER USER "foo" SET PASSWORD 'bar'
>>> conn.execute(
...     sql.SQL("ALTER USER {} SET PASSWORD {}")
...     .format(sql.Identifier(username), password))

同一查询中的多个语句

由于使用服务端绑定的关系,当使用参数时,不可能在同一个execute()调用中执行多个语句,并用分号分隔它们:

>>> conn.execute(
...     "INSERT INTO foo VALUES (%s); INSERT INTO foo VALUES (%s)",
...     (10, 20))
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: cannot insert multiple commands into a prepared statement

解决该问题的一种明显方法是使用多个execute()调用。

如果不使用参数则没有这样的限制。因此,您可以在客户端编写多个查询,使用psycopg.sql对象在同一个execute()调用中运行它们:

>>> from psycopg import sql
>>> conn.execute(
...     sql.SQL("INSERT INTO foo VALUES ({}); INSERT INTO foo values ({})"
...     .format(10, 20))

或者使用客户端绑定游标

>>> cur = psycopg.ClientCursor(conn)
>>> cur.execute(
...     "INSERT INTO foo VALUES (%s); INSERT INTO foo VALUES (%s)",
...     (10, 20))

如果一条语句必须在事务外执行(例如CREATE DATABASE),则它不能与其他语句一起批量执行,即使连接处于自动提交模式:

>>> conn.autocommit = True
>>> conn.execute("CREATE DATABASE foo; SELECT 1")
Traceback (most recent call last):
...
psycopg.errors.ActiveSqlTransaction: CREATE DATABASE cannot run inside a transaction block

发生这种情况是因为 PostgreSQL 本身会将多个语句包装在一个事务中。请注意,您会在 psql 中体验到不同的行为(psql 会按分号分割查询并将它们单独发送到服务端)。

这不是 Psycopg 3 新引入的行为:同样的限制也存在于psycopg2

从多个语句返回多个结果

如果在 psycopg2 中执行多个返回结果的语句,则仅返回最后一条语句的结果:

>>> cur_pg2.execute("SELECT 1; SELECT 2")
>>> cur_pg2.fetchone()
(2,)

而在 Psycopg 3 中,所有的结果都可以获取。运行查询后,第一个结果将在游标中随时可用,并且可以使用常用的fetch*()方法获取。为了访问后面的结果,您可以使用Cursor.nextset()方法:

>>> cur_pg3.execute("SELECT 1; SELECT 2")
>>> cur_pg3.fetchone()
(1,)

>>> cur_pg3.nextset()
True
>>> cur_pg3.fetchone()
(2,)

>>> cur_pg3.nextset()
None  # no more results

请记住,如果您向查询传递参数,则不能使用服务端绑定在同一查询中执行多个语句

不同的转换规则

在极少数情况下,尤其是在可变参数的函数方面,PostgreSQL 可能无法找到给定数据类型的候选函数:

>>> conn.execute("SELECT json_build_array(%s, %s)", ["foo", "bar"])
Traceback (most recent call last):
...
psycopg.errors.IndeterminateDatatype: could not determine data type of parameter $1

这可以通过强制转换显式指定参数类型来解决:

>>> conn.execute("SELECT json_build_array(%s::text, %s::text)", ["foo", "bar"])

不能对元组使用IN %s

IN不能与用作单个参数的元组一起使用,而psycopg2是可以的:

>>> conn.execute("SELECT * FROM foo WHERE id IN %s", [(10,20,30)])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: SELECT * FROM foo WHERE id IN $1
                                      ^

您可以做的是使用 = ANY() 形式的表达式,以列表而不是元组传递候选值,这样可以适配到 PostgreSQL 数组:

>>> conn.execute("SELECT * FROM foo WHERE id = ANY(%s)", [[10,20,30]])

请注意,ANY()也可以在psycopg2中使用,并且具有接受空值列表作为参数的优点,而IN运算符不支持空值列表作为参数。

不能使用IS %s

您不能使用IS %sIS NOT %s

>>> conn.execute("SELECT * FROM foo WHERE field IS %s", [None])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: SELECT * FROM foo WHERE field IS $1
                                     ^

这可能是由于在 PostgreSQL 中 IS不是二元运算符所致;相反,IS NULLIS NOT NULL是一元运算符,您不能在IS的右侧放置其他任何内容一起使用。在 psql 中测试:

=# SELECT 10 IS 10;
ERROR:  syntax error at or near "10"
LINE 1: select 10 is 10;
                     ^

您可以做的是使用 IS DISTINCT FROM 运算符,它可以接受占位符:

>>> conn.execute("SELECT * FROM foo WHERE field IS NOT DISTINCT FROM %s", [None])

类似地,您可以将IS DISTINCT FROM %s用作IS NOT %s的参数版本。

不同的适配系统

为了解决服务端的参数适配,同时也考虑性能、灵活性、易于定制性,Psycopg 3 的适配系统已经完全重写。

内置数据的默认行为应该是您所期望的。如果您自定义了适配数据的方式,或者如果您正在管理自己的扩展类型,则应该查看新的适配系统

COPY 不再基于文件

psycopg2公开了与 PostgreSQL COPY交互的一些复制方法。它们基于文件的接口并不容易将动态生成的数据加载到数据库中。

现在有一个copy()方法,类似于psycopg2 copy_expert(),接受自由格式的COPY命令,并返回一个对象,按块或按记录读取/写入数据。不同的COPY使用模式还可以用于异步交互。

有关详细信息,请参阅使用 COPY TO 和 COPY FROM

with连接

psycopg2中,使用 with 连接 语法,仅关闭事务,而不关闭连接。对于习惯了其他几个包装资源(例如文件)的 Python 类的人来说,这种行为是令人惊讶的。

在 Psycopg 3 中,使用 with 连接 将在with块末尾关闭连接,从而使处理连接资源更加熟悉。

为了将事务作为代码块进行管理,您可以使用Connection.transaction()方法,该方法允许更精细的控制,例如使用嵌套事务。

有关详细信息,请参阅事务上下文

callproc()没有了

cursor.callproc()没有实现。该方法具有简单的语义,不考虑 PostgreSQL 位置参数、过程、返回集合的函数等等。可使用SELECT function_name(...)CALL procedure_name(...)语句调用普通的 execute() 方法代替。

client_encoding没有了

Psycopg 自动使用数据库客户端编码将数据解码为 Unicode 字符串。如果您需要读取编码,请使用ConnectionInfo.encoding。您可以使用client_encoding连接参数在连接时选择编码,并且可以通过运行SET client_encoding语句来更改连接的编码。但是为什么要这样做呢?

没有默认的无限日期处理

PostgreSQL 可以表示比 Python 范围更大的日期和时间戳。Python 日期仅限于 1 到 9999 之间的年份(由datetime.date.minmax等常量表示),而 PostgreSQL 日期则扩展到 BC 日期和 10K 以上的年份。此外,PostgreSQL 还可以在两个方向上表示符号日期 “infinity”。

在 psycopg2 中,默认情况下,无限日期和时间戳映射到 ‘date.max’ 和类似的常量。这存在创建非对称映射的问题(两个 Postgres 日期,无穷大和 9999-12-31,都映射到相同的 Python 日期)。还有一个反常现象是,有效的 Postgres 日期(大于 Python 的date.max但小于无穷大)仍然会溢出。

在 Psycopg 3 中,每个大于 9999 年的日期都会溢出,包括无穷大。如果您想自定义此映射(例如,将 10000 年后的每个日期按date.max展平),您可以子类化并调整适当的加载器:查看此示例以了解如何操作。

Psycopg 3 中的新增功能