psycopg 3.2.11 COPY 第二行报 integer expected,检查混合值类型
psycopg 3.2.11 的 COPY 行适配可能固定首个 Python 类型,导致后续异类型值失败,3.2.12已恢复相关行为。
用 COPY FROM STDIN 向 PostgreSQL 的 text 列写入两行,先写 Python 整数 1,再写字符串 '2',第二行却报 psycopg.DataError: integer expected, got 'str'。把顺序反过来,又可能变成另一条类型错误。
这符合 psycopg 3.2.11 的一项适配回归。同一份输入在 3.2.10 可以工作:普通文本 COPY 会对每一行的 Python 值进行适配,数据库再按目标列解析。3.2.11 的优化将某列首个值的类型固定下来,后面的不同 Python 类型就被送入不匹配的 dumper。错误因此与行顺序有关,不一定表示数据库列定义错了。
使用包含回退修补的版本
psycopg 3.2.12 已包含修补,恢复这条 COPY 路径原有的适配行为。更新到包含修补且适合项目的版本后,要核对应用实际使用的 psycopg 实现;只更新某个独立测试环境,不能证明运行中的应用已经替换。
在已有测试数据库上新建连接,可以用临时表验证两种输入顺序:
import psycopg
with psycopg.connect("dbname=copy_check") as conn:
with conn.cursor() as cur:
cur.execute("CREATE TEMP TABLE copy_check(value text) ON COMMIT DROP")
for values in ((1, "2"), ("1", 2)):
cur.execute("TRUNCATE copy_check")
with cur.copy("COPY copy_check FROM STDIN") as copy:
for value in values:
copy.write_row((value,))
cur.execute("SELECT value FROM copy_check ORDER BY value")
assert cur.fetchall() == [("1",), ("2",)]
这里的目标是新连接中的临时表,不应换成生产目标表,也不要用一整批真实导入数据来试错。两个顺序都成功且值正确,才说明该回归路径恢复;仅让第一行通过,没有覆盖问题。
文本 COPY 和二进制 COPY 的契约不同
这份例子使用普通文本 COPY,不能据此认为二进制 COPY 也可以依赖数据库自动把任意 Python 类型转换成目标列类型。二进制格式对类型更严格,必要时应按真实 schema 使用 set_types(),并核对整数宽度、数值类型等要求。
也不必把所有输入统一 str() 后当作通用修复。那会改变日期、NULL、二进制值等原有适配契约,还可能掩盖真正不符合目标列的数据。
一次 COPY 出错后,应按原有事务策略处理失败,不要捕获类型异常后在同一失败事务里继续导入,更不能不核对事务状态就重试整批。更新后的验收应包含行数、值和不同类型顺序,并保持已有的导入原子性与失败处理逻辑。


