记使用 psycopg2 事务时一件容易出错的情况

注意:以下情况中,psycopg2 的 isolation_level 不是 autocommit mode。

使用 psycopg2 操作 pg(postgresql) 时,经常会用到事务(Transaction)。

按照一般数据库的做法,我们一般会认为,事务一般是用在 INSERT/UPDATE/DELETE 和其他对数据库有更改的 SQL 语句中,如下就是处理事务的代码写法:

import psycopg2

conn = psycopg2.connect(...)
try:
    cur = conn.cursor()
    cur.execute(sql)
    conn.commit()
except:
    conn.rollback()

同时,我们一般也会认为,查询语句(SELECT)之类不对数据产生影响的语句,就不需要做事务处理了。

但是,在 psycopg2 中,这么做是错误的,一旦 SELECT 语句出错抛出异常(如:psycopg2.ProgrammingError)后,继续运行 SQL 语句便会抛出事务异常;

psycopg2.InternalError: current transaction is aborted, commands ignored until end of transaction block

于是,psycopg2 的 connection 对象便无法使用,每次执行 SQL 都会抛出异常(详情见上),必须运行 rollback() 后才能继续执行其他 SQL 语句;

  • 错误代码
import psycopg2

conn = psycopg2.connect(...)
try:
    cur.execute(wrong sql) # 抛出异常
except:
    pass
cur.execute(sql) # 抛出 InternalError 异常,提示 transaction block
...
  • 正确代码
import psycopg2

conn = psycopg2.connect(...)
try:
    cur = conn.cursor()
    cur.execute(sql)
except:
    conn.rollback()
...

出现这种情况大多在单连接项目,如果每次操作都创建一个新的 connection 对象的话,这个错误也就不会发生了。

官方文档 Transactions control 中对此有所警告,警告原文如下:

Warning:By default even a simple SELECT will start a transaction: in long-running programs, if no further action is taken, the session will remain “idle in transaction”, a condition non desiderable for several reasons (locks are held by the session, tables bloat…). For long lived scripts, either make sure to terminate a transaction as soon as possible or use an autocommit connection.